Status: | Needs review |
---|---|
Proposed branch: | lp:~henrikno/lookit/trunk |
Merge into: | lp:lookit |
Diff against target: |
378 lines (+166/-79) 5 files modified
src/lookit/data/pref.xml (+33/-0) src/lookit/lookit (+15/-12) src/lookit/prefdlg.py (+8/-0) src/lookit/screencapper.py (+94/-65) src/lookit/uploader.py (+16/-2) |
To merge this branch: | bzr merge lp:~henrikno/lookit/trunk |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Zach Tibbitts | Pending | ||
Review via email:
|
Commit message
Description of the change
To post a comment you must log in.
lp:~henrikno/lookit/trunk
updated
- 9. By Henrik Nordvik
-
Avoid errors when not dragging a selection area.
- 10. By Henrik Nordvik
-
Added an option to select directory to save screenshots.
- 11. By Henrik Nordvik
-
Using Xlib and grab_pointer to get window/area selection.
Unmerged revisions
- 11. By Henrik Nordvik
-
Using Xlib and grab_pointer to get window/area selection.
- 10. By Henrik Nordvik
-
Added an option to select directory to save screenshots.
- 9. By Henrik Nordvik
-
Avoid errors when not dragging a selection area.
- 8. By Henrik Nordvik
-
Added some error checking code.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/lookit/data/pref.xml' | |||
2 | --- src/lookit/data/pref.xml 2010-05-25 23:53:57 +0000 | |||
3 | +++ src/lookit/data/pref.xml 2010-05-30 06:56:26 +0000 | |||
4 | @@ -52,6 +52,39 @@ | |||
5 | 52 | </packing> | 52 | </packing> |
6 | 53 | </child> | 53 | </child> |
7 | 54 | <child> | 54 | <child> |
8 | 55 | <object class="GtkHBox" id="hbox2"> | ||
9 | 56 | <property name="visible">True</property> | ||
10 | 57 | <child> | ||
11 | 58 | <object class="GtkLabel" id="label10"> | ||
12 | 59 | <property name="visible">True</property> | ||
13 | 60 | <property name="tooltip_text" translatable="yes">Select a directory to save the screenshots to.</property> | ||
14 | 61 | <property name="label" translatable="yes">Directory:</property> | ||
15 | 62 | </object> | ||
16 | 63 | <packing> | ||
17 | 64 | <property name="expand">False</property> | ||
18 | 65 | <property name="position">0</property> | ||
19 | 66 | </packing> | ||
20 | 67 | </child> | ||
21 | 68 | <child> | ||
22 | 69 | <object class="GtkFileChooserButton" id="savedir"> | ||
23 | 70 | <property name="visible">True</property> | ||
24 | 71 | <property name="action">select-folder</property> | ||
25 | 72 | <property name="title" translatable="yes">Select A Directory to save Screenshots</property> | ||
26 | 73 | </object> | ||
27 | 74 | <packing> | ||
28 | 75 | <property name="position">1</property> | ||
29 | 76 | </packing> | ||
30 | 77 | </child> | ||
31 | 78 | </object> | ||
32 | 79 | <packing> | ||
33 | 80 | <property name="expand">False</property> | ||
34 | 81 | <property name="position">2</property> | ||
35 | 82 | </packing> | ||
36 | 83 | </child> | ||
37 | 84 | <child> | ||
38 | 85 | <placeholder/> | ||
39 | 86 | </child> | ||
40 | 87 | <child> | ||
41 | 55 | <placeholder/> | 88 | <placeholder/> |
42 | 56 | </child> | 89 | </child> |
43 | 57 | </object> | 90 | </object> |
44 | 58 | 91 | ||
45 | === modified file 'src/lookit/lookit' | |||
46 | --- src/lookit/lookit 2010-05-25 23:53:57 +0000 | |||
47 | +++ src/lookit/lookit 2010-05-30 06:56:26 +0000 | |||
48 | @@ -28,18 +28,18 @@ | |||
49 | 28 | import urllib | 28 | import urllib |
50 | 29 | import urlparse | 29 | import urlparse |
51 | 30 | 30 | ||
53 | 31 | try: | 31 | #try: |
54 | 32 | # Try to import these from current directory, in case running from src | 32 | # Try to import these from current directory, in case running from src |
65 | 33 | import aboutdlg | 33 | import aboutdlg |
66 | 34 | import prefdlg | 34 | import prefdlg |
67 | 35 | import screencapper | 35 | import screencapper |
68 | 36 | import uploader | 36 | import uploader |
69 | 37 | except ImportError: | 37 | #except ImportError: |
70 | 38 | # Then fall back to globally installed versions | 38 | ## Then fall back to globally installed versions |
71 | 39 | from lookit import aboutdlg | 39 | #from lookit import aboutdlg |
72 | 40 | from lookit import prefdlg | 40 | #from lookit import prefdlg |
73 | 41 | from lookit import screencapper | 41 | #from lookit import screencapper |
74 | 42 | from lookit import uploader | 42 | #from lookit import uploader |
75 | 43 | 43 | ||
76 | 44 | CONF_FILE = os.path.expanduser('~/.config/lookit.conf') | 44 | CONF_FILE = os.path.expanduser('~/.config/lookit.conf') |
77 | 45 | TRASH_DIR = os.path.expanduser('~/.local/share/Trash/files') | 45 | TRASH_DIR = os.path.expanduser('~/.local/share/Trash/files') |
78 | @@ -142,7 +142,7 @@ | |||
79 | 142 | if pb != None: | 142 | if pb != None: |
80 | 143 | m = hashlib.md5() | 143 | m = hashlib.md5() |
81 | 144 | m.update(pb.get_pixels()) | 144 | m.update(pb.get_pixels()) |
83 | 145 | hashstring = m.hexdigest() + '.png' | 145 | hashstring = self.prefs['savedir'] + '/' + m.hexdigest() + '.png' |
84 | 146 | pb.save(hashstring, 'png') | 146 | pb.save(hashstring, 'png') |
85 | 147 | self.upload_image(hashstring) | 147 | self.upload_image(hashstring) |
86 | 148 | else: | 148 | else: |
87 | @@ -170,6 +170,9 @@ | |||
88 | 170 | ) | 170 | ) |
89 | 171 | elif proto == 'Imgur': | 171 | elif proto == 'Imgur': |
90 | 172 | success, data = uploader.upload_file_imgur(image) | 172 | success, data = uploader.upload_file_imgur(image) |
91 | 173 | elif proto == 'None': | ||
92 | 174 | self.show_notification('Image saved', image) | ||
93 | 175 | return | ||
94 | 173 | else: | 176 | else: |
95 | 174 | success = False | 177 | success = False |
96 | 175 | data = "Error: no such protocol: {0}".format(proto) | 178 | data = "Error: no such protocol: {0}".format(proto) |
97 | 176 | 179 | ||
98 | === modified file 'src/lookit/prefdlg.py' | |||
99 | --- src/lookit/prefdlg.py 2010-05-25 23:53:57 +0000 | |||
100 | +++ src/lookit/prefdlg.py 2010-05-30 06:56:26 +0000 | |||
101 | @@ -1,4 +1,5 @@ | |||
102 | 1 | import gtk | 1 | import gtk |
103 | 2 | import gio | ||
104 | 2 | import os | 3 | import os |
105 | 3 | import sys | 4 | import sys |
106 | 4 | 5 | ||
107 | @@ -29,6 +30,7 @@ | |||
108 | 29 | 30 | ||
109 | 30 | self.combobox = builder.get_object("combobox") | 31 | self.combobox = builder.get_object("combobox") |
110 | 31 | connections = gtk.ListStore(str) | 32 | connections = gtk.ListStore(str) |
111 | 33 | connections.append(['None']) | ||
112 | 32 | for connection in CONNECTION_TYPES: | 34 | for connection in CONNECTION_TYPES: |
113 | 33 | connections.append([connection]) | 35 | connections.append([connection]) |
114 | 34 | self.combobox.set_model(connections) | 36 | self.combobox.set_model(connections) |
115 | @@ -43,6 +45,7 @@ | |||
116 | 43 | self.password = builder.get_object("password") | 45 | self.password = builder.get_object("password") |
117 | 44 | self.directory = builder.get_object("directory") | 46 | self.directory = builder.get_object("directory") |
118 | 45 | self.url = builder.get_object("url") | 47 | self.url = builder.get_object("url") |
119 | 48 | self.savedir = builder.get_object("savedir") | ||
120 | 46 | 49 | ||
121 | 47 | builder.connect_signals(self) | 50 | builder.connect_signals(self) |
122 | 48 | 51 | ||
123 | @@ -61,6 +64,7 @@ | |||
124 | 61 | self.password.set_text(prefs['password']) | 64 | self.password.set_text(prefs['password']) |
125 | 62 | self.directory.set_text(prefs['directory']) | 65 | self.directory.set_text(prefs['directory']) |
126 | 63 | self.url.set_text(prefs['url']) | 66 | self.url.set_text(prefs['url']) |
127 | 67 | self.savedir.set_current_folder_file(gio.File(prefs['savedir'])) | ||
128 | 64 | except KeyError: | 68 | except KeyError: |
129 | 65 | if 'Imgur' in CONNECTION_TYPES: | 69 | if 'Imgur' in CONNECTION_TYPES: |
130 | 66 | self.combobox.set_active( | 70 | self.combobox.set_active( |
131 | @@ -90,10 +94,13 @@ | |||
132 | 90 | self.port.get_adjustment().set_value(21) | 94 | self.port.get_adjustment().set_value(21) |
133 | 91 | elif proto == 'SSH': | 95 | elif proto == 'SSH': |
134 | 92 | self.port.get_adjustment().set_value(22) | 96 | self.port.get_adjustment().set_value(22) |
135 | 97 | else: | ||
136 | 98 | self.port.get_adjustment().set_value(21) | ||
137 | 93 | 99 | ||
138 | 94 | 100 | ||
139 | 95 | 101 | ||
140 | 96 | def on_pref_dialog_response(self, widget, data=None): | 102 | def on_pref_dialog_response(self, widget, data=None): |
141 | 103 | print "on_pref_dialog_response" | ||
142 | 97 | if data == 1: | 104 | if data == 1: |
143 | 98 | self.prefs['trash'] = self.trash.get_active() | 105 | self.prefs['trash'] = self.trash.get_active() |
144 | 99 | self.prefs['shortenurl'] = self.shortenurl.get_active() | 106 | self.prefs['shortenurl'] = self.shortenurl.get_active() |
145 | @@ -104,6 +111,7 @@ | |||
146 | 104 | self.prefs['password'] = self.password.get_text() | 111 | self.prefs['password'] = self.password.get_text() |
147 | 105 | self.prefs['directory'] = self.directory.get_text() | 112 | self.prefs['directory'] = self.directory.get_text() |
148 | 106 | self.prefs['url'] = self.url.get_text() | 113 | self.prefs['url'] = self.url.get_text() |
149 | 114 | self.prefs['savedir'] = self.savedir.get_current_folder_file().get_path() | ||
150 | 107 | else: | 115 | else: |
151 | 108 | self.prefs = dict() | 116 | self.prefs = dict() |
152 | 109 | self.dialog.destroy() | 117 | self.dialog.destroy() |
153 | 110 | 118 | ||
154 | === modified file 'src/lookit/screencapper.py' | |||
155 | --- src/lookit/screencapper.py 2010-05-25 01:21:09 +0000 | |||
156 | +++ src/lookit/screencapper.py 2010-05-30 06:56:26 +0000 | |||
157 | @@ -1,50 +1,18 @@ | |||
158 | 1 | import gtk | 1 | import gtk |
159 | 2 | import gtk.gdk | 2 | import gtk.gdk |
160 | 3 | 3 | ||
161 | 4 | from Xlib.display import Display | ||
162 | 5 | from Xlib import X | ||
163 | 6 | from Xlib import XK | ||
164 | 7 | from Xlib import Xcursorfont | ||
165 | 8 | import Xlib | ||
166 | 9 | |||
167 | 4 | class ScreenCapper: | 10 | class ScreenCapper: |
168 | 5 | def __init__(self): | 11 | def __init__(self): |
169 | 12 | self.pixbuf = None | ||
170 | 6 | self.root = gtk.gdk.get_default_root_window() | 13 | self.root = gtk.gdk.get_default_root_window() |
171 | 7 | self.size = self.root.get_size() | 14 | self.size = self.root.get_size() |
172 | 8 | 15 | ||
173 | 9 | def on_key_press(self, widget, event): | ||
174 | 10 | if event.keyval == gtk.keysyms.Escape: | ||
175 | 11 | self.pixbuf = None | ||
176 | 12 | self.overlay.destroy() | ||
177 | 13 | gtk.main_quit() | ||
178 | 14 | |||
179 | 15 | def on_mouse_up(self, widget, event): | ||
180 | 16 | self.x2 = int(event.x) | ||
181 | 17 | self.y2 = int(event.y) | ||
182 | 18 | |||
183 | 19 | sz_x = abs(self.x1 - self.x2) | ||
184 | 20 | sz_y = abs(self.y1 - self.y2) | ||
185 | 21 | |||
186 | 22 | print sz_x, sz_y, self.x1, self.x2, self.y1, self.y2 | ||
187 | 23 | |||
188 | 24 | if sz_x == 0 and sz_y == 0: | ||
189 | 25 | # TODO: We should capture the window that was clicked | ||
190 | 26 | self.pixbuf = None | ||
191 | 27 | self.overlay.destroy() | ||
192 | 28 | gtk.main_quit() | ||
193 | 29 | |||
194 | 30 | self.pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, | ||
195 | 31 | False, 8, sz_x, sz_y) | ||
196 | 32 | self.pixbuf.get_from_drawable(self.root, | ||
197 | 33 | self.root.get_colormap(), | ||
198 | 34 | min(self.x1, self.x2), | ||
199 | 35 | min(self.y1, self.y2), | ||
200 | 36 | 0, 0, sz_x, sz_y) | ||
201 | 37 | |||
202 | 38 | self.overlay.destroy() | ||
203 | 39 | gtk.main_quit() | ||
204 | 40 | |||
205 | 41 | def on_mouse_down(self, widget, event): | ||
206 | 42 | self.x1 = int(event.x) | ||
207 | 43 | self.y1 = int(event.y) | ||
208 | 44 | |||
209 | 45 | def realize_area(self, widget): | ||
210 | 46 | self.overlay.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.CROSS)) | ||
211 | 47 | |||
212 | 48 | def capture_screen(self): | 16 | def capture_screen(self): |
213 | 49 | self.pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, | 17 | self.pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, |
214 | 50 | self.size[0], self.size[1]) | 18 | self.size[0], self.size[1]) |
215 | @@ -54,31 +22,92 @@ | |||
216 | 54 | self.size[0], self.size[1]) | 22 | self.size[0], self.size[1]) |
217 | 55 | return self.pixbuf | 23 | return self.pixbuf |
218 | 56 | 24 | ||
219 | 25 | def grab_area(self, start_x, start_y, width, height): | ||
220 | 26 | self.pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, | ||
221 | 27 | width, height) | ||
222 | 28 | self.pixbuf.get_from_drawable(self.root, self.root.get_colormap(), | ||
223 | 29 | start_x, start_y, 0, 0, width, height) | ||
224 | 30 | |||
225 | 57 | def capture_area(self): | 31 | def capture_area(self): |
252 | 58 | self.has_result = False | 32 | try: |
253 | 59 | 33 | display = Display() | |
254 | 60 | self.overlay = gtk.Window() | 34 | root = display.screen().root |
255 | 61 | 35 | cursor_font = display.open_font('cursor') | |
256 | 62 | self.overlay.connect("key_press_event", self.on_key_press) | 36 | mycursor = cursor_font.create_glyph_cursor(cursor_font, |
257 | 63 | self.overlay.connect("button_press_event", self.on_mouse_down) | 37 | Xcursorfont.crosshair, |
258 | 64 | self.overlay.connect("button_release_event", self.on_mouse_up) | 38 | Xcursorfont.crosshair+1, (0,0,0), |
259 | 65 | self.overlay.connect("realize", self.realize_area) | 39 | (0xFFFF, 0xFFFF, 0xFFFF)) |
260 | 66 | 40 | root.grab_pointer(True, X.ButtonPressMask | X.ButtonReleaseMask | | |
261 | 67 | self.overlay.set_events(gtk.gdk.BUTTON_PRESS_MASK | | 41 | X.PointerMotionMask, X.GrabModeAsync, X.GrabModeAsync, 0, |
262 | 68 | gtk.gdk.BUTTON_RELEASE_MASK | | 42 | mycursor, X.CurrentTime) |
263 | 69 | gtk.gdk.KEY_PRESS_MASK) | 43 | root.grab_keyboard(True, X.GrabModeAsync, X.GrabModeAsync, |
264 | 70 | 44 | X.CurrentTime) | |
265 | 71 | warning = gtk.Label('Warning: You must be using a window ' + | 45 | colormap = display.screen().default_colormap |
266 | 72 | 'manager that supports compositing. Press Esc to ' + | 46 | |
267 | 73 | 'exit.') | 47 | # (r,g,b) blue = (0,0,65535) |
268 | 74 | warning.show() | 48 | color = colormap.alloc_color(0, 0, 65535) |
269 | 75 | self.overlay.add(warning) | 49 | # Xor it because we'll draw with X.GXxor function |
270 | 76 | 50 | xor_color = color.pixel ^ 0xffffff | |
271 | 77 | self.overlay.set_opacity(0) | 51 | |
272 | 78 | self.overlay.fullscreen() | 52 | print hex(color.pixel), hex(xor_color) |
273 | 79 | 53 | ||
274 | 80 | self.overlay.show() | 54 | self.gc = root.create_gc( |
275 | 81 | 55 | line_width = 1, | |
276 | 82 | gtk.main() | 56 | line_style = X.LineSolid, |
277 | 83 | 57 | fill_style = X.FillSolid, | |
278 | 58 | fill_rule = X.WindingRule, | ||
279 | 59 | cap_style = X.CapButt, | ||
280 | 60 | join_style = X.JoinMiter, | ||
281 | 61 | foreground = xor_color, | ||
282 | 62 | background = display.screen().black_pixel, | ||
283 | 63 | function = X.GXxor, | ||
284 | 64 | graphics_exposures = False, | ||
285 | 65 | subwindow_mode = X.IncludeInferiors, | ||
286 | 66 | ) | ||
287 | 67 | |||
288 | 68 | pressed = False | ||
289 | 69 | done = False | ||
290 | 70 | lastx, lasty = None, None | ||
291 | 71 | while not done: | ||
292 | 72 | event = display.next_event() | ||
293 | 73 | if event.type == X.DestroyNotify: | ||
294 | 74 | self.pixbuf = None | ||
295 | 75 | done = True | ||
296 | 76 | if event.type == X.KeyPress: | ||
297 | 77 | self.pixbuf = None | ||
298 | 78 | done = True | ||
299 | 79 | if event.type == X.MotionNotify: | ||
300 | 80 | if pressed: | ||
301 | 81 | if lastx and lasty: | ||
302 | 82 | ax = abs(startx - lastx) | ||
303 | 83 | ay = abs(starty - lasty) | ||
304 | 84 | root.rectangle(self.gc, min(startx, lastx), min(starty, lasty), ax, ay) | ||
305 | 85 | lastx, lasty = event.root_x, event.root_y | ||
306 | 86 | ax = abs(startx - event.root_x) | ||
307 | 87 | ay = abs(starty - event.root_y) | ||
308 | 88 | root.rectangle(self.gc, min(startx, event.root_x), min(starty, event.root_y), ax, ay) | ||
309 | 89 | |||
310 | 90 | elif event.type == X.ButtonPress and event.child != X.NONE: | ||
311 | 91 | startx, starty = event.event_x, event.event_y | ||
312 | 92 | pressed = True | ||
313 | 93 | |||
314 | 94 | elif event.type == X.ButtonRelease and event.child != X.NONE: | ||
315 | 95 | endx, endy = event.event_x, event.event_y | ||
316 | 96 | geometry = event.child.get_geometry() | ||
317 | 97 | width, height = geometry.width, geometry.height | ||
318 | 98 | window_x, window_y = geometry.x, geometry.y | ||
319 | 99 | diff_x = abs(startx - endx) | ||
320 | 100 | diff_y = abs(starty - endy) | ||
321 | 101 | |||
322 | 102 | if diff_x == 0 and diff_y == 0: | ||
323 | 103 | self.grab_area(window_x, window_y, width, height) | ||
324 | 104 | else: | ||
325 | 105 | self.grab_area(startx, starty, diff_x, diff_y) | ||
326 | 106 | done = True | ||
327 | 107 | except: | ||
328 | 108 | raise | ||
329 | 109 | finally: | ||
330 | 110 | display.ungrab_pointer(X.CurrentTime) | ||
331 | 111 | display.ungrab_keyboard(X.CurrentTime) | ||
332 | 112 | display.flush() | ||
333 | 84 | return self.pixbuf | 113 | return self.pixbuf |
334 | 85 | 114 | ||
335 | === modified file 'src/lookit/uploader.py' | |||
336 | --- src/lookit/uploader.py 2010-05-25 01:21:09 +0000 | |||
337 | +++ src/lookit/uploader.py 2010-05-30 06:56:26 +0000 | |||
338 | @@ -71,6 +71,10 @@ | |||
339 | 71 | return PROTO_LIST | 71 | return PROTO_LIST |
340 | 72 | 72 | ||
341 | 73 | def upload_file_ftp(f, hostname, port, username, password, directory, url): | 73 | def upload_file_ftp(f, hostname, port, username, password, directory, url): |
342 | 74 | if not 'FTP' in PROTO_LIST: | ||
343 | 75 | errormsg = 'Error: FTP support not installed. Install \'ftplib\' package.' | ||
344 | 76 | print errormsg | ||
345 | 77 | return False, errormsg | ||
346 | 74 | i = open(f, 'r') | 78 | i = open(f, 'r') |
347 | 75 | 79 | ||
348 | 76 | try: | 80 | try: |
349 | @@ -88,6 +92,10 @@ | |||
350 | 88 | return True, None | 92 | return True, None |
351 | 89 | 93 | ||
352 | 90 | def upload_file_sftp(f, hostname, port, username, password, directory, url): | 94 | def upload_file_sftp(f, hostname, port, username, password, directory, url): |
353 | 95 | if not 'SSH' in PROTO_LIST: | ||
354 | 96 | errormsg = 'Error: SSH support not installed. Install \'paramiko\' package.' | ||
355 | 97 | print errormsg | ||
356 | 98 | return False, errormsg | ||
357 | 91 | t = paramiko.Transport((hostname, port)) | 99 | t = paramiko.Transport((hostname, port)) |
358 | 92 | t.connect(username=username, password=password) | 100 | t.connect(username=username, password=password) |
359 | 93 | sftp = paramiko.SFTPClient.from_transport(t) | 101 | sftp = paramiko.SFTPClient.from_transport(t) |
360 | @@ -102,10 +110,16 @@ | |||
361 | 102 | 110 | ||
362 | 103 | def upload_file_imgur(f): | 111 | def upload_file_imgur(f): |
363 | 104 | if not 'Imgur' in PROTO_LIST: | 112 | if not 'Imgur' in PROTO_LIST: |
365 | 105 | print 'Error: Imgur not supported' | 113 | errormsg = 'Error: Imgur support not installed. Install \'pycurl\' package.' |
366 | 114 | print errormsg | ||
367 | 115 | return False, errormsg | ||
368 | 106 | i = ImgurUploader() | 116 | i = ImgurUploader() |
369 | 107 | i.upload(f) | 117 | i.upload(f) |
371 | 108 | return True, i.mapping | 118 | print i.mapping |
372 | 119 | if not 'error_msg' in i.mapping: | ||
373 | 120 | return True, i.mapping | ||
374 | 121 | else: | ||
375 | 122 | return False, i.mapping.get('error_msg') | ||
376 | 109 | 123 | ||
377 | 110 | def upload_file_ubuntuone(f): | 124 | def upload_file_ubuntuone(f): |
378 | 111 | pass | 125 | pass |