|
1
|
|
|
"""Available window-management commands""" |
|
2
|
|
|
|
|
3
|
|
|
__author__ = "Stephan Sokolow (deitarion/SSokolow)" |
|
4
|
|
|
__license__ = "GNU GPL 2.0 or later" |
|
5
|
|
|
|
|
6
|
|
|
import logging, time |
|
7
|
|
|
from functools import wraps |
|
8
|
|
|
from heapq import heappop, heappush |
|
9
|
|
|
|
|
10
|
|
|
import gtk.gdk, wnck # pylint: disable=import-error |
|
11
|
|
|
|
|
12
|
|
|
from .wm import GRAVITY |
|
13
|
|
|
from .util import clamp_idx, fmt_table |
|
14
|
|
|
|
|
15
|
|
|
# Allow MyPy to work without depending on the `typing` package |
|
16
|
|
|
# (And silence complaints from only using the imported types in comments) |
|
17
|
|
|
try: |
|
18
|
|
|
# pylint: disable=unused-import |
|
19
|
|
|
from typing import (Any, Callable, Dict, Iterable, Iterator, List, # NOQA |
|
20
|
|
|
Optional, Sequence, Tuple, TYPE_CHECKING) |
|
21
|
|
|
from mypy_extensions import VarArg, KwArg # NOQA |
|
22
|
|
|
|
|
23
|
|
|
if TYPE_CHECKING: |
|
24
|
|
|
from .wm import WindowManager # NOQA |
|
25
|
|
|
from .util import CommandCB |
|
26
|
|
|
|
|
27
|
|
|
# FIXME: Replace */** with a dict so I can be strict here |
|
28
|
|
|
CommandCBWrapper = Callable[..., Any] |
|
|
|
|
|
|
29
|
|
|
except: # pylint: disable=bare-except |
|
30
|
|
|
pass |
|
|
|
|
|
|
31
|
|
|
|
|
32
|
|
|
class CommandRegistry(object): |
|
33
|
|
|
"""Handles lookup and boilerplate for window management commands. |
|
34
|
|
|
|
|
35
|
|
|
Separated from WindowManager so its lifecycle is not tied to a specific |
|
36
|
|
|
GDK Screen object. |
|
37
|
|
|
""" |
|
38
|
|
|
|
|
39
|
|
|
extra_state = {} # type: Dict[str, Any] |
|
40
|
|
|
|
|
41
|
|
|
def __init__(self): # type: () -> None |
|
42
|
|
|
self.commands = {} # type: Dict[str, CommandCBWrapper] |
|
43
|
|
|
self.help = {} # type: Dict[str, str] |
|
44
|
|
|
|
|
45
|
|
|
def __iter__(self): # type: () -> Iterator[str] |
|
46
|
|
|
for x in self.commands: |
|
|
|
|
|
|
47
|
|
|
yield x |
|
48
|
|
|
|
|
49
|
|
|
def __str__(self): # type: () -> str |
|
50
|
|
|
return fmt_table(self.help, ('Known Commands', 'desc'), group_by=1) |
|
51
|
|
|
|
|
52
|
|
|
def add(self, name, *p_args, **p_kwargs): |
|
53
|
|
|
# type: (str, *Any, **Any) -> Callable[[CommandCB], CommandCB] |
|
54
|
|
|
# TODO: Rethink the return value of the command function. |
|
55
|
|
|
"""Decorator to wrap a function in boilerplate and add it to the |
|
56
|
|
|
command registry under the given name. |
|
57
|
|
|
|
|
58
|
|
|
@param name: The name to know the command by. |
|
59
|
|
|
@param p_args: Positional arguments to prepend to all calls made |
|
60
|
|
|
via C{name}. |
|
61
|
|
|
@param p_kwargs: Keyword arguments to prepend to all calls made |
|
62
|
|
|
via C{name}. |
|
63
|
|
|
|
|
64
|
|
|
@type name: C{str} |
|
65
|
|
|
""" |
|
66
|
|
|
|
|
67
|
|
|
def decorate(func): # type: (CommandCB) -> CommandCB |
|
68
|
|
|
"""Closure used to allow decorator to take arguments""" |
|
69
|
|
|
@wraps(func) |
|
70
|
|
|
# pylint: disable=missing-docstring |
|
71
|
|
|
def wrapper(winman, window=None, *args, **kwargs): |
|
72
|
|
|
# TODO: Add a MyPy type signature |
|
73
|
|
|
|
|
74
|
|
|
# Get Wnck and GDK window objects |
|
75
|
|
|
window = window or winman.screen.get_active_window() |
|
76
|
|
|
if isinstance(window, gtk.gdk.Window): |
|
77
|
|
|
win = wnck.window_get(window.xid) # pylint: disable=E1101 |
|
78
|
|
|
else: |
|
79
|
|
|
win = window |
|
80
|
|
|
|
|
81
|
|
|
# pylint: disable=no-member |
|
82
|
|
|
if not win: |
|
83
|
|
|
logging.debug("Received no window object to manipulate.") |
|
84
|
|
|
return None |
|
85
|
|
|
elif win.get_window_type() == wnck.WINDOW_DESKTOP: |
|
86
|
|
|
logging.debug("Received desktop window object. Ignoring.") |
|
87
|
|
|
return None |
|
88
|
|
|
else: |
|
89
|
|
|
# FIXME: Make calls to win.get_* lazy in case --debug |
|
90
|
|
|
# wasn't passed. |
|
91
|
|
|
logging.debug("Operating on window 0x%x with title \"%s\" " |
|
92
|
|
|
"and geometry %r", |
|
93
|
|
|
win.get_xid(), win.get_name(), |
|
94
|
|
|
win.get_geometry()) |
|
95
|
|
|
|
|
96
|
|
|
monitor_id, monitor_geom = winman.get_monitor(window) |
|
97
|
|
|
|
|
98
|
|
|
use_area, use_rect = winman.workarea.get(monitor_geom) |
|
99
|
|
|
|
|
100
|
|
|
# TODO: Replace this MPlayer safety hack with a properly |
|
101
|
|
|
# comprehensive exception catcher. |
|
102
|
|
|
if not use_rect: |
|
103
|
|
|
logging.debug("Received a worthless value for largest " |
|
104
|
|
|
"rectangular subset of desktop (%r). Doing " |
|
105
|
|
|
"nothing.", use_rect) |
|
106
|
|
|
return None |
|
107
|
|
|
|
|
108
|
|
|
state = {} |
|
109
|
|
|
state.update(self.extra_state) |
|
110
|
|
|
state.update({ |
|
111
|
|
|
"cmd_name": name, |
|
112
|
|
|
"monitor_id": monitor_id, |
|
113
|
|
|
"monitor_geom": monitor_geom, |
|
114
|
|
|
"usable_region": use_area, |
|
115
|
|
|
"usable_rect": use_rect, |
|
116
|
|
|
}) |
|
117
|
|
|
|
|
118
|
|
|
args, kwargs = p_args + args, dict(p_kwargs, **kwargs) |
|
119
|
|
|
func(winman, win, state, *args, **kwargs) |
|
120
|
|
|
|
|
121
|
|
|
if name in self.commands: |
|
122
|
|
|
logging.warn("Redefining existing command: %s", name) |
|
123
|
|
|
self.commands[name] = wrapper |
|
124
|
|
|
|
|
125
|
|
|
assert func.__doc__, ("Command must have a docstring: %r" % func) |
|
126
|
|
|
help_str = func.__doc__.strip().split('\n')[0].split('. ')[0] |
|
127
|
|
|
self.help[name] = help_str.strip('.') |
|
128
|
|
|
|
|
129
|
|
|
# Return the unwrapped function so decorators can be stacked |
|
130
|
|
|
# to define multiple commands using the same code with different |
|
131
|
|
|
# arguments |
|
132
|
|
|
return func |
|
133
|
|
|
return decorate |
|
134
|
|
|
|
|
135
|
|
|
def add_many(self, command_map): |
|
136
|
|
|
# type: (Dict[str, List[Any]]) -> Callable[[CommandCB], CommandCB] |
|
137
|
|
|
# TODO: Make this type signature more strict |
|
138
|
|
|
"""Convenience decorator to allow many commands to be defined from |
|
139
|
|
|
the same function with different arguments. |
|
140
|
|
|
|
|
141
|
|
|
@param command_map: A dict mapping command names to argument lists. |
|
142
|
|
|
@type command_map: C{dict} |
|
143
|
|
|
""" |
|
144
|
|
|
# TODO: Refactor and redesign for better maintainability |
|
145
|
|
|
def decorate(func): |
|
146
|
|
|
"""Closure used to allow decorator to take arguments""" |
|
147
|
|
|
for cmd, arglist in command_map.items(): |
|
148
|
|
|
self.add(cmd, *arglist)(func) |
|
149
|
|
|
return func |
|
150
|
|
|
return decorate |
|
151
|
|
|
|
|
152
|
|
|
def call(self, command, winman, *args, **kwargs): |
|
153
|
|
|
# type: (str, WindowManager, *Any, **Any) -> Any |
|
154
|
|
|
# TODO: Decide what to do about return values |
|
155
|
|
|
"""Resolve a textual positioning command and execute it.""" |
|
156
|
|
|
cmd = self.commands.get(command, None) |
|
157
|
|
|
|
|
158
|
|
|
if cmd: |
|
159
|
|
|
logging.debug("Executing command '%s' with arguments %r, %r", |
|
160
|
|
|
command, args, kwargs) |
|
161
|
|
|
cmd(winman, *args, **kwargs) |
|
162
|
|
|
else: |
|
163
|
|
|
logging.error("Unrecognized command: %s", command) |
|
164
|
|
|
|
|
165
|
|
|
|
|
166
|
|
|
#: The instance of L{CommandRegistry} to be used in 99.9% of use cases. |
|
167
|
|
|
commands = CommandRegistry() |
|
|
|
|
|
|
168
|
|
|
|
|
169
|
|
|
def cycle_dimensions(winman, # type: WindowManager |
|
170
|
|
|
win, # type: Any # TODO: Consistent Window type |
|
171
|
|
|
state, # type: Dict[str, Any] |
|
172
|
|
|
*dimensions # type: Any |
|
173
|
|
|
): # type: (...) -> Optional[gtk.gdk.Rectangle] |
|
174
|
|
|
# type: (WindowManager, Any, Dict[str, Any], *Tuple[...]) -> |
|
175
|
|
|
# TODO: Standardize on what kind of window object to pass around |
|
176
|
|
|
"""Cycle the active window through a list of positions and shapes. |
|
177
|
|
|
|
|
178
|
|
|
Takes one step each time this function is called. |
|
179
|
|
|
|
|
180
|
|
|
If the window's dimensions are not within 100px (by euclidean distance) |
|
181
|
|
|
of an entry in the list, set them to the first list entry. |
|
182
|
|
|
|
|
183
|
|
|
@param dimensions: A list of tuples representing window geometries as |
|
184
|
|
|
floating-point values between 0 and 1, inclusive. |
|
185
|
|
|
@type dimensions: C{[(x, y, w, h), ...]} |
|
186
|
|
|
@type win: C{gtk.gdk.Window} |
|
187
|
|
|
|
|
188
|
|
|
@returns: The new window dimensions. |
|
189
|
|
|
@rtype: C{gtk.gdk.Rectangle} |
|
190
|
|
|
""" |
|
191
|
|
|
win_geom = winman.get_geometry_rel(win, state['monitor_geom']) |
|
192
|
|
|
usable_region = state['usable_region'] |
|
193
|
|
|
|
|
194
|
|
|
# Get the bounding box for the usable region (overlaps panels which |
|
195
|
|
|
# don't fill 100% of their edge of the screen) |
|
196
|
|
|
clip_box = usable_region.get_clipbox() |
|
197
|
|
|
|
|
198
|
|
|
logging.debug("Selected preset sequence:\n\t%r", dimensions) |
|
199
|
|
|
|
|
200
|
|
|
# Resolve proportional (eg. 0.5) and preserved (None) coordinates |
|
201
|
|
|
dims = [] |
|
202
|
|
|
for tup in dimensions: |
|
203
|
|
|
current_dim = [] |
|
204
|
|
|
for pos, val in enumerate(tup): |
|
205
|
|
|
if val is None: |
|
206
|
|
|
current_dim.append(tuple(win_geom)[pos]) |
|
207
|
|
|
else: |
|
208
|
|
|
# FIXME: This is a bit of an ugly way to get (w, h, w, h) |
|
209
|
|
|
# from clip_box. |
|
210
|
|
|
current_dim.append(int(val * tuple(clip_box)[2 + pos % 2])) |
|
211
|
|
|
|
|
212
|
|
|
dims.append(current_dim) |
|
213
|
|
|
|
|
214
|
|
|
if not dims: |
|
215
|
|
|
return None |
|
216
|
|
|
|
|
217
|
|
|
logging.debug("Selected preset sequence resolves to these monitor-relative" |
|
218
|
|
|
" pixel dimensions:\n\t%r", dims) |
|
219
|
|
|
|
|
220
|
|
|
# Calculate euclidean distances between the window's current geometry |
|
221
|
|
|
# and all presets and store them in a min heap. |
|
222
|
|
|
euclid_distance = [] # type: List[Tuple[int, int]] |
|
223
|
|
|
for pos, val in enumerate(dims): |
|
224
|
|
|
distance = sum([(wg - vv) ** 2 for (wg, vv) |
|
225
|
|
|
in zip(tuple(win_geom), tuple(val))]) ** 0.5 |
|
226
|
|
|
heappush(euclid_distance, (distance, pos)) |
|
227
|
|
|
|
|
228
|
|
|
# If the window is already on one of the configured geometries, advance |
|
229
|
|
|
# to the next configuration. Otherwise, use the first configuration. |
|
230
|
|
|
min_distance = heappop(euclid_distance) |
|
231
|
|
|
if float(min_distance[0]) / tuple(clip_box)[2] < 0.1: |
|
232
|
|
|
pos = (min_distance[1] + 1) % len(dims) |
|
233
|
|
|
else: |
|
234
|
|
|
pos = 0 |
|
235
|
|
|
result = gtk.gdk.Rectangle(*dims[pos]) |
|
236
|
|
|
|
|
237
|
|
|
logging.debug("Target preset is %s relative to monitor %s", |
|
238
|
|
|
result, clip_box) |
|
239
|
|
|
result.x += clip_box.x |
|
240
|
|
|
result.y += clip_box.y |
|
241
|
|
|
|
|
242
|
|
|
# If we're overlapping a panel, fall back to a monitor-specific |
|
243
|
|
|
# analogue to _NET_WORKAREA to prevent overlapping any panels and |
|
244
|
|
|
# risking the WM potentially meddling with the result of resposition() |
|
245
|
|
|
if not usable_region.rect_in(result) == gtk.gdk.OVERLAP_RECTANGLE_IN: |
|
246
|
|
|
result = result.intersect(state['usable_rect']) |
|
247
|
|
|
logging.debug("Result exceeds usable (non-rectangular) region of " |
|
248
|
|
|
"desktop. (overlapped a non-fullwidth panel?) Reducing " |
|
249
|
|
|
"to within largest usable rectangle: %s", |
|
250
|
|
|
state['usable_rect']) |
|
251
|
|
|
|
|
252
|
|
|
logging.debug("Calling reposition() with default gravity and dimensions " |
|
253
|
|
|
"%r", tuple(result)) |
|
254
|
|
|
winman.reposition(win, result) |
|
255
|
|
|
return result |
|
256
|
|
|
|
|
257
|
|
|
@commands.add('monitor-switch', force_wrap=True) |
|
258
|
|
|
@commands.add('monitor-next', 1) |
|
259
|
|
|
@commands.add('monitor-prev', -1) |
|
260
|
|
|
def cycle_monitors(winman, win, state, step=1, force_wrap=False): |
|
261
|
|
|
# type: (WindowManager, Any, Dict[str, Any], int, bool) -> None |
|
262
|
|
|
"""Cycle the active window between monitors while preserving position. |
|
263
|
|
|
|
|
264
|
|
|
@todo 1.0.0: Remove C{monitor-switch} in favor of C{monitor-next} |
|
265
|
|
|
(API-breaking change) |
|
266
|
|
|
""" |
|
267
|
|
|
mon_id = state['monitor_id'] |
|
268
|
|
|
n_monitors = winman.gdk_screen.get_n_monitors() |
|
269
|
|
|
|
|
270
|
|
|
new_mon_id = clamp_idx(mon_id + step, n_monitors, |
|
271
|
|
|
state['config'].getboolean('general', 'MovementsWrap') or |
|
272
|
|
|
force_wrap) |
|
273
|
|
|
|
|
274
|
|
|
new_mon_geom = winman.gdk_screen.get_monitor_geometry(new_mon_id) |
|
275
|
|
|
logging.debug("Moving window to monitor %s, which has geometry %s", |
|
276
|
|
|
new_mon_id, new_mon_geom) |
|
277
|
|
|
|
|
278
|
|
|
winman.reposition(win, None, new_mon_geom, keep_maximize=True) |
|
279
|
|
|
|
|
280
|
|
|
@commands.add('monitor-switch-all', force_wrap=True) |
|
281
|
|
|
@commands.add('monitor-prev-all', -1) |
|
282
|
|
|
@commands.add('monitor-next-all', 1) |
|
283
|
|
|
def cycle_monitors_all(winman, win, state, step=1, force_wrap=False): |
|
284
|
|
|
# type: (WindowManager, wnck.Window, Dict[str, Any], int, bool) -> None |
|
285
|
|
|
"""Cycle all windows between monitors while preserving position.""" |
|
286
|
|
|
n_monitors = winman.gdk_screen.get_n_monitors() |
|
287
|
|
|
curr_workspace = win.get_workspace() |
|
288
|
|
|
|
|
289
|
|
|
if not curr_workspace: |
|
290
|
|
|
logging.debug("get_workspace() returned None") |
|
291
|
|
|
return |
|
292
|
|
|
|
|
293
|
|
|
for window in winman.screen.get_windows(): |
|
294
|
|
|
# Skip windows on other virtual desktops for intuitiveness |
|
295
|
|
|
if not window.is_on_workspace(curr_workspace): |
|
296
|
|
|
logging.debug("Skipping window on other workspace") |
|
297
|
|
|
continue |
|
298
|
|
|
|
|
299
|
|
|
# Don't cycle elements of the desktop |
|
300
|
|
|
if window.get_window_type() in [ |
|
301
|
|
|
wnck.WINDOW_DESKTOP, wnck.WINDOW_DOCK]: # pylint: disable=E1101 |
|
302
|
|
|
logging.debug("Skipping desktop/dock window") |
|
303
|
|
|
continue |
|
304
|
|
|
|
|
305
|
|
|
gdkwin = gtk.gdk.window_foreign_new(window.get_xid()) |
|
306
|
|
|
mon_id = winman.gdk_screen.get_monitor_at_window(gdkwin) |
|
307
|
|
|
|
|
308
|
|
|
# TODO: deduplicate cycle_monitors and cycle_monitors_all |
|
309
|
|
|
new_mon_id = clamp_idx(mon_id + step, n_monitors, |
|
310
|
|
|
state['config'].getboolean('general', 'MovementsWrap') or |
|
311
|
|
|
force_wrap) |
|
312
|
|
|
|
|
313
|
|
|
new_mon_geom = winman.gdk_screen.get_monitor_geometry(new_mon_id) |
|
314
|
|
|
logging.debug( |
|
315
|
|
|
"Moving window %s to monitor 0x%d, which has geometry %s", |
|
316
|
|
|
hex(window.get_xid()), new_mon_id, new_mon_geom) |
|
317
|
|
|
|
|
318
|
|
|
winman.reposition(window, None, new_mon_geom, keep_maximize=True) |
|
319
|
|
|
|
|
320
|
|
|
# pylint: disable=no-member |
|
321
|
|
|
MOVE_TO_COMMANDS = { |
|
322
|
|
|
'move-to-top-left': [wnck.WINDOW_GRAVITY_NORTHWEST, |
|
323
|
|
|
wnck.WINDOW_CHANGE_X | wnck.WINDOW_CHANGE_Y], |
|
324
|
|
|
'move-to-top': [wnck.WINDOW_GRAVITY_NORTH, wnck.WINDOW_CHANGE_Y], |
|
325
|
|
|
'move-to-top-right': [wnck.WINDOW_GRAVITY_NORTHEAST, |
|
326
|
|
|
wnck.WINDOW_CHANGE_X | wnck.WINDOW_CHANGE_Y], |
|
327
|
|
|
'move-to-left': [wnck.WINDOW_GRAVITY_WEST, wnck.WINDOW_CHANGE_X], |
|
328
|
|
|
'move-to-center': [wnck.WINDOW_GRAVITY_CENTER, |
|
329
|
|
|
wnck.WINDOW_CHANGE_X | wnck.WINDOW_CHANGE_Y], |
|
330
|
|
|
'move-to-right': [wnck.WINDOW_GRAVITY_EAST, wnck.WINDOW_CHANGE_X], |
|
331
|
|
|
'move-to-bottom-left': [wnck.WINDOW_GRAVITY_SOUTHWEST, |
|
332
|
|
|
wnck.WINDOW_CHANGE_X | wnck.WINDOW_CHANGE_Y], |
|
333
|
|
|
'move-to-bottom': [wnck.WINDOW_GRAVITY_SOUTH, wnck.WINDOW_CHANGE_Y], |
|
334
|
|
|
'move-to-bottom-right': [wnck.WINDOW_GRAVITY_SOUTHEAST, |
|
335
|
|
|
wnck.WINDOW_CHANGE_X | wnck.WINDOW_CHANGE_Y], |
|
336
|
|
|
} |
|
337
|
|
|
|
|
338
|
|
|
@commands.add_many(MOVE_TO_COMMANDS) |
|
339
|
|
|
def move_to_position(winman, # type: WindowManager |
|
340
|
|
|
win, # type: Any # TODO: Make this specific |
|
341
|
|
|
state, # type: Dict[str, Any] |
|
342
|
|
|
gravity, # type: Any # TODO: Make this specific |
|
343
|
|
|
gravity_mask # type: wnck.WindowMoveResizeMask |
|
344
|
|
|
): # type: (...) -> None # TODO: Decide on a return type |
|
345
|
|
|
"""Move window to a position on the screen, preserving its dimensions.""" |
|
346
|
|
|
use_rect = state['usable_rect'] |
|
347
|
|
|
|
|
348
|
|
|
grav_x, grav_y = GRAVITY[gravity] |
|
349
|
|
|
dims = (int(use_rect.width * grav_x), int(use_rect.height * grav_y), 0, 0) |
|
350
|
|
|
result = gtk.gdk.Rectangle(*dims) |
|
351
|
|
|
logging.debug("Calling reposition() with %r gravity and dimensions %r", |
|
352
|
|
|
gravity, tuple(result)) |
|
353
|
|
|
|
|
354
|
|
|
# pylint: disable=no-member |
|
355
|
|
|
winman.reposition(win, result, use_rect, gravity=gravity, |
|
356
|
|
|
geometry_mask=gravity_mask) |
|
357
|
|
|
|
|
358
|
|
|
@commands.add('bordered') |
|
359
|
|
|
def toggle_decorated(winman, win, state): # pylint: disable=unused-argument |
|
360
|
|
|
# type: (WindowManager, wnck.Window, Any) -> None |
|
361
|
|
|
"""Toggle window state on the active window.""" |
|
362
|
|
|
win = gtk.gdk.window_foreign_new(win.get_xid()) |
|
363
|
|
|
win.set_decorations(not win.get_decorations()) |
|
364
|
|
|
|
|
365
|
|
|
@commands.add('show-desktop') |
|
366
|
|
|
def toggle_desktop(winman, win, state): # pylint: disable=unused-argument |
|
367
|
|
|
# type: (WindowManager, Any, Any) -> None |
|
368
|
|
|
"""Toggle "all windows minimized" to view the desktop""" |
|
369
|
|
|
target = not winman.screen.get_showing_desktop() |
|
370
|
|
|
winman.screen.toggle_showing_desktop(target) |
|
371
|
|
|
|
|
372
|
|
|
@commands.add('all-desktops', 'pin', 'is_pinned') |
|
373
|
|
|
@commands.add('fullscreen', 'set_fullscreen', 'is_fullscreen', True) |
|
374
|
|
|
@commands.add('vertical-maximize', 'maximize_vertically', |
|
375
|
|
|
'is_maximized_vertically') |
|
376
|
|
|
@commands.add('horizontal-maximize', 'maximize_horizontally', |
|
377
|
|
|
'is_maximized_horizontally') |
|
378
|
|
|
@commands.add('maximize', 'maximize', 'is_maximized') |
|
379
|
|
|
@commands.add('minimize', 'minimize', 'is_minimized') |
|
380
|
|
|
@commands.add('always-above', 'make_above', 'is_above') |
|
381
|
|
|
@commands.add('always-below', 'make_below', 'is_below') |
|
382
|
|
|
@commands.add('shade', 'shade', 'is_shaded') |
|
383
|
|
|
# pylint: disable=unused-argument,too-many-arguments |
|
384
|
|
|
def toggle_state(winman, win, state, command, check, takes_bool=False): |
|
385
|
|
|
# type: (WindowManager, wnck.Window, Any, str, str, bool) -> None |
|
386
|
|
|
"""Toggle window state on the active window. |
|
387
|
|
|
|
|
388
|
|
|
@param command: The C{wnck.Window} method name to be conditionally prefixed |
|
389
|
|
|
with "un", resolved, and called. |
|
390
|
|
|
@param check: The C{wnck.Window} method name to be called to check |
|
391
|
|
|
whether C{command} should be prefixed with "un". |
|
392
|
|
|
@param takes_bool: If C{True}, pass C{True} or C{False} to C{check} rather |
|
393
|
|
|
thank conditionally prefixing it with C{un} before resolving. |
|
394
|
|
|
@type command: C{str} |
|
395
|
|
|
@type check: C{str} |
|
396
|
|
|
@type takes_bool: C{bool} |
|
397
|
|
|
|
|
398
|
|
|
@todo 1.0.0: Rename C{vertical-maximize} and C{horizontal-maximize} to |
|
399
|
|
|
C{maximize-vertical} and C{maximize-horizontal}. (API-breaking change) |
|
400
|
|
|
""" |
|
401
|
|
|
target = not getattr(win, check)() |
|
402
|
|
|
|
|
403
|
|
|
logging.debug("Calling action '%s' with state '%s'", command, target) |
|
404
|
|
|
if takes_bool: |
|
405
|
|
|
getattr(win, command)(target) |
|
406
|
|
|
else: |
|
407
|
|
|
getattr(win, ('' if target else 'un') + command)() |
|
408
|
|
|
|
|
409
|
|
|
@commands.add('trigger-move', 'move') |
|
410
|
|
|
@commands.add('trigger-resize', 'size') |
|
411
|
|
|
# pylint: disable=unused-argument |
|
412
|
|
|
def trigger_keyboard_action(winman, win, state, command): |
|
413
|
|
|
# type: (WindowManager, wnck.Window, Any, str) -> None |
|
414
|
|
|
"""Ask the Window Manager to begin a keyboard-driven operation.""" |
|
415
|
|
|
getattr(win, 'keyboard_' + command)() |
|
416
|
|
|
|
|
417
|
|
|
@commands.add('workspace-go-next', 1) |
|
418
|
|
|
@commands.add('workspace-go-prev', -1) |
|
419
|
|
|
@commands.add('workspace-go-up', wnck.MOTION_UP) # pylint: disable=E1101 |
|
420
|
|
|
@commands.add('workspace-go-down', wnck.MOTION_DOWN) # pylint: disable=E1101 |
|
421
|
|
|
@commands.add('workspace-go-left', wnck.MOTION_LEFT) # pylint: disable=E1101 |
|
422
|
|
|
@commands.add('workspace-go-right', wnck.MOTION_RIGHT) # pylint: disable=E1101 |
|
423
|
|
|
def workspace_go(winman, win, state, motion): # pylint: disable=W0613 |
|
424
|
|
|
# type: (WindowManager, wnck.Window, Any, wnck.MotionDirection) -> None |
|
425
|
|
|
"""Switch the active workspace (next/prev wrap around)""" |
|
426
|
|
|
target = winman.get_workspace(None, motion, |
|
427
|
|
|
wrap_around=state['config'].getboolean('general', 'MovementsWrap')) |
|
428
|
|
|
if not target: |
|
429
|
|
|
return # It's either pinned, on no workspaces, or there is no match |
|
430
|
|
|
target.activate(int(time.time())) |
|
431
|
|
|
|
|
432
|
|
|
@commands.add('workspace-send-next', 1) |
|
433
|
|
|
@commands.add('workspace-send-prev', -1) |
|
434
|
|
|
@commands.add('workspace-send-up', wnck.MOTION_UP) # pylint: disable=E1101 |
|
435
|
|
|
@commands.add('workspace-send-down', wnck.MOTION_DOWN) # pylint: disable=E1101 |
|
436
|
|
|
@commands.add('workspace-send-left', wnck.MOTION_LEFT) # pylint: disable=E1101 |
|
437
|
|
|
# pylint: disable=E1101 |
|
438
|
|
|
@commands.add('workspace-send-right', wnck.MOTION_RIGHT) |
|
439
|
|
|
# pylint: disable=unused-argument |
|
440
|
|
|
def workspace_send_window(winman, win, state, motion): |
|
441
|
|
|
# type: (WindowManager, wnck.Window, Any, wnck.MotionDirection) -> None |
|
442
|
|
|
"""Move the active window to another workspace (next/prev wrap around)""" |
|
443
|
|
|
target = winman.get_workspace(win, motion, |
|
444
|
|
|
wrap_around=state['config'].getboolean('general', 'MovementsWrap')) |
|
445
|
|
|
if not target: |
|
446
|
|
|
return # It's either pinned, on no workspaces, or there is no match |
|
447
|
|
|
|
|
448
|
|
|
win.move_to_workspace(target) |
|
449
|
|
|
|
|
450
|
|
|
# vim: set sw=4 sts=4 expandtab : |
|
451
|
|
|
|
This check looks for invalid names for a range of different identifiers.
You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.
If your project includes a Pylint configuration file, the settings contained in that file take precedence.
To find out more about Pylint, please refer to their site.