Something went wrong. Try again.
A Material 3 inspired desktop shell for Niri and Hyprland created with Ignis.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362from ignis.services.applications import ApplicationsServicefrom ignis.services.hyprland import HyprlandService, HyprlandWindowfrom ignis.services.niri import NiriService, NiriWindow, NiriWorkspacefrom user_settings import user_settings
from ignis import widgetsfrom modules.shared_modules import AppIcon
SERVICE = Noneif NiriService.get_default().is_available: SERVICE = NiriService.get_default()elif HyprlandService.get_default().is_available: SERVICE = HyprlandService.get_default()
APPLICATIONS = ApplicationsService.get_default()
class DummyWorkspace: def __init__(self, id: int): self.id = id self.idx = id
def switch_to(self): if SERVICE: SERVICE.switch_to_workspace(self.id)
def get_active_workspace(): if not SERVICE: return None if isinstance(SERVICE, NiriService): for workspace in SERVICE.workspaces: if workspace.is_active: return workspace return None elif isinstance(SERVICE, HyprlandService): return SERVICE.active_workspace return None
class WorkspaceButton(widgets.Button): def __init__(self, workspace) -> None: style = user_settings.interface.modules.options.workspaces_style
if isinstance(workspace, NiriWorkspace): label_text = str(workspace.idx) else: label_text = str(workspace.id)
self.workspace = workspace self._label = widgets.Label(label=label_text, halign="center", valign="center")
children = [self._label] self._icons_box = None
if style == "windows": self._icons_box = widgets.Box(spacing=5, css_classes=["workspace-icons"]) children.append(self._icons_box)
self._main_content_box = widgets.Box( halign="center", valign="center", spacing=4, child=children )
super().__init__( css_classes=["workspace"], on_click=lambda x: self.workspace.switch_to(), child=self._main_content_box, )
def update_css_classes(*args): active_workspace = get_active_workspace() if active_workspace and self.workspace.id == active_workspace.id: self.add_css_class("active") else: self.remove_css_class("active") if not self._get_windows_for_workspace(): self.add_css_class("empty") else: self.remove_css_class("empty")
if SERVICE: SERVICE.connect("notify::workspaces", update_css_classes) if isinstance(SERVICE, HyprlandService): SERVICE.connect("notify::active-workspace", update_css_classes) update_css_classes()
self.update_layout() if style == "windows": self._update_icons() if SERVICE: SERVICE.connect("notify::windows", lambda *args: self._update_icons())
def _get_windows_for_workspace(self): if isinstance(SERVICE, NiriService): return [w for w in SERVICE.windows if w.workspace_id == self.workspace.id] elif isinstance(SERVICE, HyprlandService): return SERVICE.get_windows_on_workspace(self.workspace.id) return []
def _is_same_app(self, id1: str, id2: str): if not id1 or not id2: return False id1_lower = id1.lower() id2_lower = id2.lower() return id1_lower in id2_lower or id2_lower in id1_lower
def _update_icons(self): if not self._icons_box: return
last_child = self._icons_box.get_last_child() while last_child: self._icons_box.remove(last_child) last_child = self._icons_box.get_last_child()
windows = self._get_windows_for_workspace()
self._icons_box.set_visible(bool(windows))
for window in windows: app_id = None if isinstance(window, NiriWindow): app_id = window.app_id elif isinstance(window, HyprlandWindow): app_id = window.class_name
icon_widget = AppIcon(app_id=app_id, name=window.title, pixel_size=16) self._icons_box.append(icon_widget)
self._main_content_box.queue_resize()
def update_layout(self): bar = ( user_settings.interface.bar if user_settings.interface.modules.bar_id.workspaces == 0 else user_settings.interface.bar2 ) vertical = bar.vertical style = user_settings.interface.modules.options.workspaces_style
if self._icons_box: self._icons_box.set_vertical(vertical)
if vertical: self._main_content_box.set_vertical(True) self.set_halign("center") if style == "dots": self.set_valign("center") else: self.set_valign("fill") else: self._main_content_box.set_vertical(False) self.set_valign("center") if style == "dots": self.set_halign("center") else: self.set_halign("fill")
if style == "dots": self._main_content_box.set_spacing(0)
class Workspaces(widgets.EventBox): def __init__(self): self._workspace_box = widgets.Box( css_classes=["workspaces"], halign="center", hexpand=True, valign="center", vexpand=True, ) user_settings.interface.modules.options.connect_option( "workspaces_style", lambda: self.update_workspaces() ) user_settings.interface.modules.options.connect_option( "fixed_workspaces_enabled", lambda: self.update_workspaces() ) user_settings.interface.modules.options.connect_option( "fixed_workspaces_amount", lambda: self.update_workspaces() )
super().__init__( child=[self._workspace_box], on_scroll_up=lambda self: self.workspaces_scroll(+1), on_scroll_down=lambda self: self.workspaces_scroll(-1), )
self._last_style = None self._last_workspace_ids = [] self._last_fixed_enabled = False
if SERVICE: SERVICE.connect("notify::workspaces", self.update_workspaces) SERVICE.connect("notify::active-workspace", self.update_workspaces) self.update_workspaces()
self.update_layout()
def update_workspaces(self, *args): current_style = user_settings.interface.modules.options.workspaces_style
current_workspace_ids = [] if SERVICE: fixed_enabled = ( user_settings.interface.modules.options.fixed_workspaces_enabled ) if isinstance(SERVICE, NiriService): current_workspace_ids = [ws.idx for ws in SERVICE.workspaces] elif isinstance(SERVICE, HyprlandService): current_workspace_ids = [ws.id for ws in SERVICE.workspaces]
if fixed_enabled != self._last_fixed_enabled: self._last_style = None self._last_workspace_ids = [] if ( current_style != self._last_style or current_workspace_ids != self._last_workspace_ids ): self._last_style = current_style self._last_workspace_ids = current_workspace_ids
fixed_enabled = ( user_settings.interface.modules.options.fixed_workspaces_enabled ) fixed_amount = int( user_settings.interface.modules.options.fixed_workspaces_amount )
self._workspace_box.remove_css_class("dots") self._workspace_box.remove_css_class("windows") self._workspace_box.remove_css_class("numbers")
if current_style == "dots": self._workspace_box.add_css_class("dots") elif current_style == "windows": self._workspace_box.add_css_class("windows") elif current_style == "numbers": self._workspace_box.add_css_class("numbers")
if SERVICE: last_child = self._workspace_box.get_last_child() while last_child: self._workspace_box.remove(last_child) last_child = self._workspace_box.get_last_child()
workspaces_to_display = [] if SERVICE: all_workspaces_map = {} if isinstance(SERVICE, NiriService): for ws in SERVICE.workspaces: all_workspaces_map[ws.idx] = ws elif isinstance(SERVICE, HyprlandService): for ws in SERVICE.workspaces: all_workspaces_map[ws.id] = ws
if fixed_enabled and fixed_amount > 0: active_workspace = get_active_workspace() if active_workspace: if isinstance(SERVICE, NiriService): active_workspace_id = active_workspace.idx elif isinstance(SERVICE, HyprlandService): active_workspace_id = active_workspace.id else: active_workspace_id = None
if active_workspace_id is not None: if fixed_amount == 1: page_base_id = active_workspace_id else: page_base_id = ( (active_workspace_id - 1) // fixed_amount ) * fixed_amount + 1
start_id = page_base_id end_id = page_base_id + fixed_amount - 1 for i in range(start_id, end_id + 1): if i in all_workspaces_map: workspaces_to_display.append(all_workspaces_map[i]) else: workspaces_to_display.append(DummyWorkspace(i)) else: for i in range(1, fixed_amount + 1): if i in all_workspaces_map: workspaces_to_display.append(all_workspaces_map[i]) else: workspaces_to_display.append(DummyWorkspace(i)) else: workspaces_to_display = sorted( all_workspaces_map.values(), key=lambda ws: ws.idx if isinstance(SERVICE, NiriService) else ws.id, )
for workspace in workspaces_to_display: self._workspace_box.append(WorkspaceButton(workspace)) else: pass elif SERVICE: fixed_enabled = ( user_settings.interface.modules.options.fixed_workspaces_enabled ) if fixed_enabled: self._last_style = None self.update_workspaces() else: for child in self._workspace_box: if isinstance(child, WorkspaceButton) and current_style == "windows": child._update_icons()
def update_layout(self): bar = ( user_settings.interface.bar if user_settings.interface.modules.bar_id.workspaces == 0 else user_settings.interface.bar2 ) vertical = bar.vertical style = user_settings.interface.modules.options.workspaces_style
if style == "windows": spacing = 2 else: spacing = 5
self._workspace_box.set_vertical(vertical) self._workspace_box.set_spacing(spacing)
for child in self._workspace_box: if isinstance(child, WorkspaceButton): child.update_layout()
def workspaces_scroll(self, difference: int): if not SERVICE: return
active_workspace_id = None
if isinstance(SERVICE, NiriService): active_workspace_niri = next( (ws for ws in SERVICE.workspaces if ws.is_active), None ) if active_workspace_niri: active_workspace_id = active_workspace_niri.idx else: print("No active Niri workspace found.") return
elif isinstance(SERVICE, HyprlandService): active_workspace_hyprland = SERVICE.active_workspace if active_workspace_hyprland: active_workspace_id = active_workspace_hyprland.id else: print("No active Hyprland workspace found.") return
if active_workspace_id is not None: new_workspace_id = active_workspace_id + difference SERVICE.switch_to_workspace(new_workspace_id)
def widget(self): return self