Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# This file is part of Xpra. 

2# Copyright (C) 2011 Serviware (Arthur Huillet, <ahuillet@serviware.com>) 

3# Copyright (C) 2010-2020 Antoine Martin <antoine@xpra.org> 

4# Copyright (C) 2008, 2010 Nathaniel Smith <njs@pobox.com> 

5# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 

6# later version. See the file COPYING for details. 

7 

8import math 

9import os.path 

10from urllib.parse import unquote 

11from cairo import OPERATOR_OVER, LINE_CAP_ROUND #pylint: disable=no-name-in-module 

12from gi.repository import Gtk, Gdk, Gio 

13 

14from xpra.os_util import bytestostr, strtobytes, is_X11, monotonic_time, WIN32, OSX, POSIX 

15from xpra.util import ( 

16 AdHocStruct, typedict, envint, envbool, csv, first_time, 

17 WORKSPACE_UNSET, WORKSPACE_ALL, WORKSPACE_NAMES, MOVERESIZE_DIRECTION_STRING, SOURCE_INDICATION_STRING, 

18 MOVERESIZE_CANCEL, 

19 MOVERESIZE_SIZE_TOPLEFT, MOVERESIZE_SIZE_TOP, MOVERESIZE_SIZE_TOPRIGHT, 

20 MOVERESIZE_SIZE_RIGHT, 

21 MOVERESIZE_SIZE_BOTTOMRIGHT, MOVERESIZE_SIZE_BOTTOM, MOVERESIZE_SIZE_BOTTOMLEFT, 

22 MOVERESIZE_SIZE_LEFT, MOVERESIZE_MOVE, MOVERESIZE_MOVE_KEYBOARD, 

23 ) 

24from xpra.gtk_common.gobject_util import no_arg_signal, one_arg_signal 

25from xpra.gtk_common.gtk_util import ( 

26 get_pixbuf_from_data, get_default_root_window, 

27 set_visual, 

28 BUTTON_MASK, 

29 GRAB_STATUS_STRING, 

30 WINDOW_EVENT_MASK, 

31 ) 

32from xpra.gtk_common.keymap import KEY_TRANSLATIONS 

33from xpra.client.client_window_base import ClientWindowBase 

34from xpra.platform.gui import set_fullscreen_monitors, set_shaded 

35from xpra.platform.gui import add_window_hooks, remove_window_hooks 

36from xpra.log import Logger 

37 

38focuslog = Logger("focus", "grab") 

39workspacelog = Logger("workspace") 

40log = Logger("window") 

41keylog = Logger("keyboard") 

42keyeventlog = Logger("keyboard", "events") 

43iconlog = Logger("icon") 

44metalog = Logger("metadata") 

45statelog = Logger("state") 

46eventslog = Logger("events") 

47shapelog = Logger("shape") 

48mouselog = Logger("mouse") 

49geomlog = Logger("geometry") 

50grablog = Logger("grab") 

51draglog = Logger("dragndrop") 

52alphalog = Logger("alpha") 

53 

54CAN_SET_WORKSPACE = False 

55HAS_X11_BINDINGS = False 

56USE_X11_BINDINGS = POSIX and envbool("XPRA_USE_X11_BINDINGS", is_X11()) 

57prop_get, prop_set, prop_del = None, None, None 

58NotifyInferior = None 

59if USE_X11_BINDINGS: 

60 try: 

61 from xpra.gtk_common.error import xlog, verify_sync 

62 from xpra.x11.gtk_x11.prop import prop_get, prop_set, prop_del 

63 from xpra.x11.bindings.window_bindings import constants, X11WindowBindings, SHAPE_KIND #@UnresolvedImport 

64 from xpra.x11.bindings.core_bindings import X11CoreBindings, set_context_check 

65 from xpra.x11.gtk_x11.send_wm import send_wm_workspace 

66 

67 set_context_check(verify_sync) 

68 X11Window = X11WindowBindings() 

69 X11Core = X11CoreBindings() 

70 NotifyInferior = constants["NotifyInferior"] 

71 HAS_X11_BINDINGS = True 

72 

73 SubstructureNotifyMask = constants["SubstructureNotifyMask"] 

74 SubstructureRedirectMask = constants["SubstructureRedirectMask"] 

75 

76 def can_set_workspace(): 

77 SET_WORKSPACE = envbool("XPRA_SET_WORKSPACE", True) 

78 if not SET_WORKSPACE: 

79 return False 

80 try: 

81 #TODO: in theory this is not a proper check, meh - that will do 

82 root = get_default_root_window() 

83 supported = prop_get(root, "_NET_SUPPORTED", ["atom"], ignore_errors=True) 

84 return bool(supported) and "_NET_WM_DESKTOP" in supported 

85 except Exception as e: 

86 workspacelog("x11 workspace bindings error", exc_info=True) 

87 workspacelog.error("Error: failed to setup workspace hooks:") 

88 workspacelog.error(" %s", e) 

89 CAN_SET_WORKSPACE = can_set_workspace() 

90 except ImportError as e: 

91 log("x11 bindings", exc_info=True) 

92 log.error("Error: cannot import X11 bindings:") 

93 log.error(" %s", e) 

94 

95 

96AWT_DIALOG_WORKAROUND = envbool("XPRA_AWT_DIALOG_WORKAROUND", WIN32) 

97BREAK_MOVERESIZE = os.environ.get("XPRA_BREAK_MOVERESIZE", "Escape").split(",") 

98MOVERESIZE_X11 = envbool("XPRA_MOVERESIZE_X11", POSIX) 

99MOVERESIZE_GDK = envbool("XPRA_MOVERESIZE_GDK", True) 

100CURSOR_IDLE_TIMEOUT = envint("XPRA_CURSOR_IDLE_TIMEOUT", 6) 

101DISPLAY_HAS_SCREEN_INDEX = POSIX and os.environ.get("DISPLAY", "").split(":")[-1].find(".")>=0 

102DRAGNDROP = envbool("XPRA_DRAGNDROP", True) 

103CLAMP_WINDOW_TO_SCREEN = envbool("XPRA_CLAMP_WINDOW_TO_SCREEN", True) 

104FOCUS_RECHECK_DELAY = envint("XPRA_FOCUS_RECHECK_DELAY", 0) 

105REPAINT_MAXIMIZED = envint("XPRA_REPAINT_MAXIMIZED", 0) 

106REFRESH_MAXIMIZED = envbool("XPRA_REFRESH_MAXIMIZED", True) 

107 

108WINDOW_OVERFLOW_TOP = envbool("XPRA_WINDOW_OVERFLOW_TOP", False) 

109AWT_RECENTER = envbool("XPRA_AWT_RECENTER", True) 

110UNDECORATED_TRANSIENT_IS_OR = envint("XPRA_UNDECORATED_TRANSIENT_IS_OR", 1) 

111XSHAPE = envbool("XPRA_XSHAPE", True) 

112LAZY_SHAPE = envbool("XPRA_LAZY_SHAPE", True) 

113def parse_padding_colors(colors_str): 

114 padding_colors = 0, 0, 0 

115 if colors_str: 

116 try: 

117 padding_colors = tuple(float(x.strip()) for x in colors_str.split(",")) 

118 assert len(padding_colors)==3, "you must specify 3 components" 

119 except Exception as e: 

120 log.warn("Warning: invalid padding colors specified,") 

121 log.warn(" %s", e) 

122 log.warn(" using black") 

123 padding_colors = 0, 0, 0 

124 log("parse_padding_colors(%s)=%s", colors_str, padding_colors) 

125 return padding_colors 

126PADDING_COLORS = parse_padding_colors(os.environ.get("XPRA_PADDING_COLORS")) 

127 

128#window types we map to POPUP rather than TOPLEVEL 

129POPUP_TYPE_HINTS = set(( 

130 #"DIALOG", 

131 #"MENU", 

132 #"TOOLBAR", 

133 #"SPLASH", 

134 #"UTILITY", 

135 #"DOCK", 

136 #"DESKTOP", 

137 "DROPDOWN_MENU", 

138 "POPUP_MENU", 

139 #"TOOLTIP", 

140 #"NOTIFICATION", 

141 #"COMBO", 

142 #"DND" 

143 )) 

144#window types for which we skip window decorations (title bar) 

145UNDECORATED_TYPE_HINTS = set(( 

146 #"DIALOG", 

147 "MENU", 

148 #"TOOLBAR", 

149 "SPLASH", 

150 "SPLASHSCREEN", 

151 "UTILITY", 

152 "DOCK", 

153 "DESKTOP", 

154 "DROPDOWN_MENU", 

155 "POPUP_MENU", 

156 "TOOLTIP", 

157 "NOTIFICATION", 

158 "COMBO", 

159 "DND")) 

160 

161GDK_SCROLL_MAP = { 

162 Gdk.ScrollDirection.UP : 4, 

163 Gdk.ScrollDirection.DOWN : 5, 

164 Gdk.ScrollDirection.LEFT : 6, 

165 Gdk.ScrollDirection.RIGHT : 7, 

166 } 

167 

168OR_TYPE_HINTS = ( 

169 Gdk.WindowTypeHint.DIALOG, 

170 Gdk.WindowTypeHint.MENU, 

171 Gdk.WindowTypeHint.TOOLBAR, 

172 #Gdk.WindowTypeHint.SPLASHSCREEN, 

173 #Gdk.WindowTypeHint.UTILITY, 

174 #Gdk.WindowTypeHint.DOCK, 

175 #Gdk.WindowTypeHint.DESKTOP, 

176 Gdk.WindowTypeHint.DROPDOWN_MENU, 

177 Gdk.WindowTypeHint.POPUP_MENU, 

178 Gdk.WindowTypeHint.TOOLTIP, 

179 #Gdk.WindowTypeHint.NOTIFICATION, 

180 Gdk.WindowTypeHint.COMBO, 

181 Gdk.WindowTypeHint.DND, 

182 ) 

183 

184WINDOW_NAME_TO_HINT = { 

185 "NORMAL" : Gdk.WindowTypeHint.NORMAL, 

186 "DIALOG" : Gdk.WindowTypeHint.DIALOG, 

187 "MENU" : Gdk.WindowTypeHint.MENU, 

188 "TOOLBAR" : Gdk.WindowTypeHint.TOOLBAR, 

189 "SPLASH" : Gdk.WindowTypeHint.SPLASHSCREEN, 

190 "UTILITY" : Gdk.WindowTypeHint.UTILITY, 

191 "DOCK" : Gdk.WindowTypeHint.DOCK, 

192 "DESKTOP" : Gdk.WindowTypeHint.DESKTOP, 

193 "DROPDOWN_MENU" : Gdk.WindowTypeHint.DROPDOWN_MENU, 

194 "POPUP_MENU" : Gdk.WindowTypeHint.POPUP_MENU, 

195 "TOOLTIP" : Gdk.WindowTypeHint.TOOLTIP, 

196 "NOTIFICATION" : Gdk.WindowTypeHint.NOTIFICATION, 

197 "COMBO" : Gdk.WindowTypeHint.COMBO, 

198 "DND" : Gdk.WindowTypeHint.DND 

199 } 

200 

201 

202def wn(w): 

203 return WORKSPACE_NAMES.get(w, w) 

204 

205 

206class GTKKeyEvent(AdHocStruct): 

207 pass 

208 

209 

210class GTKClientWindowBase(ClientWindowBase, Gtk.Window): 

211 

212 __common_gsignals__ = { 

213 "state-updated" : no_arg_signal, 

214 "xpra-focus-out-event" : one_arg_signal, 

215 "xpra-focus-in-event" : one_arg_signal, 

216 } 

217 

218 #maximum size of the actual window: 

219 MAX_VIEWPORT_DIMS = 16*1024, 16*1024 

220 #maximum size of the backing pixel buffer: 

221 MAX_BACKING_DIMS = 16*1024, 16*1024 

222 

223 def init_window(self, metadata): 

224 self.init_max_window_size() 

225 if self._is_popup(metadata): 

226 window_type = Gtk.WindowType.POPUP 

227 else: 

228 window_type = Gtk.WindowType.TOPLEVEL 

229 self.on_realize_cb = {} 

230 Gtk.Window.__init__(self, type = window_type) 

231 self.set_app_paintable(True) 

232 self.init_drawing_area() 

233 self.set_decorated(self._is_decorated(metadata)) 

234 self._window_state = {} 

235 self._resize_counter = 0 

236 self._can_set_workspace = HAS_X11_BINDINGS and CAN_SET_WORKSPACE 

237 self._current_frame_extents = None 

238 self._screen = -1 

239 self._frozen = False 

240 self.window_state_timer = None 

241 self.send_iconify_timer = None 

242 self.remove_pointer_overlay_timer = None 

