Esempio n. 1
0
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)
Esempio n. 2
0
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
Esempio n. 3
0
    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
Esempio n. 4
0
    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)
Esempio n. 5
0
	def position_of(self, window: Wnck.Window):
		return window.get_geometry().xp if self is HORIZONTAL else window.get_geometry().yp
Esempio n. 6
0
	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])
Esempio n. 7
0
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
Esempio n. 8
0
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
Esempio n. 9
0
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)