1
|
|
|
"""Wrapper around libwnck for interacting with the window manager""" |
2
|
|
|
|
3
|
|
|
__author__ = "Stephan Sokolow (deitarion/SSokolow)" |
4
|
|
|
__license__ = "GNU GPL 2.0 or later" |
5
|
|
|
|
6
|
|
|
import logging |
7
|
|
|
|
8
|
|
|
import gtk.gdk, wnck # pylint: disable=import-error |
9
|
|
|
from gtk.gdk import Rectangle |
10
|
|
|
|
11
|
|
|
from .util import clamp_idx, EnumSafeDict, XInitError |
12
|
|
|
|
13
|
|
|
# Allow MyPy to work without depending on the `typing` package |
14
|
|
|
# (And silence complaints from only using the imported types in comments) |
15
|
|
|
try: |
16
|
|
|
# pylint: disable=unused-import |
17
|
|
|
from typing import List, Optional, Sequence, Tuple # NOQA |
18
|
|
|
from .util import Strut # NOQA |
19
|
|
|
except: # pylint: disable=bare-except |
20
|
|
|
pass |
|
|
|
|
21
|
|
|
|
22
|
|
|
#: Lookup table for internal window gravity support. |
23
|
|
|
#: (libwnck's support is either unreliable or broken) |
24
|
|
|
GRAVITY = EnumSafeDict({ |
25
|
|
|
'NORTH_WEST': (0.0, 0.0), |
26
|
|
|
'NORTH': (0.5, 0.0), |
27
|
|
|
'NORTH_EAST': (1.0, 0.0), |
28
|
|
|
'WEST': (0.0, 0.5), |
29
|
|
|
'CENTER': (0.5, 0.5), |
30
|
|
|
'EAST': (1.0, 0.5), |
31
|
|
|
'SOUTH_WEST': (0.0, 1.0), |
32
|
|
|
'SOUTH': (0.5, 1.0), |
33
|
|
|
'SOUTH_EAST': (1.0, 1.0), |
34
|
|
|
}) |
35
|
|
|
key, val = None, None # Safety cushion for the "del" line. |
|
|
|
|
36
|
|
|
for key, val in GRAVITY.items(): |
|
|
|
|
37
|
|
|
# Support GDK gravity constants |
38
|
|
|
GRAVITY[getattr(gtk.gdk, 'GRAVITY_%s' % key)] = val |
39
|
|
|
|
40
|
|
|
# Support libwnck gravity constants |
41
|
|
|
_name = 'WINDOW_GRAVITY_%s' % key.replace('_', '') |
42
|
|
|
GRAVITY[getattr(wnck, _name)] = val |
43
|
|
|
|
44
|
|
|
# Prevent these temporary variables from showing up in the apidocs |
45
|
|
|
del _name, key, val |
46
|
|
|
|
47
|
|
|
# --- |
48
|
|
|
|
49
|
|
|
class WorkArea(object): |
50
|
|
|
"""Helper to calculate and query available workarea on the desktop.""" |
51
|
|
|
def __init__(self, gdk_screen, ignore_struts=False): |
52
|
|
|
# type: (gtk.gdk.Screen, bool) -> None |
53
|
|
|
self.gdk_screen = gdk_screen |
54
|
|
|
self.ignore_struts = ignore_struts |
55
|
|
|
|
56
|
|
|
def get_struts(self, root_win): # type: (gtk.gdk.Window) -> List[Strut] |
57
|
|
|
"""Retrieve the struts from the root window if supported.""" |
58
|
|
|
if not self.gdk_screen.supports_net_wm_hint("_NET_WM_STRUT_PARTIAL"): |
59
|
|
|
return [] |
60
|
|
|
|
61
|
|
|
# Gather all struts |
62
|
|
|
struts = [root_win.property_get("_NET_WM_STRUT_PARTIAL")] |
63
|
|
|
if self.gdk_screen.supports_net_wm_hint("_NET_CLIENT_LIST"): |
64
|
|
|
# Source: http://stackoverflow.com/a/11332614/435253 |
65
|
|
|
for wid in root_win.property_get('_NET_CLIENT_LIST')[2]: |
66
|
|
|
w = gtk.gdk.window_foreign_new(wid) |
|
|
|
|
67
|
|
|
struts.append(w.property_get("_NET_WM_STRUT_PARTIAL")) |
68
|
|
|
struts = [tuple(x[2]) for x in struts if x] |
69
|
|
|
|
70
|
|
|
logging.debug("Gathered _NET_WM_STRUT_PARTIAL values:\n\t%s", |
71
|
|
|
struts) |
72
|
|
|
return struts |
73
|
|
|
|
74
|
|
|
def subtract_struts(self, usable_region, # type: gtk.gdk.Region |
75
|
|
|
struts # type: Sequence[Strut] |
76
|
|
|
): # type: (...) -> Tuple[gtk.gdk.Region, Rectangle] |
77
|
|
|
"""Subtract the given struts from the given region.""" |
78
|
|
|
|
79
|
|
|
# Subtract the struts from the usable region |
80
|
|
|
_Sub = lambda *g: usable_region.subtract( |
|
|
|
|
81
|
|
|
gtk.gdk.region_rectangle(g)) |
82
|
|
|
_w, _h = self.gdk_screen.get_width(), self.gdk_screen.get_height() |
|
|
|
|
83
|
|
|
for g in struts: # pylint: disable=invalid-name |
84
|
|
|
# http://standards.freedesktop.org/wm-spec/1.5/ar01s05.html |
85
|
|
|
# XXX: Must not cache unless watching for notify events. |
86
|
|
|
_Sub(0, g[4], g[0], g[5] - g[4] + 1) # left |
87
|
|
|
_Sub(_w - g[1], g[6], g[1], g[7] - g[6] + 1) # right |
88
|
|
|
_Sub(g[8], 0, g[9] - g[8] + 1, g[2]) # top |
89
|
|
|
_Sub(g[10], _h - g[3], g[11] - g[10] + 1, g[3]) # bottom |
90
|
|
|
|
91
|
|
|
# Generate a more restrictive version used as a fallback |
92
|
|
|
usable_rect = usable_region.copy() |
93
|
|
|
_Sub = lambda *g: usable_rect.subtract(gtk.gdk.region_rectangle(g)) |
|
|
|
|
94
|
|
|
for geom in struts: |
95
|
|
|
# http://standards.freedesktop.org/wm-spec/1.5/ar01s05.html |
96
|
|
|
# XXX: Must not cache unless watching for notify events. |
97
|
|
|
_Sub(0, geom[4], geom[0], _h) # left |
98
|
|
|
_Sub(_w - geom[1], geom[6], geom[1], _h) # right |
99
|
|
|
_Sub(0, 0, _w, geom[2]) # top |
100
|
|
|
_Sub(0, _h - geom[3], _w, geom[3]) # bottom |
101
|
|
|
# TODO: The required "+ 1" in certain spots confirms that we're |
102
|
|
|
# going to need unit tests which actually check that the |
103
|
|
|
# WM's code for constraining windows to the usable area |
104
|
|
|
# doesn't cause off-by-one bugs. |
105
|
|
|
# TODO: Share this on http://stackoverflow.com/q/2598580/435253 |
106
|
|
|
return usable_rect.get_clipbox(), usable_region |
107
|
|
|
|
108
|
|
|
def get(self, monitor, ignore_struts=None): |
109
|
|
|
# type: (Rectangle, bool) -> Tuple[gtk.gdk.Region, Rectangle] |
110
|
|
|
"""Retrieve the usable area of the specified monitor using |
111
|
|
|
the most expressive method the window manager supports. |
112
|
|
|
|
113
|
|
|
@param monitor: The number or dimensions of the desired monitor. |
114
|
|
|
@param ignore_struts: If C{True}, just return the size of the whole |
115
|
|
|
monitor, allowing windows to overlap panels. |
116
|
|
|
|
117
|
|
|
@type monitor: C{gtk.gdk.Rectangle} |
118
|
|
|
@type ignore_struts: C{bool} |
119
|
|
|
|
120
|
|
|
@returns: The usable region and its largest rectangular subset. |
121
|
|
|
@rtype: C{gtk.gdk.Region}, C{gtk.gdk.Rectangle} |
122
|
|
|
""" |
123
|
|
|
|
124
|
|
|
# Get the region and return failure early if it's empty |
125
|
|
|
usable_rect, usable_region = monitor, gtk.gdk.region_rectangle(monitor) |
126
|
|
|
if not usable_region.get_rectangles(): |
127
|
|
|
logging.error("WorkArea.get_monitor_rect received " |
128
|
|
|
"an empty monitor region!") |
129
|
|
|
return None, None |
130
|
|
|
|
131
|
|
|
# Return early if asked to ignore struts |
132
|
|
|
if ignore_struts or (ignore_struts is None and self.ignore_struts): |
133
|
|
|
logging.debug("Panels ignored. Reported monitor geometry is:\n%s", |
134
|
|
|
usable_rect) |
135
|
|
|
return usable_region, usable_rect |
136
|
|
|
|
137
|
|
|
# Get the list of struts from the root window |
138
|
|
|
root_win = self.gdk_screen.get_root_window() |
139
|
|
|
struts = self.get_struts(root_win) |
140
|
|
|
|
141
|
|
|
# Fall back to _NET_WORKAREA if we couldn't get any struts |
142
|
|
|
if struts: |
143
|
|
|
usable_rect, usable_region = self.subtract_struts(usable_region, |
144
|
|
|
struts) |
145
|
|
|
elif self.gdk_screen.supports_net_wm_hint("_NET_WORKAREA"): |
146
|
|
|
desktop_geo = tuple(root_win.property_get('_NET_WORKAREA')[2][0:4]) |
147
|
|
|
logging.debug("Falling back to _NET_WORKAREA: %s", desktop_geo) |
148
|
|
|
usable_region.intersect(gtk.gdk.region_rectangle(desktop_geo)) |
149
|
|
|
usable_rect = usable_region.get_clipbox() |
150
|
|
|
|
151
|
|
|
# FIXME: Only call get_rectangles if --debug |
152
|
|
|
logging.debug("Usable region of monitor calculated as:\n" |
153
|
|
|
"\tRegion: %r\n\tRectangle: %r", |
154
|
|
|
usable_region.get_rectangles(), usable_rect) |
155
|
|
|
return usable_region, usable_rect |
156
|
|
|
|
157
|
|
|
|
158
|
|
|
class WindowManager(object): |
159
|
|
|
"""A simple API-wrapper class for manipulating window positioning.""" |
160
|
|
|
|
161
|
|
|
def __init__(self, screen=None, ignore_workarea=False): |
162
|
|
|
# type: (gtk.gdk.Screen, bool) -> None |
163
|
|
|
""" |
164
|
|
|
Initializes C{WindowManager}. |
165
|
|
|
|
166
|
|
|
@param screen: The X11 screen to operate on. If C{None}, the default |
167
|
|
|
screen as retrieved by C{gtk.gdk.screen_get_default} will be used. |
168
|
|
|
@type screen: C{gtk.gdk.Screen} |
169
|
|
|
|
170
|
|
|
@todo: Confirm that the root window only changes on X11 server |
171
|
|
|
restart. (Something which will crash QuickTile anyway since |
172
|
|
|
PyGTK makes X server disconnects uncatchable.) |
173
|
|
|
|
174
|
|
|
It could possibly change while toggling "allow desktop icons" |
175
|
|
|
in KDE 3.x. (Not sure what would be equivalent elsewhere) |
176
|
|
|
""" |
177
|
|
|
self.gdk_screen = screen or gtk.gdk.screen_get_default() |
178
|
|
|
if self.gdk_screen is None: |
179
|
|
|
raise XInitError("GTK+ could not open a connection to the X server" |
180
|
|
|
" (bad DISPLAY value?)") |
181
|
|
|
|
182
|
|
|
# pylint: disable=no-member |
183
|
|
|
self.screen = wnck.screen_get(self.gdk_screen.get_number()) |
184
|
|
|
self.workarea = WorkArea(self.gdk_screen, |
185
|
|
|
ignore_struts=ignore_workarea) |
186
|
|
|
|
187
|
|
|
@staticmethod |
188
|
|
|
def calc_win_gravity(geom, gravity): |
189
|
|
|
# (Rectangle, Tuple[float, float]) -> Tuple[int, int] |
190
|
|
|
"""Calculate the X and Y coordinates necessary to simulate non-topleft |
191
|
|
|
gravity on a window. |
192
|
|
|
|
193
|
|
|
@param geom: The window geometry to which to apply the corrections. |
194
|
|
|
@param gravity: A desired gravity chosen from L{GRAVITY}. |
195
|
|
|
@type geom: C{gtk.gdk.Rectangle} |
196
|
|
|
@type gravity: C{wnck.WINDOW_GRAVITY_*} or C{gtk.gdk.GRAVITY_*} |
197
|
|
|
|
198
|
|
|
@returns: The coordinates to be used to achieve the desired position. |
199
|
|
|
@rtype: C{(x, y)} |
200
|
|
|
""" |
201
|
|
|
grav_x, grav_y = GRAVITY[gravity] |
202
|
|
|
|
203
|
|
|
return ( |
204
|
|
|
int(geom.x - (geom.width * grav_x)), |
205
|
|
|
int(geom.y - (geom.height * grav_y)) |
206
|
|
|
) |
207
|
|
|
|
208
|
|
|
@staticmethod |
209
|
|
|
def get_geometry_rel(window, monitor_geom): |
210
|
|
|
# type: (wnck.Window, Rectangle) -> Rectangle |
211
|
|
|
"""Get window position relative to the monitor rather than the desktop. |
212
|
|
|
|
213
|
|
|
@param monitor_geom: The rectangle returned by |
214
|
|
|
C{gdk.Screen.get_monitor_geometry} |
215
|
|
|
@type window: C{wnck.Window} |
216
|
|
|
@type monitor_geom: C{gtk.gdk.Rectangle} |
217
|
|
|
|
218
|
|
|
@rtype: C{gtk.gdk.Rectangle} |
219
|
|
|
""" |
220
|
|
|
win_geom = Rectangle(*window.get_geometry()) |
221
|
|
|
win_geom.x -= monitor_geom.x |
222
|
|
|
win_geom.y -= monitor_geom.y |
223
|
|
|
|
224
|
|
|
return win_geom |
225
|
|
|
|
226
|
|
|
def get_monitor(self, win): |
227
|
|
|
# type: (wnck.Window) -> Tuple[int, Rectangle] |
228
|
|
|
"""Given a C{wnck.Window}, retrieve the monitor ID and geometry. |
229
|
|
|
|
230
|
|
|
@type win: C{wnck.Window} |
231
|
|
|
@returns: A tuple containing the monitor ID and geometry. |
232
|
|
|
@rtype: C{(int, gtk.gdk.Rectangle)} |
233
|
|
|
""" |
234
|
|
|
# TODO: Look for a way to get the monitor ID without having |
235
|
|
|
# to instantiate a gtk.gdk.Window |
236
|
|
|
if not isinstance(win, gtk.gdk.Window): |
237
|
|
|
win = gtk.gdk.window_foreign_new(win.get_xid()) |
238
|
|
|
|
239
|
|
|
# TODO: How do I retrieve the root window from a given one? |
240
|
|
|
monitor_id = self.gdk_screen.get_monitor_at_window(win) |
241
|
|
|
monitor_geom = self.gdk_screen.get_monitor_geometry(monitor_id) |
242
|
|
|
|
243
|
|
|
logging.debug(" Window is on monitor %s, which has geometry %s", |
244
|
|
|
monitor_id, monitor_geom) |
245
|
|
|
return monitor_id, monitor_geom |
246
|
|
|
|
247
|
|
|
def get_relevant_windows(self, workspace): |
248
|
|
|
"""C{wnck.Screen.get_windows} without WINDOW_DESKTOP/DOCK windows.""" |
249
|
|
|
|
250
|
|
|
for window in self.screen.get_windows(): |
251
|
|
|
# Skip windows on other virtual desktops for intuitiveness |
252
|
|
|
if workspace and not window.is_on_workspace(workspace): |
253
|
|
|
logging.debug("Skipping window on other workspace: %r", window) |
254
|
|
|
continue |
255
|
|
|
|
256
|
|
|
# Don't cycle elements of the desktop |
257
|
|
|
if not self.is_relevant(window): |
258
|
|
|
continue |
259
|
|
|
|
260
|
|
|
yield window |
261
|
|
|
|
262
|
|
|
def get_workspace(self, |
263
|
|
|
window=None, # type: wnck.Window |
264
|
|
|
direction=None, # type: wnck.MotionDirection |
265
|
|
|
wrap_around=True # type: bool |
266
|
|
|
): # type: (...) -> wnck.Workspace |
267
|
|
|
"""Get a workspace relative to either a window or the active one. |
268
|
|
|
|
269
|
|
|
@param window: The point of reference. C{None} for the active workspace |
270
|
|
|
@param direction: The direction in which to look, relative to the point |
271
|
|
|
of reference. Accepts the following types: |
272
|
|
|
- C{wnck.MotionDirection}: Non-cycling direction |
273
|
|
|
- C{int}: Relative index in the list of workspaces |
274
|
|
|
- C{None}: Just get the workspace object for the point of |
275
|
|
|
reference |
276
|
|
|
@param wrap_around: Whether relative indexes should wrap around. |
277
|
|
|
|
278
|
|
|
@type window: C{wnck.Window} or C{None} |
279
|
|
|
@type wrap_around: C{bool} |
280
|
|
|
@rtype: C{wnck.Workspace} or C{None} |
281
|
|
|
@returns: The workspace object or C{None} if no match could be found. |
282
|
|
|
""" |
283
|
|
|
if window: |
284
|
|
|
cur = window.get_workspace() |
285
|
|
|
else: |
286
|
|
|
cur = self.screen.get_active_workspace() |
287
|
|
|
|
288
|
|
|
if not cur: |
289
|
|
|
return None # It's either pinned or on no workspaces |
290
|
|
|
|
291
|
|
|
# pylint: disable=no-member |
292
|
|
|
if isinstance(direction, wnck.MotionDirection): |
293
|
|
|
nxt = cur.get_neighbor(direction) |
294
|
|
|
elif isinstance(direction, int): |
295
|
|
|
# TODO: Deduplicate with the wrapping code in commands.py |
296
|
|
|
n_spaces = self.screen.get_workspace_count() |
297
|
|
|
|
298
|
|
|
nxt = self.screen.get_workspace( |
299
|
|
|
clamp_idx(cur.get_number() + direction, n_spaces, wrap_around)) |
300
|
|
|
|
301
|
|
|
elif direction is None: |
302
|
|
|
nxt = cur |
303
|
|
|
else: |
304
|
|
|
nxt = None |
305
|
|
|
logging.warn("Unrecognized direction: %r", direction) |
306
|
|
|
|
307
|
|
|
return nxt |
308
|
|
|
|
309
|
|
|
def is_relevant(self, window): |
|
|
|
|
310
|
|
|
# type: (wnck.Window) -> bool |
311
|
|
|
"""Return False if the window should be ignored. |
312
|
|
|
|
313
|
|
|
(eg. If it's the desktop or a panel) |
314
|
|
|
""" |
315
|
|
|
if not window: |
316
|
|
|
logging.debug("Received no window object to manipulate") |
317
|
|
|
return False |
318
|
|
|
|
319
|
|
|
if window.get_window_type() in [ |
320
|
|
|
wnck.WINDOW_DESKTOP, # pylint: disable=E1101 |
321
|
|
|
wnck.WINDOW_DOCK]: # pylint: disable=E1101 |
322
|
|
|
logging.debug("Irrelevant window: %r", window) |
323
|
|
|
return False |
324
|
|
|
return True |
325
|
|
|
|
326
|
|
|
def reposition(self, |
327
|
|
|
win, # type: wnck.Window |
328
|
|
|
geom=None, # type: Optional[Rectangle] |
329
|
|
|
monitor=Rectangle(0, 0, 0, 0), # type: Rectangle |
330
|
|
|
keep_maximize=False, # type: bool |
331
|
|
|
gravity=wnck.WINDOW_GRAVITY_NORTHWEST, |
332
|
|
|
geometry_mask=wnck.WINDOW_CHANGE_X | wnck.WINDOW_CHANGE_Y | |
333
|
|
|
wnck.WINDOW_CHANGE_WIDTH | |
334
|
|
|
wnck.WINDOW_CHANGE_HEIGHT # type: wnck.WindowMoveResizeMask |
335
|
|
|
): # pylint: disable=no-member,too-many-arguments |
336
|
|
|
# type: (...) -> None |
337
|
|
|
# TODO: Complete MyPy type signature |
338
|
|
|
# pylint:disable=line-too-long |
339
|
|
|
""" |
340
|
|
|
Position and size a window, decorations inclusive, according to the |
341
|
|
|
provided target window and monitor geometry rectangles. |
342
|
|
|
|
343
|
|
|
If no monitor rectangle is specified, position relative to the desktop |
344
|
|
|
as a whole. |
345
|
|
|
|
346
|
|
|
@param win: The C{wnck.Window} to operate on. |
347
|
|
|
@param geom: The new geometry for the window. Can be left unspecified |
348
|
|
|
if the intent is to move the window to another monitor without |
349
|
|
|
repositioning it. |
350
|
|
|
@param monitor: The frame relative to which C{geom} should be |
351
|
|
|
interpreted. The whole desktop if unspecified. |
352
|
|
|
@param keep_maximize: Whether to re-maximize a maximized window after |
353
|
|
|
un-maximizing it to move it. |
354
|
|
|
@param gravity: A constant specifying which point on the window is |
355
|
|
|
referred to by the X and Y coordinates in C{geom}. |
356
|
|
|
@param geometry_mask: A set of flags determining which aspects of the |
357
|
|
|
requested geometry should actually be applied to the window. |
358
|
|
|
(Allows the same geometry definition to easily be shared between |
359
|
|
|
operations like move and resize.) |
360
|
|
|
@type win: C{wnck.Window} |
361
|
|
|
@type geom: C{gtk.gdk.Rectangle} or C{None} |
362
|
|
|
@type monitor: C{gtk.gdk.Rectangle} |
363
|
|
|
@type keep_maximize: C{bool} |
364
|
|
|
@type gravity: U{WnckWindowGravity<https://developer.gnome.org/libwnck/stable/WnckWindow.html#WnckWindowGravity>} or U{GDK Gravity Constant<http://www.pygtk.org/docs/pygtk/gdk-constants.html#gdk-gravity-constants>} |
365
|
|
|
@type geometry_mask: U{WnckWindowMoveResizeMask<https://developer.gnome.org/libwnck/2.30/WnckWindow.html#WnckWindowMoveResizeMask>} |
366
|
|
|
|
367
|
|
|
@todo 1.0.0: Look for a way to accomplish this with a cleaner method |
368
|
|
|
signature. This is getting a little hairy. (API-breaking change) |
369
|
|
|
""" # NOQA |
370
|
|
|
|
371
|
|
|
# We need to ensure that ignored values are still present for |
372
|
|
|
# gravity calculations. |
373
|
|
|
old_geom = self.get_geometry_rel(win, self.get_monitor(win)[1]) |
374
|
|
|
if geom: |
375
|
|
|
for attr in ('x', 'y', 'width', 'height'): |
376
|
|
|
if not geometry_mask & getattr(wnck, |
377
|
|
|
'WINDOW_CHANGE_%s' % attr.upper()): |
378
|
|
|
setattr(geom, attr, getattr(old_geom, attr)) |
379
|
|
|
else: |
380
|
|
|
geom = old_geom |
381
|
|
|
|
382
|
|
|
# Unmaximize and record the types we may need to restore |
383
|
|
|
max_types, maxed = ['', '_horizontally', '_vertically'], [] |
384
|
|
|
for maxtype in max_types: |
385
|
|
|
if getattr(win, 'is_maximized' + maxtype)(): |
386
|
|
|
maxed.append(maxtype) |
387
|
|
|
getattr(win, 'unmaximize' + maxtype)() |
388
|
|
|
|
389
|
|
|
# Apply gravity and resolve to absolute desktop coordinates. |
390
|
|
|
new_x, new_y = self.calc_win_gravity(geom, gravity) |
391
|
|
|
new_x += monitor.x |
392
|
|
|
new_y += monitor.y |
393
|
|
|
|
394
|
|
|
logging.debug(" Repositioning to (%d, %d, %d, %d)\n", |
395
|
|
|
new_x, new_y, geom.width, geom.height) |
396
|
|
|
|
397
|
|
|
# XXX: I'm not sure whether wnck, Openbox, or both are at fault, |
398
|
|
|
# but window gravities seem to have no effect beyond double- |
399
|
|
|
# compensating for window border thickness unless using |
400
|
|
|
# WINDOW_GRAVITY_STATIC. |
401
|
|
|
# |
402
|
|
|
# My best guess is that the gravity modifiers are being applied |
403
|
|
|
# to the window frame rather than the window itself, hence why |
404
|
|
|
# static gravity would position correctly and north-west gravity |
405
|
|
|
# would double-compensate for the titlebar and border dimensions. |
406
|
|
|
# |
407
|
|
|
# ...however, that still doesn't explain why the non-topleft |
408
|
|
|
# gravities have no effect. I'm guessing something's just broken. |
409
|
|
|
win.set_geometry(wnck.WINDOW_GRAVITY_STATIC, geometry_mask, |
410
|
|
|
new_x, new_y, geom.width, geom.height) |
411
|
|
|
|
412
|
|
|
# Restore maximization if asked |
413
|
|
|
if maxed and keep_maximize: |
414
|
|
|
for maxtype in maxed: |
415
|
|
|
getattr(win, 'maximize' + maxtype)() |
416
|
|
|
|
Except handlers which only contain
pass
and do not have anelse
clause can usually simply be removed: