def move_to_position( winman: WindowManager, win: Wnck.Window, state: Dict[str, Any], gravity: Gravity, ) -> None: """Move the active window to a position on the screen, preserving its dimensions. :param win: The window to operate on. """ monitor_rect = state['monitor_geom'] win_rect = Rectangle(*win.get_geometry()) # Build a target rectangle # TODO: Think about ways to refactor scaling for better maintainability target = Rectangle(x=gravity.value[0] * monitor_rect.width, y=gravity.value[1] * monitor_rect.height, width=win_rect.width, height=win_rect.height).from_gravity( gravity).from_relative(monitor_rect) # Push it out from under any panels logging.debug("Clipping rectangle %r\n\tto usable region %r", target, winman.usable_region) confined_target = winman.usable_region.move_to_usable_region(target) # Actually reposition the window # (and be doubly-sure we're not going to resize it by accident) logging.debug("Calling reposition() with dimensions %r", confined_target) winman.reposition(win, confined_target, keep_maximize=True, geometry_mask=Wnck.WindowMoveResizeMask.X | Wnck.WindowMoveResizeMask.Y)
def set_geometry(window: Wnck.Window, x=None, y=None, w=None, h=None, synchronous=False, raise_exceptions=True): if not w and not h: geometry = window.get_geometry() w = geometry.widthp h = geometry.heightp xo, yo, wo, ho = calculate_geometry_offset(window) x, y, w, h = x + xo, y + yo, w + wo, h + ho x, y, w, h = int(x), int(y), int(w), int(h) geometry_cache[window.get_xid()] = (x, y, w, h) adjustment_cache[window.get_xid()] = False window.set_geometry(Wnck.WindowGravity.STATIC, X_Y_W_H_GEOMETRY_MASK, x, y, w, h) if synchronous: synchronized = wait_configure_event(window.get_xid(), Gdk.EventType.CONFIGURE, Gdk.Display.get_default()) return synchronized return False
def get_window_meta(window: Wnck.Window, state: Dict[str, Any], winman: WindowManager) -> bool: """Gather information about ``window`` to pass to the command :param window: The window to inspect. :param state: The metadata dict to :meth:`dict.update` with gathered values. :returns: A boolean indicating success or failure. .. todo:: Is the MPlayer safety hack in :meth:`get_window_meta` still necessary with the refactored window-handling code? .. todo:: Can the :func:`logging.debug` call in :meth:`get_window_meta` be reworked to call :meth:`Wnck.Window.get_name` lazily? """ # Bail out early on None or things like the desktop window if not winman.is_relevant(window): return False win_rect = Rectangle(*window.get_geometry()) logging.debug( "Operating on window %r with title \"%s\" " "and geometry %r", window, window.get_name(), win_rect) monitor_id, monitor_geom = winman.get_monitor(window) # MPlayer safety hack if not winman.usable_region: logging.debug( "Received a worthless value for largest " "rectangular subset of desktop (%r). Doing " "nothing.", winman.usable_region) return False state.update({ "monitor_id": monitor_id, "monitor_geom": monitor_geom, }) return True
def reposition( self, # pylint: disable=too-many-arguments win: Wnck.Window, geom: Optional[Rectangle] = None, monitor: Rectangle = Rectangle(0, 0, 0, 0), keep_maximize: bool = False, geometry_mask: Wnck.WindowMoveResizeMask = ( Wnck.WindowMoveResizeMask.X | Wnck.WindowMoveResizeMask.Y | Wnck.WindowMoveResizeMask.WIDTH | Wnck.WindowMoveResizeMask.HEIGHT) ) -> None: """ Move and resize a window, decorations inclusive, according to the provided target window and monitor geometry rectangles. If no monitor rectangle is specified, the target position will be relative to the desktop as a whole. :param win: The window to reposition. :param geom: The new geometry for the window. Can be left unspecified if the intent is to move the window to another monitor without repositioning it. :param monitor: The frame relative to which ``geom`` should be interpreted. The whole desktop if unspecified. :param keep_maximize: Whether to re-maximize the window if it had to be un-maximized to ensure it would move. :param geometry_mask: A set of flags determining which aspects of the requested geometry should actually be applied to the window. (Allows the same geometry definition to easily be shared between operations like move and resize.) .. todo:: Look for a way to accomplish this with a cleaner method signature. :meth:`reposition` is getting a little hairy. .. todo:: Decide how to refactor :meth:`reposition` to allow for smarter handling of position clamping when cycling windows through a sequence of differently sized monitors. """ old_geom = Rectangle(*win.get_geometry()).to_relative( self.get_monitor(win)[1]) new_args = {} if geom: for attr in ('x', 'y', 'width', 'height'): if geometry_mask & getattr(Wnck.WindowMoveResizeMask, attr.upper()): new_args[attr] = getattr(geom, attr) # Apply changes and return to absolute desktop coordinates. new_geom = old_geom._replace(**new_args).from_relative(monitor) # Ensure the window is fully within the monitor # TODO: Make this remember the original position and re-derive from it # on each monitor-next call as long as the window hasn't changed # (Ideally, re-derive from the tiling preset if set) if bool(monitor) and not geom: clipped_geom = self.usable_region.clip_to_usable_region(new_geom) else: clipped_geom = new_geom if bool(clipped_geom): logging.debug(" Repositioning to %s)\n", clipped_geom) with persist_maximization(win, keep_maximize): # Always use STATIC because either WMs implement window gravity # incorrectly or it's not applicable to this problem win.set_geometry(Wnck.WindowGravity.STATIC, geometry_mask, *clipped_geom) else: logging.debug(" Geometry clipping failed: %r", clipped_geom)
def position_of(self, window: Wnck.Window): return window.get_geometry().xp if self is HORIZONTAL else window.get_geometry().yp
def contains(self, window: Wnck.Window): rect = self.visible_area xp, yp, widthp, heightp = window.get_geometry() return rect[0] <= xp < (rect[0] + rect[2]) and rect[1] <= yp < (rect[1] + rect[3])
def cycle_dimensions( winman: WindowManager, win: Wnck.Window, state: Dict[str, Any], *dimensions: Optional[Tuple[float, float, float, float]] ) -> Optional[Rectangle]: """Cycle the active window through a list of positions and shapes. Takes one step each time this function is called. Keeps track of its position by storing the index in an X11 property on ``win`` named ``_QUICKTILE_CYCLE_POS``. :param dimensions: A list of tuples representing window geometries as floating-point values between 0 and 1, inclusive. :param win: The window to operate on. :returns: The new window dimensions. .. todo:: Refactor :func:`cycle_dimensions` to be less of a big pile. .. todo:: Consider replacing the ``dimensions`` argument to :func:`cycle_dimensions` with a custom type. """ monitor_rect = state['monitor_geom'] win_rect_rel = Rectangle(*win.get_geometry()).to_relative(monitor_rect) logging.debug("Selected preset sequence:\n\t%r", dimensions) # Resolve proportional (eg. 0.5) and preserved (None) coordinates dims = [ resolve_fractional_geom(i or win_rect_rel, monitor_rect) for i in dimensions ] if not dims: return None logging.debug( "Selected preset sequence resolves to these monitor-relative" " pixel dimensions:\n\t%r", dims) try: cmd_idx, pos = winman.get_property(win, '_QUICKTILE_CYCLE_POS', Xatom.INTEGER) logging.debug("Got saved cycle position: %r, %r", cmd_idx, pos) except (ValueError, TypeError): # TODO: Is TypeError still possible? logging.debug("Restarting cycle position sequence") cmd_idx, pos = None, -1 if cmd_idx == state.get('cmd_idx', 0): pos = (pos + 1) % len(dims) else: pos = 0 winman.set_property(win, '_QUICKTILE_CYCLE_POS', [int(state.get('cmd_idx', 0)), pos], prop_type=Xatom.INTEGER, format_size=32) result = None # type: Optional[Rectangle] result = Rectangle(*dims[pos]).from_relative(monitor_rect) logging.debug("Target preset is %s relative to monitor %s", result, monitor_rect) # If we're overlapping a panel, fall back to a monitor-specific # analogue to _NET_WORKAREA to prevent overlapping any panels and # risking the WM potentially meddling with the result of resposition() test_result = winman.usable_region.clip_to_usable_region(result) if test_result != result: result = test_result logging.debug( "Result exceeds usable (non-rectangular) region of " "desktop. (overlapped a non-fullwidth panel?) Reducing " "to within largest usable rectangle: %s", test_result) logging.debug( "Calling reposition() with default gravity and dimensions " "%r", result) winman.reposition(win, result) return result
def decoration_delta(window: Wnck.Window): with Trap(): wx, wy, ww, wh = window.get_geometry() cx, cy, cw, ch = window.get_client_window_geometry() return cx - wx, cy - wy, cw - ww, ch - wh
def intersect(window: Wnck.Window, monitor: Gdk.Monitor): rect = monitor.get_workarea() xp, yp, widthp, heightp = window.get_geometry() return rect.x <= xp < (rect.x + rect.width) and rect.y <= yp < ( rect.y + rect.height)