1#!/usr/bin/env python3

2

3 import gi

4 import sys

5 import gettext

6 from time import sleep

7

8 from gettext import gettext as _

9

10 gi.require_version("Gdk", "3.0")

11 gi.require_version("Gio", "2.0")

12 gi.require_version("Gtk", "3.0")

13 from gi.repository import Gio, Gtk, Gdk # noqa: E402

14 from optparse import OptionParser # noqa: E402

15

16

17 EXIT_WITH_FAILURE = 1

18 EXIT_WITH_SUCCESS = 0

19 EXIT_TIMEOUT = 30

20

21

22 class Direction(object):

23

24 def __init__(self, name):

25 self.name = name

26 self.tested = False

27 self.value = getattr(Gdk.ScrollDirection, name.upper())

28

29

30 class GtkScroller(object):

31

32 exit_code = EXIT_WITH_FAILURE

33

34 def __init__(self, directions, edge_scroll=False):

35 self.directions = directions

36 self.edge_scroll = edge_scroll

37 self.touchpad_key = "org.gnome.settings-daemon.peripherals.touchpad"

38 self.horiz_scroll_key = True

39 source = Gio.SettingsSchemaSource.get_default()

40 if not source.lookup(self.touchpad_key, True):

41 self.touchpad_key = "org.gnome.desktop.peripherals.touchpad"

42 self.horiz_scroll_key = False

43 self.touchpad_settings = Gio.Settings.new(self.touchpad_key)

44

45 # Initialize GTK constants

46 self.ICON_SIZE = Gtk.IconSize.BUTTON

47 self.ICON_TESTED = Gtk.STOCK_YES

48 self.ICON_UNTESTED = Gtk.STOCK_DIALOG_QUESTION

49 self.ICON_NOT_REQUIRED = Gtk.STOCK_REMOVE

50

51 self.button_factory = Gtk.Button

52 self.hbox_factory = Gtk.HBox

53 self.image_factory = Gtk.Image

54 self.label_factory = Gtk.Label

55 self.vbox_factory = Gtk.VBox

56

57 # Create GTK window.

58 window = Gtk.Window()

59 window.set_type_hint(Gdk.WindowType.TOPLEVEL)

60 window.add_events(

61 Gdk.EventMask.SCROLL_MASK | Gdk.EventMask.SMOOTH_SCROLL_MASK

62 )

63 window.set_size_request(200, 100)

64 window.set_resizable(False)

65 window.set_title(_("Type Text"))

66 window.connect("delete-event", lambda w, e: self.quit())

67 window.connect("scroll-event", self.on_scroll)

68 window.show()

69

70 # Add common widgets to the window.

71 vbox = self._add_vbox(window)

72 self.label = self._add_label(vbox)

73 button_hbox = self._add_hbox(vbox)

74 validation_hbox = self._add_hbox(vbox)

75 self.status = self._add_label(vbox)

76 self.exit_button = self._add_button(vbox, "_Close")

77 self.exit_button.connect("clicked", lambda w: self.quit())

78

79 # Add widgets for each direction.

80 self.icons = {}

81 for direction in self.directions:

82 self._add_label(button_hbox, direction.name)

83 self.icons[direction] = self._add_image(

84 validation_hbox, self.ICON_UNTESTED

85 )

86

87 self.show_text(

88 _("Please move the mouse cursor to this window.")

89 + "\n"

90 + _("Then scroll in each direction on your touchpad.")

91 )

92

93 def _add_button(self, context, label):

94 button = self.button_factory.new_with_mnemonic(label)

95 context.add(button)

96 button.show()

97 return button

98

99 def _add_hbox(self, context, spacing=4):

100 hbox = self.hbox_factory()

101 context.add(hbox)

102 hbox.set_spacing(4)

103 hbox.show()

104 return hbox

105

106 def _add_image(self, context, stock):

107 image = self.image_factory(stock=stock, icon_size=self.ICON_SIZE)

108 context.add(image)

109 image.show()

110 return image

111

112 def _add_label(self, context, text=None):

113 label = self.label_factory()

114 context.add(label)

115 label.set_size_request(0, 0)

