From ba72039dd56fd5857267980bb73845e06f4013a5 Mon Sep 17 00:00:00 2001 From: Stephan Sokolow Date: Wed, 23 Aug 2017 08:41:53 -0400 Subject: [PATCH] Switch to a proper solution for trackng cycle position in cycle_dimensions Fixes #20 Fixes #25 Fixes #86 --- ChangeLog | 2 ++ quicktile/commands.py | 26 +++++++++++++++++++------- quicktile/wm.py | 42 ++++++++++++++++++++++++++++++++++++++++++ test_quicktile.py | 2 ++ 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/ChangeLog b/ChangeLog index c55e25d..e0ba199 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,8 +7,10 @@ - Allow `ModMask` to be empty/None to enable bindings without a common prefix - Add `monitor-prev-all`, `monitor-next-all`, and `monitor-switch-all` commands - Add gtkexcepthook for more convenient handling of unexpected errors +- Fix window cycling when windows impose constraints on their dimensions - Work around PyGTK's incompatibilty with tox - Make install.sh a bit more robust +- Do a ton of refactoring to ease reusing existing code in new commands. 0.2.2: - Added move-to{,-top,-bottom}{,-left,-right} (David Stygstra) diff --git a/quicktile/commands.py b/quicktile/commands.py index be7d035..4f82702 100644 --- a/quicktile/commands.py +++ b/quicktile/commands.py @@ -8,8 +8,7 @@ import gtk.gdk, wnck # pylint: disable=import-error -from .layout import (check_tolerance, closest_geom_match, - resolve_fractional_geom) +from .layout import resolve_fractional_geom from .wm import GRAVITY from .util import clamp_idx, fmt_table @@ -110,6 +109,12 @@ def wrapper(winman, # type: WindowManager }) args, kwargs = p_args + args, dict(p_kwargs, **kwargs) + + # TODO: Factor out this hack + if 'cmd_idx' in kwargs: + state['cmd_idx'] = kwargs['cmd_idx'] + del kwargs['cmd_idx'] + func(winman, window, state, *args, **kwargs) if name in self.commands: @@ -140,8 +145,8 @@ def add_many(self, command_map): # TODO: Refactor and redesign for better maintainability def decorate(func): """Closure used to allow decorator to take arguments""" - for cmd, arglist in command_map.items(): - self.add(cmd, *arglist)(func) + for pos, (cmd, arglist) in enumerate(command_map.items()): + self.add(cmd, cmd_idx=pos, *arglist)(func) return func return decorate @@ -203,11 +208,18 @@ def cycle_dimensions(winman, # type: WindowManager logging.debug("Selected preset sequence resolves to these monitor-relative" " pixel dimensions:\n\t%r", dims) - closest_distance, closest_idx = closest_geom_match(win_geom, dims) - if check_tolerance(closest_distance, clip_box): - pos = (closest_idx + 1) % len(dims) + try: + cmd_idx, pos = winman.get_property('_QUICKTILE_CYCLE_POS', win)[2] + except TypeError: + cmd_idx, pos = None, -1 + + if cmd_idx == state.get('cmd_idx', 0): + pos = (pos + 1) % len(dims) else: pos = 0 + + winman.set_property('_QUICKTILE_CYCLE_POS', + (state.get('cmd_idx', 0), pos), win) result = gtk.gdk.Rectangle(*dims[pos]) logging.debug("Target preset is %s relative to monitor %s", diff --git a/quicktile/wm.py b/quicktile/wm.py index 192a102..09b2cdd 100644 --- a/quicktile/wm.py +++ b/quicktile/wm.py @@ -287,6 +287,48 @@ def _get_win_for_prop(self, window=None): return gtk.gdk.window_foreign_new(window.get_xid()) else: return self.gdk_screen.get_root_window() + + def get_property(self, key, window=None): + # type: (str, Optional[wnck.Window]) -> Any + """Retrieve the value of a property on the given window. + + @param window: If unset, the root window will be queried. + @type window: C{wnck.Window} or C{None} + """ + return self._get_win_for_prop(window).property_get(key) + + def set_property(self, key, # type: str + value, # type: Union[Sequence[int], int, str] + window=None # type: Optional[wnck.Window] + ): # type: (...) -> None + """Set the value of a property on the given window. + + @param window: If unset, the root window will be queried. + @type window: C{wnck.Window} or C{None} + """ + + if isinstance(value, basestring): + prop_format = 8 + prop_type = "STRING" + else: + prop_format = 32 + prop_type = "CARDINAL" + if isinstance(value, int): + value = [value] + + self._get_win_for_prop(window).property_change( + key, prop_type, + prop_format, gtk.gdk.PROP_MODE_REPLACE, value) + + def del_property(self, key, window=None): + # type: (str, Optional[wnck.Window]) -> None + """Unset a property on the given window. + + @param window: If unset, the root window will be queried. + @type window: C{wnck.Window} or C{None} + """ + self._get_win_for_prop(window).property_delete(key) + def get_relevant_windows(self, workspace): """C{wnck.Screen.get_windows} without WINDOW_DESKTOP/DOCK windows.""" diff --git a/test_quicktile.py b/test_quicktile.py index 7cdcd33..53d4152 100644 --- a/test_quicktile.py +++ b/test_quicktile.py @@ -7,6 +7,8 @@ __author__ = "Stephan Sokolow (deitarion/SSokolow)" __license__ = "GNU GPL 2.0 or later" +# TODO: I need a functional test to make sure issue #25 doesn't regress + try: import pygtk pygtk.require('2.0')