243 self.show_pointer_overlay_timer = None 

244 self.moveresize_timer = None 

245 self.moveresize_event = None 

246 #add platform hooks 

247 self.connect_after("realize", self.on_realize) 

248 self.connect('unrealize', self.on_unrealize) 

249 self.add_events(WINDOW_EVENT_MASK) 

250 if DRAGNDROP and not self._client.readonly: 

251 self.init_dragndrop() 

252 self.init_focus() 

253 ClientWindowBase.init_window(self, metadata) 

254 

255 def init_drawing_area(self): 

256 widget = Gtk.DrawingArea() 

257 widget.set_app_paintable(True) 

258 widget.set_size_request(*self._size) 

259 widget.show() 

260 self.drawing_area = widget 

261 self.init_widget_events(widget) 

262 self.add(widget) 

263 

264 def repaint(self, x, y, w, h): 

265 #self.queue_draw_area(0, 0, *self._size) 

266 widget = self.drawing_area 

267 if widget: 

268 widget.queue_draw_area(x, y, w, h) 

269 

270 

271 def init_widget_events(self, widget): 

272 widget.add_events(WINDOW_EVENT_MASK) 

273 def motion(_w, event): 

274 self._do_motion_notify_event(event) 

275 return True 

276 widget.connect("motion-notify-event", motion) 

277 def press(_w, event): 

278 self._do_button_press_event(event) 

279 return True 

280 widget.connect("button-press-event", press) 

281 def release(_w, event): 

282 self._do_button_release_event(event) 

283 return True 

284 widget.connect("button-release-event", release) 

285 def scroll(_w, event): 

286 self._do_scroll_event(event) 

287 return True 

288 widget.connect("scroll-event", scroll) 

289 widget.connect("draw", self.draw_widget) 

290 

291 def draw_widget(self, widget, context): 

292 raise NotImplementedError() 

293 

294 def get_drawing_area_geometry(self): 

295 raise NotImplementedError() 

296 

297 

298 ###################################################################### 

299 # drag and drop: 

300 def init_dragndrop(self): 

301 targets = [ 

302 Gtk.TargetEntry.new("text/uri-list", 0, 80), 

303 ] 

304 flags = Gtk.DestDefaults.MOTION | Gtk.DestDefaults.HIGHLIGHT 

305 actions = Gdk.DragAction.COPY # | Gdk.ACTION_LINK 

306 self.drag_dest_set(flags, targets, actions) 

307 self.connect('drag_drop', self.drag_drop_cb) 

308 self.connect('drag_motion', self.drag_motion_cb) 

309 self.connect('drag_data_received', self.drag_got_data_cb) 

310 

311 def drag_drop_cb(self, widget, context, x, y, time): 

312 targets = list(x.name() for x in context.list_targets()) 

313 draglog("drag_drop_cb%s targets=%s", (widget, context, x, y, time), targets) 

314 if not targets: 

315 #this happens on macos, but we can still get the data.. 

316 draglog("Warning: no targets provided, continuing anyway") 

317 elif "text/uri-list" not in targets: 

318 draglog("Warning: cannot handle targets:") 

319 draglog(" %s", csv(targets)) 

320 return 

321 atom = Gdk.Atom.intern("text/uri-list", False) 

322 widget.drag_get_data(context, atom, time) 

323 

324 def drag_motion_cb(self, wid, context, x, y, time): 

325 draglog("drag_motion_cb%s", (wid, context, x, y, time)) 

326 Gdk.drag_status(context, Gdk.DragAction.COPY, time) 

327 return True #accept this data 

328 

329 def drag_got_data_cb(self, wid, context, x, y, selection, info, time): 

330 draglog("drag_got_data_cb%s", (wid, context, x, y, selection, info, time)) 

331 #draglog("%s: %s", type(selection), dir(selection)) 

332 #draglog("%s: %s", type(context), dir(context)) 

333 targets = list(x.name() for x in context.list_targets()) 

334 actions = context.get_actions() 

335 def xid(w): 

336 #TODO: use a generic window handle function 

337 #this only used for debugging for now 

338 if w and POSIX: 

339 return w.get_xid() 

340 return 0 

341 dest_window = xid(context.get_dest_window()) 

342 source_window = xid(context.get_source_window()) 

343 suggested_action = context.get_suggested_action() 

344 draglog("drag_got_data_cb context: source_window=%#x, dest_window=%#x", 

345 source_window, dest_window) 

346 draglog("drag_got_data_cb context: suggested_action=%s, actions=%s, targets=%s", 

347 suggested_action, actions, targets) 

348 dtype = selection.get_data_type() 

349 fmt = selection.get_format() 

350 l = selection.get_length() 

351 target = selection.get_target() 

352 text = selection.get_text() 

353 uris = selection.get_uris() 

354 draglog("drag_got_data_cb selection: data type=%s, format=%s, length=%s, target=%s, text=%s, uris=%s", 

355 dtype, fmt, l, target, text, uris) 

356 if not uris: 

357 return 

358 filelist = [] 

359 for uri in uris: 

360 if not uri: 

361 continue 

362 if not uri.startswith("file://"): 

363 draglog.warn("Warning: cannot handle drag-n-drop URI '%s'", uri) 

364 continue 

365 filename = unquote(uri[len("file://"):].rstrip("\n\r")) 

366 if WIN32: 

367 filename = filename.lstrip("/") 

368 abspath = os.path.abspath(filename) 

369 if not os.path.isfile(abspath): 

370 draglog.warn("Warning: '%s' is not a file", abspath) 

371 continue 

372 filelist.append(abspath) 

373 draglog("drag_got_data_cb: will try to upload: %s", csv(filelist)) 

374 pending = set(filelist) 

375 #when all the files have been loaded / failed, 

376 #finish the drag and drop context so the source knows we're done with them: 

377 def file_done(filename): 

378 if not pending: 

379 return 

380 try: 

381 pending.remove(filename) 

382 except KeyError: 

383 pass 

384 if not pending: 

385 context.finish(True, False, time) 

386 for filename in filelist: 

387 def got_file_info(gfile, result, arg=None): 

388 draglog("got_file_info(%s, %s, %s)", gfile, result, arg) 

389 file_info = gfile.query_info_finish(result) 

390 basename = gfile.get_basename() 

391 ctype = file_info.get_content_type() 

392 size = file_info.get_size() 

393 draglog("file_info(%s)=%s ctype=%s, size=%s", filename, file_info, ctype, size) 

394 def got_file_data(gfile, result, user_data=None): 

395 _, data, entity = gfile.load_contents_finish(result) 

396 filesize = len(data) 

397 draglog("got_file_data(%s, %s, %s) entity=%s", gfile, result, user_data, entity) 

398 file_done(filename) 

399 openit = self._client.remote_open_files 

400 draglog.info("sending file %s (%i bytes)", basename, filesize) 

401 self._client.send_file(filename, "", data, filesize=filesize, openit=openit) 

402 cancellable = None 

403 user_data = (filename, True) 

404 gfile.load_contents_async(cancellable, got_file_data, user_data) 

405 try: 

406 gfile = Gio.File.new_for_path(filename) 

407 #basename = gf.get_basename() 

408 FILE_QUERY_INFO_NONE = 0 

409 G_PRIORITY_DEFAULT = 0 

410 cancellable = None 

411 gfile.query_info_async("standard::*", FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, cancellable, got_file_info, None) 

412 except Exception as e: 

413 draglog("file upload for %s:", filename, exc_info=True) 

414 draglog.error("Error: cannot upload '%s':", filename) 

415 draglog.error(" %s", e) 

416 del e 

417 file_done(filename) 

418 

419 ###################################################################### 

420 # focus: 

421 def init_focus(self): 

422 self.recheck_focus_timer = 0 

423 self.when_realized("init-focus", self.do_init_focus) 

424 

425 def do_init_focus(self): 

426 #hook up the X11 gdk event notifications so we can get focus-out when grabs are active: 

427 if POSIX and not OSX: 

428 try: 

429 from xpra.x11.gtk_x11.gdk_bindings import add_event_receiver 

430 except ImportError as e: 

431 log("do_init_focus()", exc_info=True) 

432 log.warn("Warning: missing gdk bindings:") 

433 log.warn(" %s", e) 

434 else: 

435 self._focus_latest = None 

436 grablog("adding event receiver so we can get FocusIn and FocusOut events whilst grabbing the keyboard") 

437 add_event_receiver(self.get_window(), self) 

438 #other platforms should bet getting regular focus events instead: 

439 def focus_in(_window, event): 

440 focuslog("focus-in-event for wid=%s", self._id) 

441 self.do_xpra_focus_in_event(event) 

442 def focus_out(_window, event): 

443 focuslog("focus-out-event for wid=%s", self._id) 

444 self.do_xpra_focus_out_event(event) 

445 self.connect("focus-in-event", focus_in) 

446 self.connect("focus-out-event", focus_out) 

447 if not self._override_redirect: 

448 self.connect("notify::has-toplevel-focus", self._focus_change) 

449 

450 def _focus_change(self, *args): 

451 assert not self._override_redirect 

452 htf = self.has_toplevel_focus() 

453 focuslog("%s focus_change%s has-toplevel-focus=%s, _been_mapped=%s", self, args, htf, self._been_mapped) 

454 if self._been_mapped: 

455 self._client.update_focus(self._id, htf) 

456 

457 def recheck_focus(self): 

458 self.recheck_focus_timer = 0 

459 #we receive pairs of FocusOut + FocusIn following a keyboard grab, 

460 #so we recheck the focus status via this timer to skip unnecessary churn 

461 focused = self._client._focused 

462 focuslog("recheck_focus() wid=%i, focused=%s, latest=%s", self._id, focused, self._focus_latest) 

463 hasfocus = focused==self._id 

464 if hasfocus==self._focus_latest: 

465 #we're already up to date 

466 return 

467 if not self._focus_latest: 

468 self._client.window_ungrab() 

469 self._client.update_focus(self._id, False) 

470 else: 

471 self._client.update_focus(self._id, True) 

472 

473 def cancel_focus_timer(self): 

474 rft = self.recheck_focus_timer 

475 if rft: 

476 self.recheck_focus_timer = 0 

477 self.source_remove(rft) 

478 

479 def schedule_recheck_focus(self): 

480 if FOCUS_RECHECK_DELAY<0: 

481 self.recheck_focus() 

482 return 

483 if self.recheck_focus_timer==0: 

484 self.recheck_focus_timer = self.timeout_add(FOCUS_RECHECK_DELAY, self.recheck_focus) 

485 return True 

486 

487 def do_xpra_focus_out_event(self, event): 

488 focuslog("do_xpra_focus_out_event(%s)", event) 

489 if NotifyInferior is not None: 

490 detail = getattr(event, "detail", None) 

491 if detail==NotifyInferior: 

492 focuslog("dropped NotifyInferior focus event") 

493 return True 

494 self._focus_latest = False 

495 return self.schedule_recheck_focus() 

496 

497 def do_xpra_focus_in_event(self, event): 

498 focuslog("do_xpra_focus_in_event(%s) been_mapped=%s", event, self._been_mapped) 

499 if self._been_mapped: 

500 self._focus_latest = True 

501 return self.schedule_recheck_focus() 

502 

503 

504 def init_max_window_size(self): 

505 """ used by GL windows to enforce a hard limit on window sizes """ 

506 saved_mws = self.max_window_size 

507 def clamp_to(maxw, maxh): 

508 #don't bother if the new limit is greater than 16k: 

509 if maxw>=16*1024 and maxh>=16*1024: 

510 return 

511 #only take into account the current max-window-size if non zero: 

512 mww, mwh = self.max_window_size 

513 if mww>0: 

514 maxw = min(mww, maxw) 

515 if mwh>0: 

516 maxh = min(mwh, maxh) 

517 self.max_window_size = maxw, maxh 

518 #viewport is easy, measured in window pixels: 

519 clamp_to(*self.MAX_VIEWPORT_DIMS) 

520 #backing dimensions are harder, 

521 #we have to take scaling into account (if any): 

522 clamp_to(*self._client.sp(*self.MAX_BACKING_DIMS)) 

523 if self.max_window_size!=saved_mws: 

524 log("init_max_window_size(..) max-window-size changed from %s to %s", 

525 saved_mws, self.max_window_size) 

526 log(" because of max viewport dims %s and max backing dims %s", 

527 self.MAX_VIEWPORT_DIMS, self.MAX_BACKING_DIMS) 

528 

529 

530 def is_awt(self, metadata) -> bool: 

531 wm_class = metadata.get("class-instance") 

532 return wm_class and len(wm_class)==2 and wm_class[0].startswith("sun-awt-X11") 

533 

