Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions hints/backends/atspi.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ def get_atspi_active_window(self) -> Atspi.Accessible | None:
continue
for window_index in range(window.get_child_count()):
current_window = window.get_child_at_index(window_index)
if current_window is None:
continue
# Some hidden windows that are minimized to status trays
# (like discord) will still have the Atspi.StateType.Active
# state, so the pid from the window manger allows us to filter
Expand Down
6 changes: 5 additions & 1 deletion hints/hints.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,10 @@ def get_window_system_class(
from hints.window_systems.plasmashell import Plasmashell as window_system
case "gnome-shell":
from hints.window_systems.gnome import Gnome as window_system
case "niri":
from hints.window_systems.niri import Niri as window_system
case "mango":
from hints.window_systems.mango import Mango as window_system

return window_system

Expand All @@ -301,7 +305,7 @@ def get_window_system(window_system_id: str = "") -> Type[WindowSystem]:
if window_system_type == WindowSystemType.WAYLAND:

# add new waland wms here, then add a match case below to import the class
supported_wayland_wms = {"sway", "Hyprland", "plasmashell", "gnome-shell"}
supported_wayland_wms = {"sway", "Hyprland", "plasmashell", "gnome-shell", "niri", "mango"}

# Check if there is a process running that matches the supported_wayland_wms
window_system_id = (
Expand Down
56 changes: 56 additions & 0 deletions hints/window_systems/mango.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Mango window system."""

from subprocess import run

from hints.window_systems.window_system import WindowSystem


def _parse_mmsg(output: str) -> dict[str, str]:
result = {}
for line in output.strip().splitlines():
parts = line.split(None, 2)
if len(parts) == 3:
result[parts[1]] = parts[2]
return result


class Mango(WindowSystem):
"""Mango (MangoWM) window system class."""

def __init__(self):
super().__init__()
self._state = self._query()

def _query(self) -> dict[str, str]:
result = run(
["mmsg", "-g", "-c", "-x"],
capture_output=True,
check=True,
)
return _parse_mmsg(result.stdout.decode("utf-8"))

@property
def window_system_name(self) -> str:
return "mango"

@property
def focused_window_extents(self) -> tuple[int, int, int, int]:
return (
int(self._state["x"]),
int(self._state["y"]),
int(self._state["width"]),
int(self._state["height"]),
)

@property
def focused_window_pid(self) -> int:
appid = self._state.get("appid", "")
result = run(
["pgrep", "-n", appid],
capture_output=True,
)
return int(result.stdout.strip()) if result.returncode == 0 else -1

@property
def focused_applicaiton_name(self) -> str:
return self._state.get("appid", "")
85 changes: 85 additions & 0 deletions hints/window_systems/niri.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""Niri window system."""

from json import loads
from subprocess import run

from hints.window_systems.window_system import WindowSystem


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

def __init__(self):
super().__init__()
self._focused_window = self._get_focused_window()
self._focused_output = self._get_focused_output()

def _get_focused_window(self):
result = run(
["niri", "msg", "-j", "focused-window"],
capture_output=True,
check=True,
)
return loads(result.stdout.decode("utf-8"))

def _get_focused_output(self):
result = run(
["niri", "msg", "-j", "focused-output"],
capture_output=True,
check=True,
)
return loads(result.stdout.decode("utf-8"))

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

:return: The window system name
"""
return "niri"

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

:return: Active window extents (x, y, width, height).
"""
output_logical = self._focused_output["logical"]
layout = self._focused_window["layout"]

tile_pos = layout.get("tile_pos_in_workspace_view")
if tile_pos is not None:
# Floating windows: niri populates tile_pos_in_workspace_view
x = output_logical["x"] + int(tile_pos[0])
y = output_logical["y"] + int(tile_pos[1])
width = layout["window_size"][0]
height = layout["window_size"][1]
else:
# Tiled windows: niri IPC does not expose the screen position
# (https://github.com/niri-wm/niri/issues/2381). Fall back to
# the full output geometry so hints still scans the screen.
x = output_logical["x"]
y = output_logical["y"]
width = output_logical["width"]
height = output_logical["height"]

return (x, y, width, height)

@property
def focused_window_pid(self) -> int:
"""Get Process ID corresponding to the focused window.

:return: Process ID of focused window.
"""
return self._focused_window["pid"]

@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 self._focused_window["app_id"]
2 changes: 1 addition & 1 deletion hints/window_systems/window_system_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class WindowSystemType(Enum):
WAYLAND = "wayland"


SupportedWindowSystems = Literal["x11", "sway", "hyprland"]
SupportedWindowSystems = Literal["x11", "sway", "hyprland", "niri"]


def get_window_system_type() -> WindowSystemType:
Expand Down