Skip to content

Supporting Other Wayland Compositors

Alfredo Sequeida edited this page Jan 5, 2025 · 6 revisions

Overview

Hints works on both X11 and Wayland. On X11 Hint uses a single implementation that leverages libwnck to get information about the windows on which hints are drawn on. However, on the Wayland side, clients (Wayland applications) are by design not allowed to position themselves or know the position of other clients. This makes it difficult/impossible to write a single implementation that works across all Wayland Compositors. Because of this, the design decision was made to implement the required functions for a Wayland Compositor individually.

Wayland Compositor Requirements

For a compositor to be supported the following must be true:

  • The compositor must support the wlr layer shell protocol. As this is what is used to position hints over windows. If we can find another way to do this, this could open up support for other wayland compositors.
  • The compositor must have a way to retrieve the following window information:
    • Window extents for the active/focused window.
      • The absolute position of the window.
      • The size of the window.
    • Name of the focused window
    • PID of the focused window

Creating a new Window System

Implement the required class and methods

To support a Wayland compositor, a new class needs to be created in hints/window_systems. A template is provided below as a starting point. To see an example implementation of a WindowSystem, take a look at the Sway WindowSystem Class

"""[WindowSystemName] window system."""

from hints.window_systems.window_system import WindowSystem


class TheNewWindowSystemName(WindowSystem):
    """Sway Window system class."""

    def __init__(self):
        super().__init__()

    @property
    def window_system_name(self) -> str:
        """Get the name of the window system.

        This is useful for performing logic specific to a window system.
        This name must match the name of the window system process.

        :return: The window system name
        """
        return "the_new_window_system_name" # replace this

    @property
    def focused_window_extents(self) -> tuple[int, int, int, int]:
        """Get active window extents.

        :return: Active window extents (x, y, width, height).
        """
        return (0,0,0,0) # replace this
    @property
    def focused_window_pid(self) -> int:
        """Get Process ID corresponding to the focused window.

        :return: Process ID of focused window.
        """
        return 0 # replace this

    @property
    def focused_applicaiton_name(self) -> str:
        """Get focused application name.

        This name is the name used to identify applications for per-
        application rules.

        :return: Focused application name.
        """
        return "" # replace this

Register the WindowSystem

Register the WindowSystem by adding it to this set. This name must match the process name of the window manager.

For example:

supported_wayland_wms = {"sway", "the_new_window_system_name"}

Then add a matching case here

For example:

match wayland_wm:
    case "sway":
        from hints.window_systems.sway import Sway as window_system
    case "the_new_window_system_name":
        from hints.window_systems.the_new_window_system_name import TheNewWindowSystemName as window_system

Window System Development Tips

Keeping things fast

Hints is a short live process, which means that everything needed to draw hints (including the WindowSystem) should be as fast as possible so that we don't have long delays in between starting a hints process to showing the hints on screen. So to keep things fast, you should try to cache/store the information for a window system when you query for it. This way you are not constantly querying the window manager for the information we need. For reference, see how the sway window system class stores the data queried in instance variables so they can be reused.

class Sway(WindowSystem):
    """Sway Window system class."""

    def __init__(self):
        super().__init__()
        self.focused_window = self._get_focused_window_from_sway_tree()
        self.focused_workspace = self._get_focused_workspace_from_sway_tree()
        self.focused_output = self._get_focused_output_from_sway_tree()
        self.bar_height = self._get_bar_height()

Special circumstances

The hope is that the code written will support all compositors as they are implemented. However, because compositors are all different, sometimes special cases might need to be made. This will require some exploration of the code base, but the Sway implementation can be used as an example. For example consider the special case here

match window_system.window_system_name:
    case "sway":
        mouse_y_offset = window_system.bar_height

In this case the Sway does not take the height of the top bar into account when returning the mouse / element coordinates. So we had to offset the mouse position by the height of the bar. You can do this in other places in the code by making use of the window_system.window_system_name attribute for a WindowSystem.