534 def _is_popup(self, metadata) -> bool: 

535 #decide if the window type is POPUP or NORMAL 

536 if self._override_redirect: 

537 return True 

538 if UNDECORATED_TRANSIENT_IS_OR>0: 

539 transient_for = metadata.get("transient-for", -1) 

540 decorations = metadata.get("decorations", 0) 

541 if transient_for>0 and decorations<=0: 

542 if UNDECORATED_TRANSIENT_IS_OR>1: 

543 metalog("forcing POPUP type for window transient-for=%s", transient_for) 

544 return True 

545 if metadata.get("skip-taskbar") and self.is_awt(metadata): 

546 metalog("forcing POPUP type for Java AWT skip-taskbar window, transient-for=%s", transient_for) 

547 return True 

548 window_types = metadata.strtupleget("window-type") 

549 popup_types = tuple(POPUP_TYPE_HINTS.intersection(window_types)) 

550 metalog("popup_types(%s)=%s", window_types, popup_types) 

551 if popup_types: 

552 metalog("forcing POPUP window type for %s", popup_types) 

553 return True 

554 return False 

555 

556 def _is_decorated(self, metadata) -> bool: 

557 #decide if the window type is POPUP or NORMAL 

558 #(show window decorations or not) 

559 if self._override_redirect: 

560 return False 

561 return metadata.boolget("decorations", True) 

562 

563 def set_decorated(self, decorated : bool): 

564 was_decorated = self.get_decorated() 

565 if self._fullscreen and was_decorated and not decorated: 

566 #fullscreen windows aren't decorated anyway! 

567 #calling set_decorated(False) would cause it to get unmapped! (why?) 

568 pass 

569 else: 

570 Gtk.Window.set_decorated(self, decorated) 

571 if WIN32: 

572 #workaround for new window offsets: 

573 #keep the window contents where they were and adjust the frame 

574 #this generates a configure event which ensures the server has the correct window position 

575 wfs = self._client.get_window_frame_sizes() 

576 if wfs and decorated and not was_decorated: 

577 geomlog("set_decorated(%s) re-adjusting window location using %s", decorated, wfs) 

578 normal = wfs.get("normal") 

579 fixed = wfs.get("fixed") 

580 if normal and fixed: 

581 nx, ny = normal 

582 fx, fy = fixed 

583 x, y = self.get_position() 

584 Gtk.Window.move(self, max(0, x-nx+fx), max(0, y-ny+fy)) 

585 

586 

587 def setup_window(self, *args): 

588 log("setup_window%s", args) 

589 self.set_alpha() 

590 

591 if self._override_redirect: 

592 transient_for = self.get_transient_for() 

593 type_hint = self.get_type_hint() 

594 if transient_for is not None and type_hint in self.OR_TYPE_HINTS: 

595 transient_for._override_redirect_windows.append(self) 

596 

597 self.connect("property-notify-event", self.property_changed) 

598 self.connect("window-state-event", self.window_state_updated) 

599 

600 #this will create the backing: 

601 ClientWindowBase.setup_window(self, *args) 

602 

603 #try to honour the initial position 

604 geomlog("setup_window() position=%s, set_initial_position=%s, OR=%s, decorated=%s", 

605 self._pos, self._set_initial_position, self.is_OR(), self.get_decorated()) 

606 if self._pos!=(0, 0) or self._set_initial_position or self.is_OR(): 

607 x, y = self.adjusted_position(*self._pos) 

608 if self.is_OR(): 

609 #make sure OR windows are mapped on screen 

610 if self._client._current_screen_sizes: 

611 w, h = self._size 

612 self.window_offset = self.calculate_window_offset(x, y, w, h) 

613 geomlog("OR offsets=%s", self.window_offset) 

614 if self.window_offset: 

615 x += self.window_offset[0] 

616 y += self.window_offset[1] 

617 elif self.get_decorated(): 

618 #try to adjust for window frame size if we can figure it out: 

619 #Note: we cannot just call self.get_window_frame_size() here because 

620 #the window is not realized yet, and it may take a while for the window manager 

621 #to set the frame-extents property anyway 

622 wfs = self._client.get_window_frame_sizes() 

623 dx, dy = 0, 0 

624 if wfs: 

625 geomlog("setup_window() window frame sizes=%s", wfs) 

626 v = wfs.get("offset") 

627 if v: 

628 dx, dy = v 

629 x = max(0, x-dx) 

630 y = max(0, y-dy) 

631 self._pos = x, y 

632 geomlog("setup_window() adjusted initial position=%s", self._pos) 

633 self.move(x, y) 

634 self.set_default_size(*self._size) 

635 

636 def new_backing(self, bw, bh): 

637 b = ClientWindowBase.new_backing(self, bw, bh) 

638 #call via idle_add so that the backing has time to be realized too: 

639 self.when_realized("cursor", self.idle_add, self._backing.set_cursor_data, self.cursor_data) 

640 return b 

641 

642 def set_cursor_data(self, cursor_data): 

643 self.cursor_data = cursor_data 

644 b = self._backing 

645 if b: 

646 self.when_realized("cursor", b.set_cursor_data, cursor_data) 

647 

648 def adjusted_position(self, ox, oy): 

649 if AWT_RECENTER and self.is_awt(self._metadata): 

650 ss = self._client._current_screen_sizes 

651 if ss and len(ss)==1: 

652 screen0 = ss[0] 

653 monitors = screen0[5] 

654 if monitors and len(monitors)>1: 

655 monitor = monitors[0] 

656 mw = monitor[3] 

657 mh = monitor[4] 

658 w, h = self._size 

659 #adjust for window centering on monitor instead of screen java 

660 screen = self.get_screen() 

661 sw = screen.get_width() 

662 sh = screen.get_height() 

663 #re-center on first monitor if the window is within 

664 #$tolerance of the center of the screen: 

665 tolerance = 10 

666 #center of the window: 

667 cx = ox + w//2 

668 cy = oy + h//2 

