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.