-
Notifications
You must be signed in to change notification settings - Fork 87
command chaining functionality in config #89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 6 commits
c1aeaaf
f4064c3
8d0d0e9
76e97dc
cdf7ea0
f4894f2
7d4400a
f90de3d
4d47975
418185a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -57,9 +57,9 @@ def get_window_meta(window, state, winman): | |
| # FIXME: Make calls to win.get_* lazy in case --debug | ||
| # wasn't passed. | ||
| logging.debug("Operating on window %r with title \"%s\" " | ||
| "and geometry %r", | ||
| window, window.get_name(), | ||
| window.get_geometry()) | ||
| "and geometry %r", | ||
| window, window.get_name(), | ||
| window.get_geometry()) | ||
|
|
||
| monitor_id, monitor_geom = winman.get_monitor(window) | ||
| use_area, use_rect = winman.workarea.get(monitor_geom) | ||
|
|
@@ -68,16 +68,16 @@ def get_window_meta(window, state, winman): | |
| # comprehensive exception catcher. | ||
| if not use_rect: | ||
| logging.debug("Received a worthless value for largest " | ||
| "rectangular subset of desktop (%r). Doing " | ||
| "nothing.", use_rect) | ||
| "rectangular subset of desktop (%r). Doing " | ||
| "nothing.", use_rect) | ||
| return False | ||
|
|
||
| state.update({ | ||
| "monitor_id": monitor_id, | ||
| "monitor_geom": monitor_geom, | ||
| "usable_region": use_area, | ||
| "usable_rect": use_rect, | ||
| }) | ||
| }) | ||
| return True | ||
|
|
||
| def add(self, name, *p_args, **p_kwargs): | ||
|
|
@@ -103,10 +103,10 @@ def decorate(func): # type: (CommandCB) -> CommandCB | |
| @wraps(func) | ||
| # pylint: disable=missing-docstring | ||
| def wrapper(winman, # type: WindowManager | ||
| window=None, # type: wnck.Window | ||
| *args, | ||
| **kwargs | ||
| ): # type: (...) -> None | ||
| window=None, # type: wnck.Window | ||
| *args, | ||
| **kwargs | ||
| ): # type: (...) -> None | ||
|
|
||
| window = window or winman.screen.get_active_window() | ||
|
|
||
|
|
@@ -121,7 +121,7 @@ def wrapper(winman, # type: WindowManager | |
|
|
||
| # Bail out early on None or things like the desktop window | ||
| if not (windowless or self.get_window_meta( | ||
| window, state, winman)): | ||
| window, state, winman)): | ||
| logging.debug("No window and windowless=False") | ||
| return None | ||
|
|
||
|
|
@@ -140,8 +140,8 @@ def wrapper(winman, # type: WindowManager | |
|
|
||
| if not func.__doc__: | ||
| raise AssertionError("All commands must have a docstring: " | ||
| "%r" % func) | ||
| help_str = func.__doc__.strip().split('\n')[0].split('. ')[0] | ||
| "%r" % func) | ||
| help_str = func.__doc__.strip().split('\n')[0].split('. ')[0] | ||
| self.help[name] = help_str.strip('.') | ||
|
|
||
| # Return the unwrapped function so decorators can be stacked | ||
|
|
@@ -168,13 +168,13 @@ def decorate(func): | |
| return decorate | ||
|
|
||
| def call(self, command, winman, *args, **kwargs): | ||
| """Check if the command is valid and execute it.""" | ||
| # type: (str, WindowManager, *Any, **Any) -> bool | ||
| """Resolve a textual positioning command and execute it.""" | ||
| cmd = self.commands.get(command, None) | ||
|
|
||
| if cmd: | ||
| logging.debug("Executing command '%s' with arguments %r, %r", | ||
| command, args, kwargs) | ||
| command, args, kwargs) | ||
| cmd(winman, *args, **kwargs) | ||
|
|
||
| # TODO: Allow commands to report success or failure | ||
|
|
@@ -183,15 +183,30 @@ def call(self, command, winman, *args, **kwargs): | |
| logging.error("Unrecognized command: %s", command) | ||
| return False | ||
|
|
||
| def call_multiple(self, command, winman, *args, **kwargs): | ||
| """Resolve a textual positioning command and execute it. | ||
| Accepts a comma seperated list as the command.""" | ||
| # type: (str, WindowManager, *Any, **Any) -> bool | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll probably need another day or two. If I'm seeing |
||
| cmds = [] | ||
| success = True | ||
| if ',' in command: | ||
| cmds = [i.strip() for i in command.split(',')] | ||
| for cmd in cmds: | ||
| success = self.call(cmd, winman, *args, **kwargs) | ||
| else: | ||
| return self.call(command, winman, *args, **kwargs) | ||
|
|
||
| return success | ||
|
|
||
|
|
||
| #: The instance of L{CommandRegistry} to be used in 99.9% of use cases. | ||
| commands = CommandRegistry() | ||
|
|
||
| def cycle_dimensions(winman, # type: WindowManager | ||
| win, # type: Any # TODO: Consistent Window type | ||
| state, # type: Dict[str, Any] | ||
| *dimensions # type: Any | ||
| ): # type: (...) -> Optional[gtk.gdk.Rectangle] | ||
| win, # type: Any # TODO: Consistent Window type | ||
| state, # type: Dict[str, Any] | ||
| *dimensions # type: Any | ||
| ): # type: (...) -> Optional[gtk.gdk.Rectangle] | ||
| # type: (WindowManager, Any, Dict[str, Any], *Tuple[...]) -> | ||
| # TODO: Standardize on what kind of window object to pass around | ||
| """Cycle the active window through a list of positions and shapes. | ||
|
|
@@ -223,7 +238,7 @@ def cycle_dimensions(winman, # type: WindowManager | |
| return None | ||
|
|
||
| logging.debug("Selected preset sequence resolves to these monitor-relative" | ||
| " pixel dimensions:\n\t%r", dims) | ||
| " pixel dimensions:\n\t%r", dims) | ||
|
|
||
| try: | ||
| cmd_idx, pos = winman.get_property('_QUICKTILE_CYCLE_POS', win)[2] | ||
|
|
@@ -236,11 +251,11 @@ def cycle_dimensions(winman, # type: WindowManager | |
| pos = 0 | ||
|
|
||
| winman.set_property('_QUICKTILE_CYCLE_POS', | ||
| (state.get('cmd_idx', 0), pos), win) | ||
| (state.get('cmd_idx', 0), pos), win) | ||
| result = gtk.gdk.Rectangle(*dims[pos]) | ||
|
|
||
| logging.debug("Target preset is %s relative to monitor %s", | ||
| result, clip_box) | ||
| result, clip_box) | ||
| result.x += clip_box.x | ||
| result.y += clip_box.y | ||
|
|
||
|
|
@@ -250,25 +265,25 @@ def cycle_dimensions(winman, # type: WindowManager | |
| if not usable_region.rect_in(result) == gtk.gdk.OVERLAP_RECTANGLE_IN: | ||
| result = result.intersect(state['usable_rect']) | ||
| logging.debug("Result exceeds usable (non-rectangular) region of " | ||
| "desktop. (overlapped a non-fullwidth panel?) Reducing " | ||
| "to within largest usable rectangle: %s", | ||
| state['usable_rect']) | ||
| "desktop. (overlapped a non-fullwidth panel?) Reducing " | ||
| "to within largest usable rectangle: %s", | ||
| state['usable_rect']) | ||
|
|
||
| logging.debug("Calling reposition() with default gravity and dimensions " | ||
| "%r", tuple(result)) | ||
| winman.reposition(win, result) | ||
| logging.debug("Calling reposition() with default gravity and dimensions " | ||
| "%r", tuple(result)) | ||
| winman.reposition(win, result) | ||
| return result | ||
|
|
||
| @commands.add('monitor-switch', force_wrap=True) | ||
| @commands.add('monitor-next', 1) | ||
| @commands.add('monitor-prev', -1) | ||
| def cycle_monitors(winman, # type: WindowManager | ||
| win, # type: wnck.Window | ||
| state, # type: Dict[str, Any] | ||
| step=1, # type: int | ||
| force_wrap=False, # type: bool | ||
| n_monitors=None # type: Optional[int] | ||
| ): # type: (...) -> None | ||
| win, # type: wnck.Window | ||
| state, # type: Dict[str, Any] | ||
| step=1, # type: int | ||
| force_wrap=False, # type: bool | ||
| n_monitors=None # type: Optional[int] | ||
| ): # type: (...) -> None | ||
| """Cycle the active window between monitors while preserving position. | ||
|
|
||
| @todo 1.0.0: Remove C{monitor-switch} in favor of C{monitor-next} | ||
|
|
@@ -278,12 +293,12 @@ def cycle_monitors(winman, # type: WindowManager | |
| n_monitors = n_monitors or winman.gdk_screen.get_n_monitors() | ||
|
|
||
| new_mon_id = clamp_idx(mon_id + step, n_monitors, | ||
| state['config'].getboolean('general', 'MovementsWrap') or | ||
| force_wrap) | ||
| state['config'].getboolean('general', 'MovementsWrap') or | ||
| force_wrap) | ||
|
|
||
| new_mon_geom = winman.gdk_screen.get_monitor_geometry(new_mon_id) | ||
| logging.debug("Moving window to monitor %s, which has geometry %s", | ||
| new_mon_id, new_mon_geom) | ||
| new_mon_id, new_mon_geom) | ||
|
|
||
| winman.reposition(win, None, new_mon_geom, keep_maximize=True) | ||
|
|
||
|
|
@@ -305,48 +320,53 @@ def cycle_monitors_all(winman, win, state, step=1, force_wrap=False): | |
|
|
||
| # pylint: disable=no-member | ||
| MOVE_TO_COMMANDS = { | ||
| 'move-to-top-left': [wnck.WINDOW_GRAVITY_NORTHWEST, | ||
| wnck.WINDOW_CHANGE_X | wnck.WINDOW_CHANGE_Y], | ||
| 'move-to-top': [wnck.WINDOW_GRAVITY_NORTH, wnck.WINDOW_CHANGE_Y], | ||
| 'move-to-top-right': [wnck.WINDOW_GRAVITY_NORTHEAST, | ||
| wnck.WINDOW_CHANGE_X | wnck.WINDOW_CHANGE_Y], | ||
| 'move-to-left': [wnck.WINDOW_GRAVITY_WEST, wnck.WINDOW_CHANGE_X], | ||
| 'move-to-center': [wnck.WINDOW_GRAVITY_CENTER, | ||
| wnck.WINDOW_CHANGE_X | wnck.WINDOW_CHANGE_Y], | ||
| 'move-to-right': [wnck.WINDOW_GRAVITY_EAST, wnck.WINDOW_CHANGE_X], | ||
| 'move-to-bottom-left': [wnck.WINDOW_GRAVITY_SOUTHWEST, | ||
| wnck.WINDOW_CHANGE_X | wnck.WINDOW_CHANGE_Y], | ||
| 'move-to-bottom': [wnck.WINDOW_GRAVITY_SOUTH, wnck.WINDOW_CHANGE_Y], | ||
| 'move-to-bottom-right': [wnck.WINDOW_GRAVITY_SOUTHEAST, | ||
| wnck.WINDOW_CHANGE_X | wnck.WINDOW_CHANGE_Y], | ||
| } | ||
| 'move-to-top-left': [wnck.WINDOW_GRAVITY_NORTHWEST, | ||
| wnck.WINDOW_CHANGE_X | wnck.WINDOW_CHANGE_Y], | ||
| 'move-to-top': [wnck.WINDOW_GRAVITY_NORTH, wnck.WINDOW_CHANGE_Y], | ||
| 'move-to-top-right': [wnck.WINDOW_GRAVITY_NORTHEAST, | ||
| wnck.WINDOW_CHANGE_X | wnck.WINDOW_CHANGE_Y], | ||
| 'move-to-left': [wnck.WINDOW_GRAVITY_WEST, wnck.WINDOW_CHANGE_X], | ||
| 'move-to-center': [wnck.WINDOW_GRAVITY_CENTER, | ||
| wnck.WINDOW_CHANGE_X | wnck.WINDOW_CHANGE_Y], | ||
| 'move-to-right': [wnck.WINDOW_GRAVITY_EAST, wnck.WINDOW_CHANGE_X], | ||
| 'move-to-bottom-left': [wnck.WINDOW_GRAVITY_SOUTHWEST, | ||
| wnck.WINDOW_CHANGE_X | wnck.WINDOW_CHANGE_Y], | ||
| 'move-to-bottom': [wnck.WINDOW_GRAVITY_SOUTH, wnck.WINDOW_CHANGE_Y], | ||
| 'move-to-bottom-right': [wnck.WINDOW_GRAVITY_SOUTHEAST, | ||
| wnck.WINDOW_CHANGE_X | wnck.WINDOW_CHANGE_Y], | ||
| } | ||
|
|
||
| @commands.add_many(MOVE_TO_COMMANDS) | ||
| def move_to_position(winman, # type: WindowManager | ||
| win, # type: Any # TODO: Make this specific | ||
| state, # type: Dict[str, Any] | ||
| gravity, # type: Any # TODO: Make this specific | ||
| gravity_mask # type: wnck.WindowMoveResizeMask | ||
| ): # type: (...) -> None # TODO: Decide on a return type | ||
| win, # type: Any # TODO: Make this specific | ||
| state, # type: Dict[str, Any] | ||
| gravity, # type: Any # TODO: Make this specific | ||
| gravity_mask # type: wnck.WindowMoveResizeMask | ||
| ): # type: (...) -> None # TODO: Decide on a return type | ||
| """Move window to a position on the screen, preserving its dimensions.""" | ||
| use_rect = state['usable_rect'] | ||
|
|
||
| grav_x, grav_y = GRAVITY[gravity] | ||
| dims = (int(use_rect.width * grav_x), int(use_rect.height * grav_y), 0, 0) | ||
| result = gtk.gdk.Rectangle(*dims) | ||
| logging.debug("Calling reposition() with %r gravity and dimensions %r", | ||
| gravity, tuple(result)) | ||
| gravity, tuple(result)) | ||
|
|
||
| # pylint: disable=no-member | ||
| winman.reposition(win, result, use_rect, gravity=gravity, | ||
| geometry_mask=gravity_mask) | ||
|
|
||
| @commands.add('bordered-set', True) | ||
| @commands.add('bordered-unset', False) | ||
| @commands.add('bordered') | ||
| def toggle_decorated(winman, win, state): # pylint: disable=unused-argument | ||
| def toggle_decorated(winman, win, state, decoration=None): # pylint: disable=unused-argument | ||
| # type: (WindowManager, wnck.Window, Any) -> None | ||
| """Toggle window decoration state on the active window.""" | ||
| win = gtk.gdk.window_foreign_new(win.get_xid()) | ||
| win.set_decorations(not win.get_decorations()) | ||
| if decoration is not None: | ||
| win.set_decorations(decoration) | ||
| else: | ||
| win.set_decorations(not win.get_decorations()) | ||
|
|
||
| @commands.add('show-desktop', windowless=True) | ||
| def toggle_desktop(winman, win, state): # pylint: disable=unused-argument | ||
|
|
@@ -358,9 +378,9 @@ def toggle_desktop(winman, win, state): # pylint: disable=unused-argument | |
| @commands.add('all-desktops', 'pin', 'is_pinned') | ||
| @commands.add('fullscreen', 'set_fullscreen', 'is_fullscreen', True) | ||
| @commands.add('vertical-maximize', 'maximize_vertically', | ||
| 'is_maximized_vertically') | ||
| 'is_maximized_vertically') | ||
| @commands.add('horizontal-maximize', 'maximize_horizontally', | ||
| 'is_maximized_horizontally') | ||
| 'is_maximized_horizontally') | ||
| @commands.add('maximize', 'maximize', 'is_maximized') | ||
| @commands.add('minimize', 'minimize', 'is_minimized') | ||
| @commands.add('always-above', 'make_above', 'is_above') | ||
|
|
@@ -411,7 +431,7 @@ def workspace_go(winman, win, state, motion): # pylint: disable=W0613 | |
| # type: (WindowManager, wnck.Window, Any, wnck.MotionDirection) -> None | ||
| """Switch the active workspace (next/prev wrap around)""" | ||
| target = winman.get_workspace(None, motion, | ||
| wrap_around=state['config'].getboolean('general', 'MovementsWrap')) | ||
| wrap_around=state['config'].getboolean('general', 'MovementsWrap')) | ||
| if not target: | ||
| logging.debug("Couldn't get the active workspace.") | ||
| return | ||
|
|
@@ -430,7 +450,7 @@ def workspace_send_window(winman, win, state, motion): | |
| # type: (WindowManager, wnck.Window, Any, wnck.MotionDirection) -> None | ||
| """Move the active window to another workspace (next/prev wrap around)""" | ||
| target = winman.get_workspace(win, motion, | ||
| wrap_around=state['config'].getboolean('general', 'MovementsWrap')) | ||
| wrap_around=state['config'].getboolean('general', 'MovementsWrap')) | ||
| if not target: | ||
| return # It's either pinned, on no workspaces, or there is no match | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ssokolow@monolith quicktile [master] % ./run_tests.sh [...] quicktile/commands.py:172: error: misplaced type annotationThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great! That's done.