669 if abs(sw//2 - cx) <= tolerance: 

670 x = mw//2 - w//2 

671 else: 

672 x = ox 

673 if abs(sh//2 - cy) <= tolerance: 

674 y = mh//2 - h//2 

675 else: 

676 y = oy 

677 geomlog("adjusted_position(%i, %i)=%i, %i", ox, oy, x, y) 

678 return x, y 

679 return ox, oy 

680 

681 

682 def calculate_window_offset(self, wx, wy, ww, wh): 

683 ss = self._client._current_screen_sizes 

684 if not ss: 

685 return None 

686 if len(ss)!=1: 

687 geomlog("cannot handle more than one screen for OR offset") 

688 return None 

689 screen0 = ss[0] 

690 monitors = screen0[5] 

691 if not monitors: 

692 geomlog("screen %s lacks monitors information: %s", screen0) 

693 return None 

694 from xpra.rectangle import rectangle #@UnresolvedImport 

695 wrect = rectangle(wx, wy, ww, wh) 

696 rects = [wrect] 

697 pixels_in_monitor = {} 

698 for i, monitor in enumerate(monitors): 

699 plug_name, x, y, w, h = monitor[:5] 

700 new_rects = [] 

701 for rect in rects: 

702 new_rects += rect.substract(x, y, w, h) 

703 geomlog("after removing areas visible on %s from %s: %s", plug_name, rects, new_rects) 

704 rects = new_rects 

705 if not rects: 

706 #the whole window is visible 

707 return None 

708 #keep track of how many pixels would be on this monitor: 

709 inter = wrect.intersection(x, y, w, h) 

710 if inter: 

711 pixels_in_monitor[inter.width*inter.height] = i 

712 #if we're here, then some of the window would land on an area 

713 #not show on any monitors 

714 #choose the monitor that had most of the pixels and make it fit: 

715 geomlog("pixels in monitor=%s", pixels_in_monitor) 

716 if not pixels_in_monitor: 

717 i = 0 

718 else: 

719 best = max(pixels_in_monitor.keys()) 

720 i = pixels_in_monitor[best] 

721 monitor = monitors[i] 

722 plug_name, x, y, w, h = monitor[:5] 

723 geomlog("calculating OR offset for monitor %i: %s", i, plug_name) 

724 if ww>w or wh>=h: 

725 geomlog("window %ix%i is bigger than the monitor %i: %s %ix%i, not adjusting it", 

726 ww, wh, i, plug_name, w, h) 

727 return None 

728 dx = 0 

729 dy = 0 

730 if wx<x: 

731 dx = x-wx 

732 elif wx+ww>x+w: 

733 dx = (x+w) - (wx+ww) 

734 if wy<y: 

735 dy = y-wy 

736 elif wy+wh>y+h: 

737 dy = (y+h) - (wy+wh) 

738 assert dx!=0 or dy!=0 

739 geomlog("calculate_window_offset%s=%s", (wx, wy, ww, wh), (dx, dy)) 

740 return dx, dy 

741 

742 def when_realized(self, identifier, callback, *args): 

743 if self.get_realized(): 

744 callback(*args) 

745 else: 

746 self.on_realize_cb[identifier] = callback, args 

747 

748 def on_realize(self, widget): 

749 eventslog("on_realize(%s) gdk window=%s", widget, self.get_window()) 

750 add_window_hooks(self) 

751 cb = self.on_realize_cb 

752 self.on_realize_cb = {} 

753 for x, args in cb.values(): 

754 try: 

755 x(*args) 

756 except Exception: 

757 log.error("Error on realize callback %s for window %i", x, self._id, exc_info=True) 

758 if HAS_X11_BINDINGS: 

759 #request frame extents if the window manager supports it 

760 self._client.request_frame_extents(self) 

761 if self.watcher_pid: 

762 log("using watcher pid=%i for wid=%i", self.watcher_pid, self._id) 

763 prop_set(self.get_window(), "_NET_WM_PID", "u32", self.watcher_pid) 

764 if self.group_leader: 

765 self.get_window().set_group(self.group_leader) 

766 

767 def on_unrealize(self, widget): 

768 eventslog("on_unrealize(%s)", widget) 

769 remove_window_hooks(self) 

770 

771 

772 def set_alpha(self): 

773 #try to enable alpha on this window if needed, 

774 #and if the backing class can support it: 

775 bc = self.get_backing_class() 

776 alphalog("set_alpha() has_alpha=%s, %s.HAS_ALPHA=%s, realized=%s", 

777 self._has_alpha, bc, bc.HAS_ALPHA, self.get_realized()) 

778 #by default, only RGB (no transparency): 

779 #rgb_formats = tuple(BACKING_CLASS.RGB_MODES) 

780 self._client_properties["encodings.rgb_formats"] = ["RGB", "RGBX"] 

781 #only set the visual if we need to enable alpha: 

782 #(breaks the headerbar otherwise!) 

783 if not self.get_realized() and self._has_alpha: 

784 if set_visual(self, True): 

785 if self._has_alpha: 

786 self._client_properties["encodings.rgb_formats"] = ["RGBA", "RGB", "RGBX"] 

787 self._window_alpha = self._has_alpha 

788 else: 

789 alphalog("failed to set RGBA visual") 

790 self._has_alpha = False 

791 self._client_properties["encoding.transparency"] = False 

792 if not self._has_alpha or not bc.HAS_ALPHA: 

793 self._client_properties["encoding.transparency"] = False 

794 

795 

796 def freeze(self): 

797 #the OpenGL subclasses override this method to also free their GL context 

798 self._frozen = True 

799 self.iconify() 

800 

801 def unfreeze(self): 

802 if not self._frozen or not self._iconified: 

803 return 

804 log("unfreeze() wid=%i, frozen=%s, iconified=%s", self._id, self._frozen, self._iconified) 

805 if not self._frozen or not self._iconified: 

806 #has been deiconified already 

807 return 

808 self._frozen = False 

809 self.deiconify() 

810 

811 

812 def window_state_updated(self, widget, event): 

813 statelog("%s.window_state_updated(%s, %s) changed_mask=%s, new_window_state=%s", 

814 self, widget, repr(event), event.changed_mask, event.new_window_state) 

815 state_updates = {} 

816 for flag in ("fullscreen", "above", "below", "sticky", "iconified", "maximized", "focused"): 

817 wstate = getattr(Gdk.WindowState, flag.upper()) #ie: Gdk.WindowState.FULLSCREEN 

818 if event.changed_mask & wstate: 

819 state_updates[flag] = bool(event.new_window_state & wstate) 

820 self.update_window_state(state_updates) 

821 

822 def update_window_state(self, state_updates): 

823 if self._client.readonly: 

824 log("update_window_state(%s) ignored in readonly mode", state_updates) 

825 return 

826 if state_updates.get("maximized") is False or state_updates.get("fullscreen") is False: 

827 #if we unfullscreen or unmaximize, re-calculate offsets if we have any: 

828 w, h = self._backing.render_size 

829 ww, wh = self.get_size() 

830 log("update_window_state(%s) unmax or unfullscreen", state_updates) 

831 log("window_offset=%s, backing render_size=%s, window size=%s", 

832 self.window_offset, (w, h), (ww, wh)) 

833 if self._backing.offsets!=(0, 0, 0, 0): 

834 self.center_backing(w, h) 

835 self.repaint(0, 0, ww, wh) 

836 #decide if this is really an update by comparing with our local state vars: 

837 #(could just be a notification of a state change we already know about) 

838 actual_updates = {} 

839 for state,value in state_updates.items(): 

840 var = "_" + state.replace("-", "_") #ie: "skip-pager" -> "_skip_pager" 

841 cur = getattr(self, var) #ie: self._maximized 

842 if cur!=value: 

843 setattr(self, var, value) #ie: self._maximized = True 

844 actual_updates[state] = value 

845 statelog("%s=%s (was %s)", var, value, cur) 

846 server_updates = dict((k,v) for k,v in actual_updates.items() if k in self._client.server_window_states) 

847 #iconification is handled a bit differently... 

848 iconified = server_updates.pop("iconified", None) 

849 if iconified is not None: 

850 statelog("iconified=%s", iconified) 

851 #handle iconification as map events: 

852 if iconified: 

853 #usually means it is unmapped 

854 self._unfocus() 

855 if not self._override_redirect and not self.send_iconify_timer: 

856 #tell server, but wait a bit to try to prevent races: 

857 self.schedule_send_iconify() 

858 else: 

859 self.cancel_send_iconifiy_timer() 

860 self._frozen = False 

861 self.process_map_event() 

862 statelog("window_state_updated(..) state updates: %s, actual updates: %s, server updates: %s", 

863 state_updates, actual_updates, server_updates) 

864 if "maximized" in state_updates: 

865 if REPAINT_MAXIMIZED>0: 

866 def repaint_maximized(): 

867 if not self._backing: 

868 return 

869 ww, wh = self.get_size() 

870 self.repaint(0, 0, ww, wh) 

871 self.timeout_add(REPAINT_MAXIMIZED, repaint_maximized) 

872 if REFRESH_MAXIMIZED: 

873 self._client.send_refresh(self._id) 

874 

875 self._window_state.update(server_updates) 

876 self.emit("state-updated") 

877 #if we have state updates, send them back to the server using a configure window packet: 

878 if self._window_state and not self.window_state_timer: 

879 self.window_state_timer = self.timeout_add(25, self.send_updated_window_state) 

880 

881 def send_updated_window_state(self): 

882 self.window_state_timer = None 

883 if self._window_state and self.get_window(): 

884 self.send_configure_event(True) 

885 

886 def cancel_window_state_timer(self): 

887 wst = self.window_state_timer 

888 if wst: 

889 self.window_state_timer = None 

890 self.source_remove(wst) 

891 

892 

893 def schedule_send_iconify(self): 

894 #calculate a good delay to prevent races causing minimize/unminimize loops: 

895 if self._client.readonly: 

896 return 

897 delay = 150 

898 spl = tuple(self._client.server_ping_latency) 

899 if spl: 

900 worst = max(x[1] for x in self._client.server_ping_latency) 

901 delay += int(1000*worst) 

902 delay = min(1000, delay) 

903 statelog("telling server about iconification with %sms delay", delay) 

904 self.send_iconify_timer = self.timeout_add(delay, self.send_iconify) 

905 

906 def send_iconify(self): 

907 self.send_iconify_timer = None 

908 if self._iconified: 

909 self.send("unmap-window", self._id, True, self._window_state) 

910 #we have sent the window-state already: 

911 self._window_state = {} 

912 self.cancel_window_state_timer() 

913 

914 def cancel_send_iconifiy_timer(self): 

915 sit = self.send_iconify_timer 

916 if sit: 

917 self.send_iconify_timer = None 

918 self.source_remove(sit) 

919 

920 

921 def set_command(self, command): 

922 if not HAS_X11_BINDINGS: 

923 return 

924 v = command 

925 if not isinstance(command, str): 

926 try: 

927 v = v.decode("utf8") 

928 except UnicodeDecodeError: 

929 v = bytestostr(command) 

930 def do_set_command(): 

931 metalog("do_set_command() str(%s)='%r' (type=%s)", command, v, type(command)) 

932 prop_set(self.get_window(), "WM_COMMAND", "latin1", v) 

933 self.when_realized("command", do_set_command) 

934 

935 

936 def set_x11_property(self, prop_name, dtype, dformat, value): 

937 metalog("set_x11_property%s", (prop_name, dtype, dformat, value)) 

938 dtype = bytestostr(dtype) 

939 if dtype=="latin1": 

940 value = bytestostr(value) 

941 if isinstance(value, (list, tuple)): 

942 dtype = (dtype, ) 

943 def do_set_prop(): 

944 gdk_window = self.get_window() 

945 if not dtype and not dformat: 

946 #remove prop 

947 prop_del(gdk_window, prop_name) 

948 else: 

949 prop_set(gdk_window, prop_name, dtype, value) 

950 self.when_realized("x11-prop-%s" % prop_name, do_set_prop) 

951 

952 def set_class_instance(self, wmclass_name, wmclass_class): 

953 if not self.get_realized(): 

954 #Warning: window managers may ignore the icons we try to set 

955 #if the wm_class value is set and matches something somewhere undocumented 

956 #(if the default is used, you cannot override the window icon) 

957 self.set_wmclass(wmclass_name, wmclass_class) 

958 elif HAS_X11_BINDINGS: 

959 xid = self.get_window().get_xid() 

960 with xlog: 

961 X11Window.setClassHint(xid, strtobytes(wmclass_class), strtobytes(wmclass_name)) 

962 log("XSetClassHint(%s, %s) done", wmclass_class, wmclass_name) 

963 

964 def set_shape(self, shape): 

965 shapelog("set_shape(%s)", shape) 

966 if not HAS_X11_BINDINGS or not XSHAPE: 

967 return 

968 def do_set_shape(): 

969 xid = self.get_window().get_xid() 

970 x_off, y_off = shape.get("x", 0), shape.get("y", 0) 

971 for kind, name in SHAPE_KIND.items(): #@UndefinedVariable 

972 rectangles = shape.get("%s.rectangles" % name) #ie: Bounding.rectangles = [(0, 0, 150, 100)] 

973 if rectangles: 

974 #adjust for scaling: 

975 if self._client.xscale!=1 or self._client.yscale!=1: 

976 x_off, y_off = self._client.sp(x_off, y_off) 

977 rectangles = self.scale_shape_rectangles(name, rectangles) 

978 #too expensive to log with actual rectangles: 

979 shapelog("XShapeCombineRectangles(%#x, %s, %i, %i, %i rects)", 

980 xid, name, x_off, y_off, len(rectangles)) 

981 with xlog: 

982 X11Window.XShapeCombineRectangles(xid, kind, x_off, y_off, rectangles) 

983 self.when_realized("shape", do_set_shape) 

984 

985 def scale_shape_rectangles(self, kind_name, rectangles): 

986 if LAZY_SHAPE or len(rectangles)<2: 

987 #scale the rectangles without a bitmap... 

988 #results aren't so good! (but better than nothing?) 

989 srect = self._client.srect 

990 return [srect(*x) for x in rectangles] 

991 from PIL import Image, ImageDraw #@UnresolvedImport 

992 ww, wh = self._size 

993 sw, sh = self._client.cp(ww, wh) 

994 img = Image.new('1', (sw, sh), color=0) 

995 shapelog("drawing %s on bitmap(%s,%s)=%s", kind_name, sw, sh, img) 

996 d = ImageDraw.Draw(img) 

997 for x,y,w,h in rectangles: 

998 d.rectangle([x, y, x+w, y+h], fill=1) 

999 img = img.resize((ww, wh)) 

1000 shapelog("resized %s bitmap to window size %sx%s: %s", kind_name, ww, wh, img) 

1001 #now convert back to rectangles... 

1002 rectangles = [] 

1003 for y in range(wh): 

1004 #for debugging, this is very useful, but costly! 

1005 #shapelog("pixels[%3i]=%s", y, "".join([str(img.getpixel((x, y))) for x in range(ww)])) 

1006 x = 0 

1007 start = None 

1008 while x<ww: 

1009 #find first white pixel: 

1010 while x<ww and img.getpixel((x, y))==0: 

1011 x += 1 

1012 start = x 

1013 #find next black pixel: 

1014 while x<ww and img.getpixel((x, y))!=0: 

1015 x += 1 

1016 end = x 

1017 if start<end: 

1018 rectangles.append((start, y, end-start, 1)) 

1019 return rectangles 

1020 

1021 def set_bypass_compositor(self, v): 

1022 if not HAS_X11_BINDINGS: 

1023 return 

1024 if v not in (0, 1, 2): 

1025 v = 0 

1026 def do_set_bypass_compositor(): 

1027 prop_set(self.get_window(), "_NET_WM_BYPASS_COMPOSITOR", "u32", v) 

1028 self.when_realized("bypass-compositor", do_set_bypass_compositor) 

1029 

1030 

1031 def set_strut(self, strut): 

1032 if not HAS_X11_BINDINGS: 

1033 return 

1034 log("strut=%s", strut) 

1035 d = typedict(strut) 

1036 values = [] 

1037 for x in ("left", "right", "top", "bottom"): 

1038 v = d.intget(x, 0) 

1039 #handle scaling: 

1040 if x in ("left", "right"): 

1041 v = self._client.sx(v) 

1042 else: 

1043 v = self._client.sy(v) 

1044 values.append(v) 

1045 has_partial = False 

1046 for x in ("left_start_y", "left_end_y", 

1047 "right_start_y", "right_end_y", 

1048 "top_start_x", "top_end_x", 

1049 "bottom_start_x", "bottom_end_x"): 

1050 if x in d: 

1051 has_partial = True 

1052 v = d.intget(x, 0) 

1053 if x.find("_x"): 

1054 v = self._client.sx(v) 

1055 elif x.find("_y"): 

1056 v = self._client.sy(v) 

1057 values.append(v) 

1058 log("setting strut=%s, has partial=%s", values, has_partial) 

1059 def do_set_strut(): 

1060 if has_partial: 

1061 prop_set(self.get_window(), "_NET_WM_STRUT_PARTIAL", ["u32"], values) 

1062 prop_set(self.get_window(), "_NET_WM_STRUT", ["u32"], values[:4]) 

1063 self.when_realized("strut", do_set_strut) 

1064 

1065 

1066 def set_window_type(self, window_types): 

1067 pass 

1068 hints = 0 

1069 for window_type in window_types: 

1070 #win32 workaround: 

1071 if AWT_DIALOG_WORKAROUND and window_type=="DIALOG" and self._metadata.boolget("skip-taskbar"): 

1072 wm_class = self._metadata.strtupleget("class-instance", (None, None), 2, 2) 

1073 if wm_class and len(wm_class)==2 and wm_class[0] and wm_class[0].startswith("sun-awt-X11"): 

1074 #replace "DIALOG" with "NORMAL": 

1075 if "NORMAL" in window_types: 

1076 continue 

1077 window_type = "NORMAL" 

1078 hint = WINDOW_NAME_TO_HINT.get(window_type, None) 

1079 if hint is not None: 

1080 hints |= hint 

1081 else: 

1082 log("ignoring unknown window type hint: %s", window_type) 

1083 log("set_window_type(%s) hints=%s", window_types, hints) 

1084 if hints: 

1085 self.set_type_hint(hints) 

1086 

1087 def set_modal(self, modal): 

1088 #with gtk2 setting the window as modal would prevent 

1089 #all other windows we manage from receiving input 

1090 #including other unrelated applications 

1091 #what we want is "window-modal" 

1092 #so we can turn this off using the "modal_windows" feature, 

1093 #from the command line and the system tray: 

1094 mw = self._client.modal_windows 

1095 log("set_modal(%s) modal_windows=%s", modal, mw) 

1096 Gtk.Window.set_modal(self, modal and mw) 

1097 

1098 

1099 def set_fullscreen_monitors(self, fsm): 

1100 #platform specific code: 

1101 log("set_fullscreen_monitors(%s)", fsm) 

1102 def do_set_fullscreen_monitors(): 

1103 set_fullscreen_monitors(self.get_window(), fsm) 

1104 self.when_realized("fullscreen-monitors", do_set_fullscreen_monitors) 

1105 

1106 

1107 def set_shaded(self, shaded): 

1108 #platform specific code: 

1109 log("set_shaded(%s)", shaded) 

1110 def do_set_shaded(): 

1111 set_shaded(self.get_window(), shaded) 

1112 self.when_realized("shaded", do_set_shaded) 

1113 

1114 

1115 def set_fullscreen(self, fullscreen): 

1116 statelog("%s.set_fullscreen(%s)", self, fullscreen) 

1117 def do_set_fullscreen(): 

1118 if fullscreen: 

1119 #we may need to temporarily remove the max-window-size restrictions 

1120 #to be able to honour the fullscreen request: 

1121 w, h = self.max_window_size 

1122 if w>0 and h>0: 

1123 self.set_size_constraints(self.size_constraints, (0, 0)) 

1124 self.fullscreen() 

1125 else: 

1126 self.unfullscreen() 

1127 #re-apply size restrictions: 

1128 w, h = self.max_window_size 

1129 if w>0 and h>0: 

1130 self.set_size_constraints(self.size_constraints, self.max_window_size) 

1131 self.when_realized("fullscreen", do_set_fullscreen) 

1132 

1133 def set_xid(self, xid): 

1134 if not HAS_X11_BINDINGS: 

1135 return 

1136 if xid.startswith("0x") and xid.endswith("L"): 

1137 xid = xid[:-1] 

1138 try: 

1139 iid = int(xid, 16) 

1140 except Exception as e: 

1141 log("%s.set_xid(%s) error parsing/setting xid: %s", self, xid, e) 

1142 return 

1143 def do_set_xid(): 

1144 self.xset_u32_property(self.get_window(), "XID", iid) 

1145 self.when_realized("xid", do_set_xid) 

1146 

1147 def xget_u32_property(self, target, name): 

1148 if prop_get: 

1149 v = prop_get(target, name, "u32", ignore_errors=True) 

1150 log("%s.xget_u32_property(%s, %s)=%s", self, target, name, v) 

1151 if isinstance(v, int): 

1152 return v 

1153 return None 

1154 

1155 def xset_u32_property(self, target, name, value): 

1156 prop_set(target, name, "u32", value) 

1157 

1158 

1159 def property_changed(self, widget, event): 

1160 atom = str(event.atom) 

1161 statelog("property_changed(%s, %s) : %s", widget, event, atom) 

1162 if atom=="_NET_WM_DESKTOP": 

1163 if self._been_mapped and not self._override_redirect and self._can_set_workspace: 

1164 self.do_workspace_changed(event) 

1165 elif atom=="_NET_FRAME_EXTENTS": 

1166 if prop_get: 

1167 v = prop_get(self.get_window(), "_NET_FRAME_EXTENTS", ["u32"], ignore_errors=False) 

1168 statelog("_NET_FRAME_EXTENTS: %s", v) 

1169 if v: 

1170 if v==self._current_frame_extents: 

1171 #unchanged 

1172 return 

1173 if not self._been_mapped: 

1174 #map event will take care of sending it 

1175 return 

1176 if self.is_OR() or self.is_tray(): 

1177 #we can't do it: the server can't handle configure packets for OR windows! 

1178 return 

1179 if not self._client.server_window_frame_extents: 

1180 #can't send cheap "skip-geometry" packets or frame-extents feature not supported: 

1181 return 

1182 #tell server about new value: 

1183 self._current_frame_extents = v 

1184 statelog("sending configure event to update _NET_FRAME_EXTENTS to %s", v) 

1185 self._window_state["frame"] = self._client.crect(*v) 

1186 self.send_configure_event(True) 

1187 elif atom=="XKLAVIER_STATE": 

1188 if prop_get: 

1189 #unused for now, but log it: 

1190 xklavier_state = prop_get(self.get_window(), "XKLAVIER_STATE", ["integer"], ignore_errors=False) 

1191 keylog("XKLAVIER_STATE=%s", [hex(x) for x in (xklavier_state or [])]) 

1192 elif atom=="_NET_WM_STATE": 

1193 if prop_get: 

1194 wm_state_atoms = prop_get(self.get_window(), "_NET_WM_STATE", ["atom"], ignore_errors=False) 

1195 #code mostly duplicated from gtk_x11/window.py: 

1196 WM_STATE_NAME = { 

1197 "fullscreen" : ("_NET_WM_STATE_FULLSCREEN", ), 

1198 "maximized" : ("_NET_WM_STATE_MAXIMIZED_VERT", "_NET_WM_STATE_MAXIMIZED_HORZ"), 

1199 "shaded" : ("_NET_WM_STATE_SHADED", ), 

1200 "sticky" : ("_NET_WM_STATE_STICKY", ), 

1201 "skip-pager" : ("_NET_WM_STATE_SKIP_PAGER", ), 

1202 "skip-taskbar" : ("_NET_WM_STATE_SKIP_TASKBAR", ), 

1203 "above" : ("_NET_WM_STATE_ABOVE", ), 

1204 "below" : ("_NET_WM_STATE_BELOW", ), 

1205 "focused" : ("_NET_WM_STATE_FOCUSED", ), 

1206 } 

1207 state_atoms = set(wm_state_atoms or []) 

1208 state_updates = {} 

1209 for state, atoms in WM_STATE_NAME.items(): 

1210 var = "_" + state.replace("-", "_") #ie: "skip-pager" -> "_skip_pager" 

1211 cur_state = getattr(self, var) 

1212 wm_state_is_set = set(atoms).issubset(state_atoms) 

1213 if wm_state_is_set and not cur_state: 

1214 state_updates[state] = True 

1215 elif cur_state and not wm_state_is_set: 

1216 state_updates[state] = False 

1217 log("_NET_WM_STATE=%s, state_updates=%s", wm_state_atoms, state_updates) 

1218 if state_updates: 

1219 self.update_window_state(state_updates) 

1220 

1221 

1222 ###################################################################### 

1223 # workspace 

1224 def workspace_changed(self): 

1225 #on X11 clients, this fires from the root window property watcher 

1226 ClientWindowBase.workspace_changed(self) 

1227 if self._can_set_workspace: 

1228 self.do_workspace_changed("desktop workspace changed") 

1229 

1230 def do_workspace_changed(self, info): 

1231 #call this method whenever something workspace related may have changed 

1232 window_workspace = self.get_window_workspace() 

1233 desktop_workspace = self.get_desktop_workspace() 

1234 workspacelog("do_workspace_changed(%s) for window %i (window, desktop): from %s to %s", 

1235 info, self._id, 

1236 (wn(self._window_workspace), wn(self._desktop_workspace)), 

1237 (wn(window_workspace), wn(desktop_workspace))) 

1238 if self._window_workspace==window_workspace and self._desktop_workspace==desktop_workspace: 

1239 #no change 

1240 return 

1241 suspend_resume = None 

1242 if desktop_workspace<0 or window_workspace is None: 

1243 #maybe the property has been cleared? maybe the window is being scrubbed? 

1244 workspacelog("not sure if the window is shown or not: %s vs %s, resuming to be safe", 

1245 wn(desktop_workspace), wn(window_workspace)) 

1246 suspend_resume = False 

1247 elif window_workspace==WORKSPACE_UNSET: 

1248 workspacelog("workspace unset: assume current") 

1249 suspend_resume = False 

1250 elif window_workspace==WORKSPACE_ALL: 

1251 workspacelog("window is on all workspaces") 

1252 suspend_resume = False 

1253 elif desktop_workspace!=window_workspace: 

1254 workspacelog("window is on a different workspace, increasing its batch delay") 

1255 workspacelog(" desktop: %s, window: %s", wn(desktop_workspace), wn(window_workspace)) 

1256 suspend_resume = True 

1257 elif self._window_workspace!=self._desktop_workspace: 

1258 assert desktop_workspace==window_workspace 

1259 workspacelog("window was on a different workspace, resetting its batch delay") 

1260 workspacelog(" (was desktop: %s, window: %s, now both on %s)", 

1261 wn(self._window_workspace), wn(self._desktop_workspace), wn(desktop_workspace)) 

1262 suspend_resume = False 

1263 self._window_workspace = window_workspace 

1264 self._desktop_workspace = desktop_workspace 

1265 client_properties = {} 

1266 if window_workspace is not None: 

1267 client_properties["workspace"] = window_workspace 

1268 self.send_control_refresh(suspend_resume, client_properties) 

1269 

1270 def send_control_refresh(self, suspend_resume, client_properties=None, refresh=False): 

1271 statelog("send_control_refresh%s", (suspend_resume, client_properties, refresh)) 

1272 #we can tell the server using a "buffer-refresh" packet instead 

1273 #and also take care of tweaking the batch config 

1274 options = {"refresh-now" : refresh} #no need to refresh it 

1275 self._client.control_refresh(self._id, suspend_resume, refresh=refresh, options=options, client_properties=client_properties) 

1276 

1277 def get_workspace_count(self): 

1278 if not self._can_set_workspace: 

1279 return None 

1280 root = get_default_root_window() 

1281 return self.xget_u32_property(root, "_NET_NUMBER_OF_DESKTOPS") 

1282 

1283 def set_workspace(self, workspace): 

1284 workspacelog("set_workspace(%s)", workspace) 

1285 if not self._can_set_workspace: 

1286 return 

1287 if not self._been_mapped: 

1288 #will be dealt with in the map event handler 

1289 #which will look at the window metadata again 

1290 workspacelog("workspace=%s will be set when the window is mapped", wn(workspace)) 

1291 return 

1292 workspace = workspace 

1293 if workspace is not None: 

1294 workspace = workspace & 0xffffffff 

1295 desktop = self.get_desktop_workspace() 

1296 ndesktops = self.get_workspace_count() 

1297 current = self.get_window_workspace() 

1298 workspacelog("set_workspace(%s) realized=%s", wn(workspace), self.get_realized()) 

1299 workspacelog(" current workspace=%s, detected=%s, desktop workspace=%s, ndesktops=%s", 

1300 wn(self._window_workspace), wn(current), wn(desktop), ndesktops) 

1301 if not self._can_set_workspace or ndesktops is None: 

1302 return 

1303 if workspace==desktop or workspace==WORKSPACE_ALL or desktop is None: 

1304 #window is back in view 

1305 self._client.control_refresh(self._id, False, False) 

1306 if (workspace<0 or workspace>=ndesktops) and workspace not in(WORKSPACE_UNSET, WORKSPACE_ALL): 

1307 #this should not happen, workspace is unsigned (CARDINAL) 

1308 #and the server should have the same list of desktops that we have here 

1309 workspacelog.warn("Warning: invalid workspace number: %s", wn(workspace)) 

1310 workspace = WORKSPACE_UNSET 

1311 if workspace==WORKSPACE_UNSET: 

1312 #we cannot unset via send_wm_workspace, so we have to choose one: 

1313 workspace = self.get_desktop_workspace() 

1314 if workspace in (None, WORKSPACE_UNSET): 

1315 workspacelog.warn("workspace=%s (doing nothing)", wn(workspace)) 

1316 return 

1317 #we will need the gdk window: 

1318 if current==workspace: 

1319 workspacelog("window workspace unchanged: %s", wn(workspace)) 

1320 return 

1321 gdkwin = self.get_window() 

1322 workspacelog("do_set_workspace: gdkwindow: %#x, mapped=%s, visible=%s", 

1323 gdkwin.get_xid(), self.get_mapped(), gdkwin.is_visible()) 

1324 root = get_default_root_window() 

1325 with xlog: 

1326 send_wm_workspace(root, gdkwin, workspace) 

1327 

1328 def get_desktop_workspace(self): 

1329 window = self.get_window() 

1330 if window: 

1331 root = window.get_screen().get_root_window() 

1332 else: 

1333 #if we are called during init.. we don't have a window 

1334 root = get_default_root_window() 

1335 return self.do_get_workspace(root, "_NET_CURRENT_DESKTOP") 

1336 

1337 def get_window_workspace(self): 

1338 return self.do_get_workspace(self.get_window(), "_NET_WM_DESKTOP", WORKSPACE_UNSET) 

1339 

1340 def do_get_workspace(self, target, prop, default_value=None): 

1341 if not self._can_set_workspace: 

1342 workspacelog("do_get_workspace: not supported, returning %s", wn(default_value)) 

1343 return default_value #windows and OSX do not have workspaces 

1344 if target is None: 

1345 workspacelog("do_get_workspace: target is None, returning %s", wn(default_value)) 

1346 return default_value #window is not realized yet 

1347 value = self.xget_u32_property(target, prop) 

1348 if value is not None: 

1349 workspacelog("do_get_workspace %s=%s on window %i: %#x", 

1350 prop, wn(value), self._id, target.get_xid()) 

1351 return value & 0xffffffff 

1352 workspacelog("do_get_workspace %s unset on window %i: %#x, returning default value=%s", 

1353 prop, self._id, target.get_xid(), wn(default_value)) 

1354 return default_value 

1355 

1356 

1357 def keyboard_ungrab(self, *args): 

1358 grablog("keyboard_ungrab%s", args) 

1359 self._client.keyboard_grabbed = False 

1360 gdkwin = self.get_window() 

1361 if gdkwin: 

1362 d = gdkwin.get_display() 

1363 if d: 

1364 d.keyboard_ungrab(0) 

1365 return True 

1366 

1367 def keyboard_grab(self, *args): 

1368 grablog("keyboard_grab%s", args) 

1369 r = Gdk.keyboard_grab(self.get_window(), True, 0) 

1370 self._client.keyboard_grabbed = r==Gdk.GrabStatus.SUCCESS 

1371 grablog("keyboard_grab%s Gdk.keyboard_grab(%s, True)=%s, keyboard_grabbed=%s", 

1372 args, self.get_window(), GRAB_STATUS_STRING.get(r), self._client.keyboard_grabbed) 

1373 

1374 def toggle_keyboard_grab(self): 

1375 grabbed = self._client.keyboard_grabbed 

1376 grablog("toggle_keyboard_grab() grabbed=%s", grabbed) 

1377 if grabbed: 

1378 self.keyboard_ungrab() 

1379 else: 

1380 self.keyboard_grab() 

1381 

1382 def pointer_grab(self, *args): 

1383 gdkwin = self.get_window() 

1384 em = Gdk.EventMask 

1385 event_mask = (em.BUTTON_PRESS_MASK | 

1386 em.BUTTON_RELEASE_MASK | 

1387 em.POINTER_MOTION_MASK | 

1388 em.POINTER_MOTION_HINT_MASK | 

1389 em.ENTER_NOTIFY_MASK | 

1390 em.LEAVE_NOTIFY_MASK) 

1391 r = Gdk.pointer_grab(gdkwin, True, event_mask, gdkwin, None, 0) 

1392 self._client.pointer_grabbed = r==Gdk.GrabStatus.SUCCESS 

1393 grablog("pointer_grab%s Gdk.pointer_grab(%s, True)=%s, pointer_grabbed=%s", 

1394 args, self.get_window(), GRAB_STATUS_STRING.get(r), self._client.pointer_grabbed) 

1395 

1396 def pointer_ungrab(self, *args): 

1397 grablog("pointer_ungrab%s pointer_grabbed=%s", 

1398 args, self._client.pointer_grabbed) 

1399 self._client.pointer_grabbed = False 

1400 gdkwin = self.get_window() 

1401 if gdkwin: 

1402 d = gdkwin.get_display() 

1403 if d: 

1404 d.pointer_ungrab(0) 

1405 return True 

1406 

1407 def toggle_pointer_grab(self): 

1408 pg = self._client.pointer_grabbed 

1409 grablog("toggle_pointer_grab() pointer_grabbed=%s", pg) 

1410 if pg: 

1411 self.pointer_ungrab() 

1412 else: 

1413 self.pointer_grab() 

1414 

1415 

1416 def toggle_fullscreen(self): 

1417 geomlog("toggle_fullscreen()") 

1418 if self._fullscreen: 

1419 self.unfullscreen() 

1420 else: 

1421 self.fullscreen() 

1422 

1423 

1424 ###################################################################### 

1425 # pointer overlay handling 

1426 def cancel_remove_pointer_overlay_timer(self): 

1427 rpot = self.remove_pointer_overlay_timer 

1428 if rpot: 

1429 self.remove_pointer_overlay_timer = None 

1430 self.source_remove(rpot) 

1431 

1432 def cancel_show_pointer_overlay_timer(self): 

1433 rsot = self.show_pointer_overlay_timer 

1434 if rsot: 

1435 self.show_pointer_overlay_timer = None 

1436 self.source_remove(rsot) 

1437 

1438 def show_pointer_overlay(self, pos): 

1439 #schedule do_show_pointer_overlay if needed 

1440 b = self._backing 

1441 if not b: 

1442 return 

1443 prev = b.pointer_overlay 

1444 if pos is None: 

1445 if prev is None: 

1446 return 

1447 value = None 

1448 else: 

1449 if prev and prev[:2]==pos[:2]: 

1450 return 

1451 #store both scaled and unscaled value: 

1452 #(the opengl client uses the raw value) 

1453 value = pos[:2]+self._client.sp(*pos[:2])+pos[2:] 

1454 mouselog("show_pointer_overlay(%s) previous value=%s, new value=%s", pos, prev, value) 

1455 b.pointer_overlay = value 

1456 if not self.show_pointer_overlay_timer: 

1457 self.show_pointer_overlay_timer = self.timeout_add(10, self.do_show_pointer_overlay, prev) 

1458 

1459 def do_show_pointer_overlay(self, prev): 

1460 #queue a draw event at the previous and current position of the pointer 

1461 #(so the backend will repaint / overlay the cursor image there) 

1462 self.show_pointer_overlay_timer = None 

1463 b = self._backing 

1464 if not b: 

1465 return 

1466 cursor_data = b.cursor_data 

1467 def abs_coords(x, y, size): 

1468 if self.window_offset: 

1469 x += self.window_offset[0] 

1470 y += self.window_offset[1] 

1471 w, h = size, size 

1472 if cursor_data: 

1473 w = cursor_data[3] 

1474 h = cursor_data[4] 

1475 xhot = cursor_data[5] 

1476 yhot = cursor_data[6] 

1477 x = x-xhot 

1478 y = y-yhot 

1479 return x, y, w, h 

1480 value = b.pointer_overlay 

1481 if value: 

1482 #repaint the scale value (in window coordinates): 

1483 x, y, w, h = abs_coords(*value[2:5]) 

1484 self.repaint(x, y, w, h) 

1485 #clear it shortly after: 

1486 self.cancel_remove_pointer_overlay_timer() 

1487 def remove_pointer_overlay(): 

1488 self.remove_pointer_overlay_timer = None 

1489 self.show_pointer_overlay(None) 

1490 self.remove_pointer_overlay_timer = self.timeout_add(CURSOR_IDLE_TIMEOUT*1000, remove_pointer_overlay) 

1491 if prev: 

1492 x, y, w, h = abs_coords(*prev[2:5]) 

1493 self.repaint(x, y, w, h) 

1494 

1495 

1496 def _do_button_press_event(self, event): 

1497 #Gtk.Window.do_button_press_event(self, event) 

1498 self._button_action(event.button, event, True) 

1499 

1500 def _do_button_release_event(self, event): 

1501 #Gtk.Window.do_button_release_event(self, event) 

1502 self._button_action(event.button, event, False) 

1503 

1504 ###################################################################### 

1505 # pointer motion 

1506 

1507 def _do_motion_notify_event(self, event): 

1508 #Gtk.Window.do_motion_notify_event(self, event) 

1509 if self.moveresize_event: 

1510 self.motion_moveresize(event) 

1511 ClientWindowBase._do_motion_notify_event(self, event) 

1512 

1513 def motion_moveresize(self, event): 

1514 x_root, y_root, direction, button, start_buttons, wx, wy, ww, wh = self.moveresize_event 

1515 dirstr = MOVERESIZE_DIRECTION_STRING.get(direction, direction) 

1516 buttons = self._event_buttons(event) 

1517 geomlog("motion_moveresize(%s) direction=%s, buttons=%s", event, dirstr, buttons) 

1518 if start_buttons is None: 

1519 #first time around, store the buttons 

1520 start_buttons = buttons 

1521 self.moveresize_event[4] = buttons 

1522 if (button>0 and button not in buttons) or (button==0 and start_buttons!=buttons): 

1523 geomlog("%s for window button %i is no longer pressed (buttons=%s) cancelling moveresize", 

1524 dirstr, button, buttons) 

1525 self.moveresize_event = None 

1526 self.cancel_moveresize_timer() 

1527 else: 

1528 x = event.x_root 

1529 y = event.y_root 

1530 dx = x-x_root 

1531 dy = y-y_root 

1532 #clamp resizing using size hints, 

1533 #or sane defaults: minimum of (1x1) and maximum of (2*15x2*25) 

1534 minw = self.geometry_hints.get("min_width", 1) 

1535 minh = self.geometry_hints.get("min_height", 1) 

1536 maxw = self.geometry_hints.get("max_width", 2**15) 

1537 maxh = self.geometry_hints.get("max_height", 2**15) 

1538 geomlog("%s: min=%ix%i, max=%ix%i, window=%ix%i, delta=%ix%i", 

1539 dirstr, minw, minh, maxw, maxh, ww, wh, dx, dy) 

1540 if direction in (MOVERESIZE_SIZE_BOTTOMRIGHT, MOVERESIZE_SIZE_BOTTOM, MOVERESIZE_SIZE_BOTTOMLEFT): 

1541 #height will be set to: wh+dy 

1542 dy = max(minh-wh, dy) 

1543 dy = min(maxh-wh, dy) 

1544 elif direction in (MOVERESIZE_SIZE_TOPRIGHT, MOVERESIZE_SIZE_TOP, MOVERESIZE_SIZE_TOPLEFT): 

1545 #height will be set to: wh-dy 

1546 dy = min(wh-minh, dy) 

1547 dy = max(wh-maxh, dy) 

1548 if direction in (MOVERESIZE_SIZE_BOTTOMRIGHT, MOVERESIZE_SIZE_RIGHT, MOVERESIZE_SIZE_TOPRIGHT): 

1549 #width will be set to: ww+dx 

1550 dx = max(minw-ww, dx) 

1551 dx = min(maxw-ww, dx) 

1552 elif direction in (MOVERESIZE_SIZE_BOTTOMLEFT, MOVERESIZE_SIZE_LEFT, MOVERESIZE_SIZE_TOPLEFT): 

1553 #width will be set to: ww-dx 

1554 dx = min(ww-minw, dx) 

1555 dx = max(ww-maxw, dx) 

1556 #calculate move + resize: 

1557 if direction==MOVERESIZE_MOVE: 

1558 data = (wx+dx, wy+dy), None 

1559 elif direction==MOVERESIZE_SIZE_BOTTOMRIGHT: 

1560 data = None, (ww+dx, wh+dy) 

1561 elif direction==MOVERESIZE_SIZE_BOTTOM: 

1562 data = None, (ww, wh+dy) 

1563 elif direction==MOVERESIZE_SIZE_BOTTOMLEFT: 

1564 data = (wx+dx, wy), (ww-dx, wh+dy) 

1565 elif direction==MOVERESIZE_SIZE_RIGHT: 

1566 data = None, (ww+dx, wh) 

1567 elif direction==MOVERESIZE_SIZE_LEFT: 

1568 data = (wx+dx, wy), (ww-dx, wh) 

1569 elif direction==MOVERESIZE_SIZE_TOPRIGHT: 

1570 data = (wx, wy+dy), (ww+dx, wh-dy) 

1571 elif direction==MOVERESIZE_SIZE_TOP: 

1572 data = (wx, wy+dy), (ww, wh-dy) 

1573 elif direction==MOVERESIZE_SIZE_TOPLEFT: 

1574 data = (wx+dx, wy+dy), (ww-dx, wh-dy) 

1575 else: 

1576 #not handled yet! 

1577 data = None 

1578 geomlog("%s for window %ix%i: started at %s, now at %s, delta=%s, button=%s, buttons=%s, data=%s", 

1579 dirstr, ww, wh, (x_root, y_root), (x, y), (dx, dy), button, buttons, data) 

1580 if data: 

1581 #modifying the window is slower than moving the pointer, 

1582 #do it via a timer to batch things together 

1583 self.moveresize_data = data 

1584 if self.moveresize_timer is None: 

1585 self.moveresize_timer = self.timeout_add(20, self.do_moveresize) 

1586 

1587 def cancel_moveresize_timer(self): 

1588 mrt = self.moveresize_timer 

1589 if mrt: 

1590 self.moveresize_timer = None 

1591 self.source_remove(mrt) 

1592 

1593 def do_moveresize(self): 

1594 self.moveresize_timer = None 

1595 mrd = self.moveresize_data 

1596 geomlog("do_moveresize() data=%s", mrd) 

1597 if not mrd: 

1598 return 

1599 move, resize = mrd 

1600 if move: 

1601 x, y = int(move[0]), int(move[1]) 

1602 if resize: 

1603 w, h = int(resize[0]), int(resize[1]) 

1604 if self._client.readonly: 

1605 #change size-constraints first, 

1606 #so the resize can be honoured: 

1607 sc = self._force_size_constraint(w, h) 

1608 self._metadata.update(sc) 

1609 self.set_metadata(sc) 

1610 if move and resize: 

1611 self.get_window().move_resize(x, y, w, h) 

1612 elif move: 

1613 self.get_window().move(x, y) 

1614 elif resize: 

1615 self.get_window().resize(w, h) 

1616 

1617 

1618 def initiate_moveresize(self, x_root, y_root, direction, button, source_indication): 

1619 geomlog("initiate_moveresize%s", 

1620 (x_root, y_root, MOVERESIZE_DIRECTION_STRING.get(direction, direction), 

1621 button, SOURCE_INDICATION_STRING.get(source_indication, source_indication))) 

1622 #the values we get are bogus! 

1623 #x, y = x_root, y_root 

1624 #use the current position instead: 

1625 p = self.get_root_window().get_pointer()[-3:-1] 

1626 x, y = p[0], p[1] 

1627 if MOVERESIZE_X11 and HAS_X11_BINDINGS: 

1628 self.initiate_moveresize_X11(x, y, direction, button, source_indication) 

1629 return 

1630 if direction==MOVERESIZE_CANCEL: 

1631 self.moveresize_event = None 

1632 self.moveresize_data = None 

1633 self.cancel_moveresize_timer() 

1634 elif MOVERESIZE_GDK: 

1635 if direction in (MOVERESIZE_MOVE, MOVERESIZE_MOVE_KEYBOARD): 

1636 self.begin_move_drag(button, x, y, 0) 

1637 else: 

1638 edge = { 

1639 MOVERESIZE_SIZE_TOPLEFT : Gdk.WindowEdge.NORTH_WEST, 

1640 MOVERESIZE_SIZE_TOP : Gdk.WindowEdge.NORTH, 

1641 MOVERESIZE_SIZE_TOPRIGHT : Gdk.WindowEdge.NORTH_EAST, 

1642 MOVERESIZE_SIZE_RIGHT : Gdk.WindowEdge.EAST, 

1643 MOVERESIZE_SIZE_BOTTOMRIGHT : Gdk.WindowEdge.SOUTH_EAST, 

1644 MOVERESIZE_SIZE_BOTTOM : Gdk.WindowEdge.SOUTH, 

1645 MOVERESIZE_SIZE_BOTTOMLEFT : Gdk.WindowEdge.SOUTH_WEST, 

1646 MOVERESIZE_SIZE_LEFT : Gdk.WindowEdge.WEST, 

1647 #MOVERESIZE_SIZE_KEYBOARD, 

1648 }.get(direction) 

1649 geomlog("edge(%s)=%s", MOVERESIZE_DIRECTION_STRING.get(direction), edge) 

1650 if direction is not None: 

1651 self.begin_resize_drag(edge, button, x, y, 0) 

1652 else: 

1653 #handle it ourselves: 

1654 #use window coordinates (which include decorations) 

1655 wx, wy = self.get_window().get_root_origin() 

1656 ww, wh = self.get_size() 

1657 self.moveresize_event = [x_root, y_root, direction, button, None, wx, wy, ww, wh] 

1658 

1659 def initiate_moveresize_X11(self, x_root, y_root, direction, button, source_indication): 

1660 statelog("initiate_moveresize_X11%s", 

1661 (x_root, y_root, MOVERESIZE_DIRECTION_STRING.get(direction, direction), 

1662 button, SOURCE_INDICATION_STRING.get(source_indication, source_indication))) 

1663 event_mask = SubstructureNotifyMask | SubstructureRedirectMask 

1664 root = self.get_window().get_screen().get_root_window() 

1665 root_xid = root.get_xid() 

1666 xwin = self.get_window().get_xid() 

1667 with xlog: 

1668 X11Core.UngrabPointer() 

1669 X11Window.sendClientMessage(root_xid, xwin, False, event_mask, "_NET_WM_MOVERESIZE", 

1670 x_root, y_root, direction, button, source_indication) 

1671 

1672 

1673 def apply_transient_for(self, wid): 

1674 if wid==-1: 

1675 def set_root_transient(): 

1676 #root is a gdk window, so we need to ensure we have one 

1677 #backing our gtk window to be able to call set_transient_for on it 

1678 log("%s.apply_transient_for(%s) gdkwindow=%s, mapped=%s", 

1679 self, wid, self.get_window(), self.get_mapped()) 

1680 self.get_window().set_transient_for(get_default_root_window()) 

1681 self.when_realized("transient-for-root", set_root_transient) 

1682 else: 

1683 #gtk window is easier: 

1684 window = self._client._id_to_window.get(wid) 

1685 log("%s.apply_transient_for(%s) window=%s", self, wid, window) 

1686 if window: 

1687 self.set_transient_for(window) 

1688 

1689 def cairo_paint_border(self, context, clip_area=None): 

1690 log("cairo_paint_border(%s, %s)", context, clip_area) 

1691 b = self.border 

1692 if b is None or not b.shown: 

1693 return 

1694 s = b.size 

1695 ww, wh = self.get_size() 

1696 borders = [] 

1697 #window is wide enough, add borders on the side: 

1698 borders.append((0, 0, s, wh)) #left 

1699 borders.append((ww-s, 0, s, wh)) #right 

1700 #window is tall enough, add borders on top and bottom: 

1701 borders.append((0, 0, ww, s)) #top 

1702 borders.append((0, wh-s, ww, s)) #bottom 

1703 for x, y, w, h in borders: 

1704 if w<=0 or h<=0: 

1705 continue 

1706 r = Gdk.Rectangle() 

1707 r.x = x 

1708 r.y = y 

1709 r.width = w 

1710 r.height = h 

1711 rect = r 

1712 if clip_area: 

1713 rect = clip_area.intersect(r) 

1714 if rect.width==0 or rect.height==0: 

1715 continue 

1716 context.save() 

1717 context.rectangle(x, y, w, h) 

1718 context.clip() 

1719 context.set_source_rgba(self.border.red, self.border.green, self.border.blue, self.border.alpha) 

1720 context.fill() 

1721 context.paint() 

1722 context.restore() 

1723 

1724 

1725 def paint_spinner(self, context, area=None): 

1726 log("%s.paint_spinner(%s, %s)", self, context, area) 

1727 c = self._client 

1728 if not c: 

1729 return 

1730 ww, wh = self.get_size() 

1731 w = c.cx(ww) 

1732 h = c.cy(wh) 

1733 #add grey semi-opaque layer on top: 

1734 context.set_operator(OPERATOR_OVER) 

1735 context.set_source_rgba(0.2, 0.2, 0.2, 0.4) 

1736 #we can't use the area as rectangle with: 

1737 #context.rectangle(area) 

1738 #because those would be unscaled dimensions 

1739 #it's easier and safer to repaint the whole window: 

1740 context.rectangle(0, 0, w, h) 

1741 context.fill() 

1742 #add spinner: 

1743 dim = min(w/3.0, h/3.0, 100.0) 

1744 context.set_line_width(dim/10.0) 

1745 context.set_line_cap(LINE_CAP_ROUND) 

1746 context.translate(w/2, h/2) 

1747 from xpra.client.spinner import cv 

1748 count = int(monotonic_time()*4.0) 

1749 for i in range(8): #8 lines 

1750 context.set_source_rgba(0, 0, 0, cv.trs[count%8][i]) 

1751 context.move_to(0.0, -dim/4.0) 

1752 context.line_to(0.0, -dim) 

1753 context.rotate(math.pi/4) 

1754 context.stroke() 

1755 

1756 def spinner(self, _ok): 

1757 c = self._client 

1758 if not self.can_have_spinner() or not c: 

1759 return 

1760 #with normal windows, we just queue a draw request 

1761 #and let the expose event paint the spinner 

1762 w, h = self.get_size() 

1763 self.repaint(0, 0, w, h) 

1764 

1765 

1766 def do_map_event(self, event): 

1767 log("%s.do_map_event(%s) OR=%s", self, event, self._override_redirect) 

1768 Gtk.Window.do_map_event(self, event) 

1769 if not self._override_redirect: 

1770 #we can get a map event for an iconified window on win32: 

1771 if self._iconified: 

1772 self.deiconify() 

1773 self.process_map_event() 

1774 #use the drawing area to enforce the minimum size: 

1775 #(as this also honoured correctly with CSD, 

1776 # whereas set_geometry_hints is not..) 

1777 minw, minh = self.size_constraints.intpair("minimum-size", (0, 0)) 

1778 self.drawing_area.set_size_request(minw, minh) 

1779 

1780 def process_map_event(self): 

1781 x, y, w, h = self.get_drawing_area_geometry() 

1782 state = self._window_state 

1783 props = self._client_properties 

1784 self._client_properties = {} 

1785 self._window_state = {} 

1786 self.cancel_window_state_timer() 

1787 workspace = self.get_window_workspace() 

1788 if self._been_mapped: 

1789 if workspace is None: 

1790 #not set, so assume it is on the current workspace: 

1791 workspace = self.get_desktop_workspace() 

1792 else: 

1793 self._been_mapped = True 

1794 workspace = self._metadata.intget("workspace", WORKSPACE_UNSET) 

1795 if workspace!=WORKSPACE_UNSET: 

1796 log("map event set workspace %s", wn(workspace)) 

1797 self.set_workspace(workspace) 

1798 if self._window_workspace!=workspace and workspace is not None: 

1799 workspacelog("map event: been_mapped=%s, changed workspace from %s to %s", 

1800 self._been_mapped, wn(self._window_workspace), wn(workspace)) 

1801 self._window_workspace = workspace 

1802 if workspace is not None: 

1803 props["workspace"] = workspace 

1804 if self._client.server_window_frame_extents and "frame" not in state: 

1805 wfs = self.get_window_frame_size() 

1806 if wfs and len(wfs)==4: 

1807 state["frame"] = self._client.crect(*wfs) 

1808 self._current_frame_extents = wfs 

1809 geomlog("map-window wid=%s, geometry=%s, client props=%s, state=%s", self._id, (x, y, w, h), props, state) 

1810 cx = self._client.cx 

1811 cy = self._client.cy 

1812 sx, sy, sw, sh = cx(x), cy(y), cx(w), cy(h) 

1813 packet = ["map-window", self._id, sx, sy, sw, sh, props, state] 

1814 self.send(*packet) 

1815 self._pos = (x, y) 

1816 self._size = (w, h) 

1817 if self._backing is None: 

1818 #we may have cleared the backing, so we must re-create one: 

1819 self._set_backing_size(w, h) 

1820 if not self._override_redirect: 

1821 htf = self.has_toplevel_focus() 

1822 focuslog("mapped: has-toplevel-focus=%s", htf) 

1823 if htf: 

1824 self._client.update_focus(self._id, htf) 

1825 

1826 def get_window_frame_size(self): 

1827 frame = self._client.get_frame_extents(self) 

1828 if not frame: 

1829 #default to global value we may have: 

1830 wfs = self._client.get_window_frame_sizes() 

1831 if wfs: 

1832 frame = wfs.get("frame") 

1833 return frame 

1834 

1835 

1836 def send_configure(self): 

1837 self.send_configure_event() 

1838 

1839 def do_configure_event(self, event): 

1840 eventslog("%s.do_configure_event(%s) OR=%s, iconified=%s", 

1841 self, event, self._override_redirect, self._iconified) 

1842 Gtk.Window.do_configure_event(self, event) 

1843 if not self._override_redirect and not self._iconified: 

1844 self.process_configure_event() 

1845 

1846 def process_configure_event(self, skip_geometry=False): 

1847 assert skip_geometry or not self.is_OR() 

1848 x, y, w, h = self.get_drawing_area_geometry() 

1849 w = max(1, w) 

1850 h = max(1, h) 

1851 ox, oy = self._pos 

1852 dx, dy = x-ox, y-oy 

1853 self._pos = (x, y) 

1854 self.send_configure_event(skip_geometry) 

1855 if dx!=0 or dy!=0: 

1856 #window has moved, also move any child OR window: 

1857 for window in self._override_redirect_windows: 

1858 x, y = window.get_position() 

1859 window.move(x+dx, y+dy) 

1860 geomlog("configure event: current size=%s, new size=%s, backing=%s, iconified=%s", 

1861 self._size, (w, h), self._backing, self._iconified) 

1862 self._size = (w, h) 

1863 self._set_backing_size(w, h) 

1864 if self._backing and not self._iconified: 

1865 geomlog("configure event: size unchanged, queueing redraw") 

1866 self.repaint(0, 0, w, h) 

1867 

1868 def send_configure_event(self, skip_geometry=False): 

1869 assert skip_geometry or not self.is_OR() 

1870 x, y, w, h = self.get_drawing_area_geometry() 

1871 w = max(1, w) 

1872 h = max(1, h) 

1873 state = self._window_state 

1874 props = self._client_properties 

1875 self._client_properties = {} 

1876 self._window_state = {} 

1877 self.cancel_window_state_timer() 

1878 if self._been_mapped: 

1879 #if the window has been mapped already, the workspace should be set: 

1880 workspace = self.get_window_workspace() 

1881 if self._window_workspace!=workspace and workspace is not None: 

1882 workspacelog("send_configure_event: changed workspace from %s to %s", 

1883 wn(self._window_workspace), wn(workspace)) 

1884 self._window_workspace = workspace 

1885 props["workspace"] = workspace 

1886 cx = self._client.cx 

1887 cy = self._client.cy 

1888 sx, sy, sw, sh = cx(x), cy(y), cx(w), cy(h) 

1889 packet = ["configure-window", self._id, sx, sy, sw, sh, props, self._resize_counter, state, skip_geometry] 

1890 pwid = self._id 

1891 if self.is_OR(): 

1892 pwid = -1 

1893 packet.append(pwid) 

1894 packet.append(self._client.get_mouse_position()) 

1895 packet.append(self._client.get_current_modifiers()) 

1896 geomlog("%s", packet) 

1897 self.send(*packet) 

1898 

1899 def _set_backing_size(self, ww, wh): 

1900 b = self._backing 

1901 bw = self._client.cx(ww) 

1902 bh = self._client.cy(wh) 

1903 if max(ww, wh)>=32000 or min(ww, wh)<0: 

1904 raise Exception("invalid window size %ix%i" % (ww, wh)) 

1905 if max(bw, bh)>=32000: 

1906 raise Exception("invalid window backing size %ix%i" % (bw, bh)) 

1907 if b: 

1908 b.init(ww, wh, bw, bh) 

1909 else: 

1910 self.new_backing(bw, bh) 

1911 self._client_properties["encoding.render-size"] = b.render_size 

1912 

1913 def resize(self, w, h, resize_counter=0): 

1914 ww, wh = self.get_size() 

1915 geomlog("resize(%s, %s, %s) current size=%s, fullscreen=%s, maximized=%s", 

1916 w, h, resize_counter, (ww, wh), self._fullscreen, self._maximized) 

1917 self._resize_counter = resize_counter 

1918 if (w, h)==(ww, wh): 

1919 self._backing.offsets = 0, 0, 0, 0 

1920 self.repaint(0, 0, w, h) 

1921 return 

1922 if not self._fullscreen and not self._maximized: 

1923 Gtk.Window.resize(self, w, h) 

1924 ww, wh = w, h 

1925 self._backing.offsets = 0, 0, 0, 0 

1926 else: 

1927 self.center_backing(w, h) 

1928 geomlog("backing offsets=%s, window offset=%s", self._backing.offsets, self.window_offset) 

1929 self._set_backing_size(w, h) 

1930 self.repaint(0, 0, ww, wh) 

1931 

1932 def center_backing(self, w, h): 

1933 ww, wh = self.get_size() 

1934 #align in the middle: 

1935 dw = max(0, ww-w) 

1936 dh = max(0, wh-h) 

1937 ox = dw//2 

1938 oy = dh//2 

1939 geomlog("using window offset values %i,%i", ox, oy) 

1940 #some backings use top,left values, 

1941 #(opengl uses left and botton since the viewport starts at the bottom) 

1942 self._backing.offsets = ox, oy, ox+(dw&0x1), oy+(dh&0x1) 

1943 geomlog("center_backing(%i, %i) window size=%ix%i, backing offsets=%s", w, h, ww, wh, self._backing.offsets) 

1944 #adjust pointer coordinates: 

1945 self.window_offset = ox, oy 

1946 

1947 def paint_backing_offset_border(self, backing, context): 

1948 w,h = self.get_size() 

1949 left, top, right, bottom = backing.offsets 

1950 if left!=0 or top!=0 or right!=0 or bottom!=0: 

1951 context.save() 

1952 context.set_source_rgb(*PADDING_COLORS) 

1953 coords = ( 

1954 (0, 0, left, h), #left hand side padding 

1955 (0, 0, w, top), #top padding 

1956 (w-right, 0, right, h), #RHS 

1957 (0, h-bottom, w, bottom), #bottom 

1958 ) 

1959 geomlog("paint_backing_offset_border(%s, %s) offsets=%s, size=%s, rgb=%s, coords=%s", 

1960 backing, context, backing.offsets, (w,h), PADDING_COLORS, coords) 

1961 for rx, ry, rw, rh in coords: 

1962 if rw>0 and rh>0: 

1963 context.rectangle(rx, ry, rw, rh) 

1964 context.fill() 

1965 context.restore() 

1966 

1967 def clip_to_backing(self, backing, context): 

1968 w,h = self.get_size() 

1969 left, top, right, bottom = backing.offsets 

1970 clip_rect = (left, top, w-left-right, h-top-bottom) 

1971 context.rectangle(*clip_rect) 

1972 geomlog("clip_to_backing%s rectangle=%s", (backing, context), clip_rect) 

1973 context.clip() 

1974 

1975 def move_resize(self, x, y, w, h, resize_counter=0): 

1976 geomlog("window %i move_resize%s", self._id, (x, y, w, h, resize_counter)) 

1977 x, y = self.adjusted_position(x, y) 

1978 w = max(1, w) 

1979 h = max(1, h) 

1980 if self.window_offset: 

1981 x += self.window_offset[0] 

1982 y += self.window_offset[1] 

1983 #TODO: check this doesn't move it off-screen! 

1984 self._resize_counter = resize_counter 

1985 wx, wy = self.get_drawing_area_geometry()[:2] 

1986 if (wx, wy)==(x, y): 

1987 #same location, just resize: 

1988 if self._size==(w, h): 

1989 geomlog("window unchanged") 

1990 else: 

1991 geomlog("unchanged position %ix%i, using resize(%i, %i)", x, y, w, h) 

1992 self.resize(w, h) 

1993 return 

1994 #we have to move: 

1995 if not self.get_realized(): 

1996 geomlog("window was not realized yet") 

1997 self.realize() 

1998 #adjust for window frame: 

1999 window = self.get_window() 

2000 ox, oy = window.get_origin()[-2:] 

2001 rx, ry = window.get_root_origin() 

2002 ax = x - (ox - rx) 

2003 ay = y - (oy - ry) 

2004 geomlog("window origin=%ix%i, root origin=%ix%i, actual position=%ix%i", ox, oy, rx, ry, ax, ay) 

2005 #validate against edge of screen (ensure window is shown): 

2006 if CLAMP_WINDOW_TO_SCREEN: 

2007 mw, mh = self._client.get_root_size() 

2008 if (ax + w)<=0: 

2009 ax = -w + 1 

2010 elif ax >= mw: 

2011 ax = mw - 1 

2012 if not WINDOW_OVERFLOW_TOP and ay<=0: 

2013 ay = 0 

2014 elif (ay + h)<=0: 

2015 ay = -y + 1 

2016 elif ay >= mh: 

2017 ay = mh -1 

2018 geomlog("validated window position for total screen area %ix%i : %ix%i", mw, mh, ax, ay) 

2019 if self._size==(w, h): 

2020 #just move: 

2021 geomlog("window size unchanged: %ix%i, using move(%i, %i)", w, h, ax, ay) 

2022 window.move(ax, ay) 

2023 return 

2024 #resize: 

2025 self._size = (w, h) 

2026 geomlog("%s.move_resize%s", window, (ax, ay, w, h)) 

2027 window.move_resize(ax, ay, w, h) 

2028 #re-init the backing with the new size 

2029 self._set_backing_size(w, h) 

2030 

2031 

2032 def noop_destroy(self): 

2033 log.warn("Warning: window destroy called twice!") 

2034 

2035 def destroy(self): #pylint: disable=method-hidden 

2036 self.cancel_window_state_timer() 

2037 self.cancel_send_iconifiy_timer() 

2038 self.cancel_show_pointer_overlay_timer() 

2039 self.cancel_remove_pointer_overlay_timer() 

2040 self.cancel_focus_timer() 

2041 self.cancel_moveresize_timer() 

2042 self.on_realize_cb = {} 

2043 ClientWindowBase.destroy(self) 

2044 Gtk.Window.destroy(self) 

2045 self._unfocus() 

2046 self.destroy = self.noop_destroy 

2047 

2048 

2049 def do_unmap_event(self, event): 

2050 eventslog("do_unmap_event(%s)", event) 

2051 self._unfocus() 

2052 if not self._override_redirect: 

2053 self.send("unmap-window", self._id, False) 

2054 

2055 def do_delete_event(self, event): 

2056 #Gtk.Window.do_delete_event(self, event) 

2057 eventslog("do_delete_event(%s)", event) 

2058 self._client.window_close_event(self._id) 

2059 return True 

2060 

2061 

2062 def _offset_pointer(self, x, y): 

2063 if self.window_offset: 

2064 x -= self.window_offset[0] 

2065 y -= self.window_offset[1] 

2066 return self._client.cp(x, y) 

2067 

2068 def _get_pointer(self, event): 

2069 return event.x_root, event.y_root 

2070 

2071 def _get_relative_pointer(self, event): 

2072 return event.x, event.y 

2073 

2074 def _pointer_modifiers(self, event): 

2075 x, y = self._get_pointer(event) 

2076 rx, ry = self._get_relative_pointer(event) 

2077 #adjust for window offset: 

2078 pointer = self._offset_pointer(x, y) 

2079 relative_pointer = self._client.cp(rx, ry) 

2080 #FIXME: state is used for both mods and buttons?? 

2081 modifiers = self._client.mask_to_names(event.state) 

2082 buttons = self._event_buttons(event) 

2083 v = pointer, relative_pointer, modifiers, buttons 

2084 mouselog("pointer_modifiers(%s)=%s (x_root=%s, y_root=%s, window_offset=%s)", 

2085 event, v, event.x_root, event.y_root, self.window_offset) 

2086 return v 

2087 

2088 def _event_buttons(self, event): 

2089 return [button for mask, button in BUTTON_MASK.items() if event.state & mask] 

2090 

2091 def parse_key_event(self, event, pressed): 

2092 keyval = event.keyval 

2093 keycode = event.hardware_keycode 

2094 keyname = Gdk.keyval_name(keyval) 

2095 keyname = KEY_TRANSLATIONS.get((keyname, keyval, keycode), keyname) 

2096 key_event = GTKKeyEvent() 

2097 key_event.modifiers = self._client.mask_to_names(event.state) 

2098 key_event.keyname = keyname or "" 

2099 key_event.keyval = keyval or 0 

2100 key_event.keycode = keycode 

2101 key_event.group = event.group 

2102 try: 

2103 key_event.string = event.string or "" 

2104 except UnicodeDecodeError as e: 

2105 keylog("parse_key_event(%s, %s)", event, pressed, exc_info=True) 

2106 if first_time("key-%s-%s" % (keycode, keyname)): 

2107 keylog.warn("Warning: failed to parse string for key") 

2108 keylog.warn(" keyname=%s, keycode=%s", keyname, keycode) 

2109 keylog.warn(" %s", e) 

2110 key_event.string = "" 

2111 key_event.pressed = pressed 

2112 keyeventlog("parse_key_event(%s, %s)=%s", event, pressed, key_event) 

2113 return key_event 

2114 

2115 def do_key_press_event(self, event): 

2116 key_event = self.parse_key_event(event, True) 

2117 if self.moveresize_event and key_event.keyname in BREAK_MOVERESIZE: 

2118 #cancel move resize if there is one: 

2119 self.moveresize_event = None 

2120 self.cancel_moveresize_timer() 

2121 return self._client.handle_key_action(self, key_event) 

2122 

2123 def do_key_release_event(self, event): 

2124 key_event = self.parse_key_event(event, False) 

2125 return self._client.handle_key_action(self, key_event) 

2126 

2127 

2128 def _do_scroll_event(self, event): 

2129 if self._client.readonly: 

2130 return 

2131 button_mapping = GDK_SCROLL_MAP.get(event.direction, -1) 

2132 mouselog("do_scroll_event device=%s, direction=%s, button_mapping=%s", 

2133 self._device_info(event), event.direction, button_mapping) 

2134 if button_mapping>=0: 

2135 self._button_action(button_mapping, event, True) 

2136 self._button_action(button_mapping, event, False) 

2137 

2138 

2139 def update_icon(self, img): 

2140 self._current_icon = img 

2141 has_alpha = img.mode=="RGBA" 

2142 width, height = img.size 

2143 rowstride = width * (3+int(has_alpha)) 

2144 pixbuf = get_pixbuf_from_data(img.tobytes(), has_alpha, width, height, rowstride) 

2145 iconlog("%s.set_icon(%s)", self, pixbuf) 

2146 self.set_icon(pixbuf)