116 label.set_line_wrap(True)

117 if text:

118 label.set_text(text)

119 label.show()

120 return label

121

122 def _add_vbox(self, context):

123 vbox = self.vbox_factory()

124 vbox.set_homogeneous(False)

125 vbox.set_spacing(8)

126 context.add(vbox)

127 vbox.show()

128 return vbox

129

130 def run(self):

131 # Save touchpad settings.

132 self.saved_edge_scrolling_enabled = self.touchpad_settings.get_boolean(

133 "edge-scrolling-enabled"

134 )

135 self.saved_two_finger_enabled = self.touchpad_settings.get_boolean(

136 "two-finger-scrolling-enabled"

137 )

138

139 # Set touchpad settings.

140 if self.edge_scroll:

141 self.touchpad_settings.set_boolean("edge-scrolling-enabled", True)

142 self.touchpad_settings.set_boolean(

143 "two-finger-scrolling-enabled", False

144 )

145 else:

146 self.touchpad_settings.set_boolean(

147 "two-finger-scrolling-enabled", True

148 )

149 self.touchpad_settings.set_boolean("edge-scrolling-enabled", False)

150 Gtk.main()

151

152 def quit(self):

153 # Reset touchpad settings.

154 self.touchpad_settings.set_boolean(

155 "two-finger-scrolling-enabled", self.saved_two_finger_enabled

156 )

157 # GNOME does not like when both settings are set at the same time, so

158 # waiting a bit.

159 sleep(0.1)

160 self.touchpad_settings.set_boolean(

161 "edge-scrolling-enabled", self.saved_edge_scrolling_enabled

162 )

163 Gtk.main_quit()

164

165 def show_text(self, text, widget=None):

166 if widget is None:

167 widget = self.label

168 widget.set_text(text)

169

170 def found_direction(self, direction):

171 direction.tested = True

172 self.icons[direction].set_from_icon_name(

173 self.ICON_TESTED, size=self.ICON_SIZE

174 )

175 self.check_directions()

176

177 def check_directions(self):

178 if all([direction.tested for direction in self.directions]):

179 self.show_text(

180 _("All required directions have been tested!"), self.status

181 )

182 self.exit_code = EXIT_WITH_SUCCESS

183 self.exit_button.grab_focus()

184

185 def on_scroll(self, window, event):

186 for direction in self.directions:

187 scroll_delta, delta_x, delta_y = event.get_scroll_deltas()

188 if scroll_delta:

189 event_direction = None

190 # Arbitrarily using 0.8, which requires a little bit of hand

191 # movement on the touchpads used for testing.

192 # Note that the directions are based on the default natural

193 # scrolling settings in GNOME settings.

194 if delta_x > 0.8:

195 event_direction = Direction("left")

196 elif delta_x < -0.8:

197 event_direction = Direction("right")

198 if delta_y > 0.8:

199 event_direction = Direction("up")

200 elif delta_y < -0.8:

201 event_direction = Direction("down")

202 if event_direction:

203 if direction.value == event_direction.value:

204 self.found_direction(direction)

205 break

206 return True

207

208

209 def main(args):

210 gettext.textdomain("checkbox")

211

212 usage = """Usage: %prog DIRECTION... [--edge-scroll]"""

213 parser = OptionParser(usage=usage)

214 parser.add_option(

215 "--edge-scroll",

216 action="store_true",

217 default=False,

218 help="Force touchpad to use edge scrolling only",

219 )

220 (options, args) = parser.parse_args(args)

221

222 if not args:

223 parser.error("Must specify directions to test.")

224

225 directions = []

226 for arg in args:

227 try:

228 direction = Direction(arg)

229 except AttributeError:

230 parser.error("Unsupported direction: %s" % arg)

231 directions.append(direction)

232

233 scroller = GtkScroller(directions, edge_scroll=options.edge_scroll)

234 try:

235 scroller.run()

236 except KeyboardInterrupt:

237 scroller.show_text(_("Test interrupted"), scroller.status)

238 scroller.quit()

239

240 return scroller.exit_code

241

242

243 if __name__ == "__main__":

244 sys.exit(main(sys.argv[1:]))