This repository has been archived by the owner on Nov 18, 2017. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
/
gtk_client_window_base.py
370 lines (323 loc) · 14.7 KB
/
gtk_client_window_base.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# This file is part of Xpra.
# Copyright (C) 2011 Serviware (Arthur Huillet, <ahuillet@serviware.com>)
# Copyright (C) 2010-2013 Antoine Martin <antoine@devloop.org.uk>
# Copyright (C) 2008, 2010 Nathaniel Smith <njs@pobox.com>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.
from xpra.client.client_window_base import ClientWindowBase
from xpra.util import AdHocStruct, nn
from xpra.gtk_common.gobject_compat import import_gtk, import_gdk
gtk = import_gtk()
gdk = import_gdk()
import os
import cairo
import time
import math
CAN_SET_WORKSPACE = False
HAS_X11_BINDINGS = False
if os.name=="posix":
try:
from xpra.x11.gtk_x11.prop import prop_get, prop_set
from xpra.x11.gtk_x11.gdk_bindings import get_xwindow
from xpra.x11.bindings.window_bindings import constants, X11WindowBindings #@UnresolvedImport
from xpra.x11.gtk_x11.error import trap
HAS_X11_BINDINGS = True
try:
#TODO: in theory this is not a proper check, meh - that will do
root = gtk.gdk.get_default_root_window()
supported = prop_get(root, "_NET_SUPPORTED", ["atom"], ignore_errors=True)
CAN_SET_WORKSPACE = bool(supported) and "_NET_WM_DESKTOP" in supported
except:
pass
except ImportError, e:
pass
#for faster handling of premultiplied argb:
try:
from xpra.codecs.argb.argb import unpremultiply_argb, byte_buffer_to_buffer #@UnresolvedImport
except:
unpremultiply_argb, byte_buffer_to_buffer = None, None
class GTKKeyEvent(AdHocStruct):
pass
class GTKClientWindowBase(ClientWindowBase, gtk.Window):
def init_window(self, metadata):
self._fullscreen = None
self._can_set_workspace = HAS_X11_BINDINGS and CAN_SET_WORKSPACE
ClientWindowBase.init_window(self, metadata)
def setup_window(self):
ClientWindowBase.setup_window(self)
self.set_app_paintable(True)
self.add_events(self.WINDOW_EVENT_MASK)
if self._override_redirect:
transient_for = self.get_transient_for()
type_hint = self.get_type_hint()
if transient_for is not None and transient_for.window is not None and type_hint in self.OR_TYPE_HINTS:
transient_for._override_redirect_windows.append(self)
if not self._override_redirect:
self.connect("notify::has-toplevel-focus", self._focus_change)
def focus_in(*args):
self.debug("focus-in-event for wid=%s", self._id)
def focus_out(*args):
self.debug("focus-out-event for wid=%s", self._id)
self.connect("focus-in-event", focus_in)
self.connect("focus-out-event", focus_out)
if self._can_set_workspace:
self.connect("property-notify-event", self.property_changed)
self.connect("window-state-event", self.window_state_updated)
self.move(*self._pos)
self.set_default_size(*self._size)
def show(self):
if self.group_leader:
if not self.is_realized():
self.realize()
self.window.set_group(self.group_leader)
gtk.Window.show(self)
def window_state_updated(self, widget, event):
self._fullscreen = bool(event.new_window_state & self.WINDOW_STATE_FULLSCREEN)
maximized = bool(event.new_window_state & self.WINDOW_STATE_MAXIMIZED)
self._client_properties["maximized"] = maximized
self.debug("window_state_updated(%s, %s) new_window_state=%s, fullscreen=%s, maximized=%s", widget, repr(event), event.new_window_state, self._fullscreen, maximized)
def set_fullscreen(self, fullscreen):
if self._fullscreen is None or self._fullscreen!=fullscreen:
#note: the "_fullscreen" flag is updated by the window-state-event, not here
self.debug("set_fullscreen(%s)", fullscreen)
if fullscreen:
self.fullscreen()
else:
self.unfullscreen()
def set_xid(self, xid):
if HAS_X11_BINDINGS and self.is_realized():
try:
if xid.startswith("0x") and xid.endswith("L"):
xid = xid[:-1]
iid = int(xid, 16)
self.xset_u32_property(self.gdk_window(), "XID", iid)
except Exception, e:
self.debug("set_xid(%s) error parsing/setting xid: %s", xid, e)
return
def xget_u32_property(self, target, name):
v = prop_get(target, name, "u32", ignore_errors=True)
self.debug("xget_u32_property(%s, %s)=%s", target, name, v)
if type(v)==int:
return v
return None
def xset_u32_property(self, target, name, value):
prop_set(target, name, "u32", value)
def is_realized(self):
if hasattr(self, "get_realized"):
#pygtk 2.22 and above have this method:
return self.get_realized()
#older versions:
return self.flags() & gtk.REALIZED
def property_changed(self, widget, event):
self.debug("property_changed: %s", event.atom)
if event.atom=="_NET_WM_DESKTOP" and self._been_mapped and not self._override_redirect:
#fake a configure event to send the new client_properties with
#the updated workspace number:
self.process_configure_event()
def do_set_workspace(self, workspace):
assert HAS_X11_BINDINGS
root = self.gdk_window().get_screen().get_root_window()
ndesktops = self.xget_u32_property(root, "_NET_NUMBER_OF_DESKTOPS")
self.debug("set_workspace() ndesktops=%s", ndesktops)
if ndesktops is None or ndesktops<=1:
return -1
workspace = max(0, min(ndesktops-1, workspace))
event_mask = constants["SubstructureNotifyMask"] | constants["SubstructureRedirectMask"]
def send():
root_window = get_xwindow(root)
window = get_xwindow(self.gdk_window())
X11WindowBindings.sendClientMessage(root_window, window, False, event_mask, "_NET_WM_DESKTOP",
workspace, constants["CurrentTime"],
0, 0, 0)
trap.call_synced(send)
return workspace
def get_current_workspace(self):
return -1
def get_window_workspace(self):
return -1
def apply_transient_for(self, wid):
if wid==-1:
#root is a gdk window, so we need to ensure we have one
#backing our gtk window to be able to call set_transient_for on it
self.debug("apply_transient_for(%s) gdkwindow=%s, mapped=%s", wid, self.gdk_window(), self.is_mapped())
if self.gdk_window() is None:
self.realize()
self.gdk_window().set_transient_for(gtk.gdk.get_default_root_window())
else:
#gtk window is easier:
window = self._client._id_to_window.get(wid)
self.debug("apply_transient_for(%s) window=%s", wid, window)
if window:
self.set_transient_for(window)
def update_icon(self, width, height, coding, data):
self.debug("update_icon(%s, %s, %s, %s bytes)", width, height, coding, len(data))
if coding == "premult_argb32":
if unpremultiply_argb is not None:
#we usually cannot do in-place and this is not performance critical
data = byte_buffer_to_buffer(unpremultiply_argb(data))
pixbuf = gdk.pixbuf_new_from_data(data, gtk.gdk.COLORSPACE_RGB, True, 8, width, height, width*4)
else:
# slower fallback: we round-trip through PNG.
# This is ridiculous, but faster than doing a bunch of alpha
# un-premultiplying and byte-swapping by hand in Python
cairo_surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
cairo_surf.get_data()[:] = data
loader = gdk.PixbufLoader()
cairo_surf.write_to_png(loader)
loader.close()
pixbuf = loader.get_pixbuf()
else:
loader = gdk.PixbufLoader(coding)
loader.write(data, len(data))
loader.close()
pixbuf = loader.get_pixbuf()
self.set_icon(pixbuf)
def paint_spinner(self, context, area):
self.debug("paint_spinner(%s, %s)", context, area)
#add grey semi-opaque layer on top:
context.set_operator(cairo.OPERATOR_OVER)
context.set_source_rgba(0.2, 0.2, 0.2, 0.8)
context.rectangle(area)
#w, h = self._size
#context.rectangle(gdk.Rectangle(0, 0, w, h))
context.fill()
#add spinner:
w, h = self.get_size()
dim = min(w/3.0, h/3.0, 100.0)
context.set_line_width(dim/10.0)
context.set_line_cap(cairo.LINE_CAP_ROUND)
context.translate(w/2, h/2)
from xpra.gtk_common.gtk_spinner import cv
count = int(time.time()*5.0)
for i in range(8): #8 lines
context.set_source_rgba(0, 0, 0, cv.trs[count%8][i])
context.move_to(0.0, -dim/4.0)
context.line_to(0.0, -dim)
context.rotate(math.pi/4)
context.stroke()
def spinner(self, ok):
if not self.can_have_spinner():
return
#with normal windows, we just queue a draw request
#and let the expose event paint the spinner
w, h = self.get_size()
self.queue_draw(0, 0, w, h)
def do_map_event(self, event):
self.debug("Got map event: %s - OR=%s", event, self._override_redirect)
gtk.Window.do_map_event(self, event)
self._been_mapped = True
xid = self._metadata.get("xid")
if xid:
self.set_xid(xid)
if not self._override_redirect:
x, y, w, h = self.get_window_geometry()
if not self._been_mapped:
workspace = self.set_workspace()
else:
#window has been mapped, so these attributes can be read (if present):
self._client_properties["screen"] = self.get_screen().get_number()
workspace = self.get_window_workspace()
if workspace<0:
workspace = self.get_current_workspace()
if workspace>=0:
self._client_properties["workspace"] = workspace
self.debug("map-window for wid=%s with client props=%s", self._id, self._client_properties)
self.send("map-window", self._id, x, y, w, h, self._client_properties)
self._pos = (x, y)
self._size = (w, h)
self.idle_add(self._focus_change, "initial")
def do_configure_event(self, event):
self.debug("Got configure event: %s", event)
gtk.Window.do_configure_event(self, event)
if not self._override_redirect:
self.process_configure_event()
def process_configure_event(self):
x, y, w, h = self.get_window_geometry()
w = max(1, w)
h = max(1, h)
ox, oy = self._pos
dx, dy = x-ox, y-oy
self._pos = (x, y)
if self._client.window_configure:
#if we support configure-window, send that first
if self._been_mapped:
#if the window has been mapped already, the workspace should be set:
self._client_properties["screen"] = self.get_screen().get_number()
workspace = self.get_window_workspace()
if workspace<0:
workspace = self.get_current_workspace()
if workspace>=0:
self._client_properties["workspace"] = workspace
self.debug("configure-window for wid=%s with client props=%s", self._id, self._client_properties)
self.send("configure-window", self._id, x, y, w, h, self._client_properties)
if dx!=0 or dy!=0:
#window has moved
if not self._client.window_configure:
#if we don't handle the move via configure:
self.send("move-window", self._id, x, y)
#move any OR window with their parent:
for window in self._override_redirect_windows:
x, y = window.get_position()
window.move(x+dx, y+dy)
if (w, h) != self._size:
self._size = (w, h)
self.new_backing(w, h)
if not self._client.window_configure:
self.send("resize-window", self._id, w, h)
def move_resize(self, x, y, w, h):
assert self._override_redirect
w = max(1, w)
h = max(1, h)
self.window.move_resize(x, y, w, h)
if (w, h) != self._size:
self._size = (w, h)
self.new_backing(w, h)
def destroy(self):
if self._refresh_timer:
self.source_remove(self._refresh_timer)
if self._backing:
self._backing.close()
self._backing = None
gtk.Window.destroy(self)
self._unfocus()
def do_unmap_event(self, event):
self._unfocus()
if not self._override_redirect:
self.send("unmap-window", self._id)
def do_delete_event(self, event):
self.send("close-window", self._id)
return True
def _pointer_modifiers(self, event):
pointer = (int(event.x_root), int(event.y_root))
modifiers = self._client.mask_to_names(event.state)
buttons = []
for mask, button in {gtk.gdk.BUTTON1_MASK : 1,
gtk.gdk.BUTTON2_MASK : 2,
gtk.gdk.BUTTON3_MASK : 3,
gtk.gdk.BUTTON4_MASK : 4,
gtk.gdk.BUTTON5_MASK : 5}.items():
if event.state & mask:
buttons.append(button)
return pointer, modifiers, buttons
def parse_key_event(self, event, pressed):
key_event = GTKKeyEvent()
key_event.modifiers = self._client.mask_to_names(event.state)
key_event.keyname = nn(gdk.keyval_name(event.keyval))
key_event.keyval = nn(event.keyval)
key_event.keycode = event.hardware_keycode
key_event.group = event.group
key_event.string = nn(event.string)
key_event.pressed = pressed
return key_event
def do_key_press_event(self, event):
key_event = self.parse_key_event(event, True)
self._client.handle_key_action(self, key_event)
def do_key_release_event(self, event):
key_event = self.parse_key_event(event, False)
self._client.handle_key_action(self, key_event)
def _focus_change(self, *args):
assert not self._override_redirect
htf = self.get_property("has-toplevel-focus")
self.debug("_focus_change(%s) wid=%s, has-toplevel-focus=%s, _been_mapped=%s", args, self._id, htf, self._been_mapped)
if self._been_mapped:
self._client.update_focus(self._id, htf)