├── .gitignore ├── README.md ├── example.py ├── hyprland ├── __init__.py ├── config.py ├── dispatch.py ├── events.py ├── gen │ ├── _util.py │ └── variables.py ├── info.py ├── obj │ ├── monitor.py │ ├── window.py │ └── workspace.py └── socket.py ├── makefile └── parse.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hyprland-py 2 | An unofficial python wrapper for Hyprland's IPC 3 | 4 | - [x] event listener 5 | - [x] change config options 6 | - [x] hyprland info 7 | - [x] dispatchers 8 | - [ ] binds 9 | - [x] window object 10 | - [x] monitor object 11 | - [x] workspace object 12 | - [ ] handle color values 13 | - [x] socket commands 14 | - [ ] docs 15 | - [ ] github action to update config options automagically 16 | 17 | # Install 18 | 19 | ### git 20 | 21 | from git 22 | ```py 23 | pip install git+https://github.com/hyprland-community/hyprland-py 24 | ``` 25 | 26 | # Example 27 | ```py 28 | import hyprland 29 | import asyncio 30 | 31 | hypr = hyprland.Events() 32 | 33 | @hypr.on("connect") 34 | async def on_connect(): 35 | print("Connected to the socket") 36 | 37 | @hypr.on("workspace") 38 | async def on_workspace(data): 39 | print(data) 40 | 41 | @hypr.on("activewindow") 42 | async def on_activewindow(win_class,title): 43 | print(win_class,title) 44 | 45 | print(hyprland.fetch_version()) 46 | 47 | async def main(): 48 | print(hyprland.fetch_workspaces()) 49 | await hypr.async_connect() 50 | 51 | # print(hyprland.Workspace.from_id(1)) 52 | 53 | print("starting") 54 | 55 | config = hyprland.config.Default() 56 | config.animations.enabled = True 57 | 58 | 59 | workspace = hyprland.Workspace.from_id(1) 60 | workspace.fetch_windows() 61 | 62 | # fetch all workspaces 63 | hyprland.fetch_workspaces() 64 | 65 | asyncio.run(main()) 66 | ``` -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | import hyprland 2 | import asyncio 3 | 4 | hypr = hyprland.Events() 5 | 6 | @hypr.on("connect") 7 | async def on_connect(): 8 | print("Connected to the socket") 9 | 10 | @hypr.on("workspace") 11 | async def on_workspace(data): 12 | print(data) 13 | 14 | @hypr.on("activewindow") 15 | async def on_activewindow(win_class,title): 16 | print(win_class,title) 17 | 18 | print(hyprland.fetch_version()) 19 | 20 | async def main(): 21 | print(hyprland.fetch_workspaces()) 22 | await hypr.async_connect()\ 23 | 24 | # print(hyprland.Workspace.from_id(1)) 25 | 26 | print("starting") 27 | 28 | config = hyprland.config.Default() 29 | config.animations.enabled = True 30 | 31 | 32 | workspace = hyprland.Workspace.from_id(1) 33 | workspace.fetch_windows() 34 | 35 | # fetch all workspaces 36 | hyprland.fetch_workspaces() 37 | 38 | asyncio.run(main()) -------------------------------------------------------------------------------- /hyprland/__init__.py: -------------------------------------------------------------------------------- 1 | from .events import * 2 | from .socket import * 3 | 4 | from .info import * 5 | 6 | from .dispatch import * 7 | 8 | from .config import * 9 | 10 | from .obj.monitor import * 11 | from .obj.workspace import * 12 | from .obj.window import * -------------------------------------------------------------------------------- /hyprland/config.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from .gen.variables import Default 3 | 4 | @dataclass 5 | class Color: 6 | r: int 7 | g: int 8 | b: int 9 | a: int 10 | 11 | def to_rgba(self)->str: 12 | return f'rgba({self.r}{self.g}{self.b}{self.a})' 13 | 14 | def to_rgb(self)->str: 15 | return f'rgb({self.r}{self.g}{self.b})' 16 | 17 | @staticmethod 18 | def from_str(dat:str): 19 | dat = dat.strip() 20 | if dat.startswith("rgb("): 21 | code = dat[4:-1] 22 | r = int(code[:2],16) 23 | g = int(code[2:4],16) 24 | b = int(code[4:],16) 25 | return Color(dat,r,g,b,255) 26 | elif dat.startswith("rgba("): 27 | code = dat[5:-1] 28 | r = int(code[:2],16) 29 | g = int(code[2:4],16) 30 | b = int(code[4:6],16) 31 | a = int(code[6:],16) 32 | return Color(dat,r,g,b,a) 33 | elif dat.startswith("0x"): 34 | code = dat[2:] 35 | a = int(code[:2],16) 36 | r = int(code[2:4],16) 37 | g = int(code[4:6],16) 38 | b = int(code[6:],16) 39 | return Color(dat,r,g,b,a) 40 | 41 | @staticmethod 42 | def rgba(r:int,g:int,b:int,a:int): 43 | r = hex(r)[2:] 44 | g = hex(g)[2:] 45 | b = hex(b)[2:] 46 | a = hex(a)[2:] 47 | 48 | r = r if len(r) == 2 else '0' + r 49 | g = g if len(g) == 2 else '0' + g 50 | b = b if len(b) == 2 else '0' + b 51 | a = a if len(a) == 2 else '0' + a 52 | 53 | return Color(r,g,b,a) 54 | 55 | @staticmethod 56 | def rgb(r:int,g:int,b:int): 57 | r = hex(r)[2:] 58 | g = hex(g)[2:] 59 | b = hex(b)[2:] 60 | 61 | r = r if len(r) == 2 else '0' + r 62 | g = g if len(g) == 2 else '0' + g 63 | b = b if len(b) == 2 else '0' + b 64 | 65 | return Color(r,g,b,255) 66 | 67 | @staticmethod 68 | def legacy(hex:str): 69 | hex = hex[1:] if hex[0] == '#' else hex 70 | hex = hex[2:] if hex[:2] == '0x' else hex 71 | a = hex[:2] 72 | r = hex[2:4] 73 | g = hex[4:6] 74 | b = hex[6:8] 75 | return Color(int(r,16),int(g,16),int(b,16),int(a,16)) 76 | 77 | 78 | @dataclass 79 | class Gradient: 80 | deg: float 81 | colors: list[Color] 82 | 83 | def to_str(self): 84 | return f"{' '.join(color.to_rgba() for color in self.colors)} {self.deg}deg" 85 | 86 | @staticmethod 87 | def from_colors(*colors:list[Color],deg:float = 0): 88 | return Gradient(f"{' '.join(color.value for color in colors)} {deg}deg",colors,deg) 89 | 90 | @staticmethod 91 | def from_str(dat:str): 92 | dat = dat.strip() 93 | segments = dat.split() 94 | if segments[-1].endswith("deg"): 95 | deg = int(segments[-1][:-3]) 96 | colors = segments[:-1] 97 | 98 | return Gradient(deg=deg,colors=[Color.from_str(color) for color in colors]) 99 | else: 100 | deg = 0 101 | colors = segments 102 | -------------------------------------------------------------------------------- /hyprland/dispatch.py: -------------------------------------------------------------------------------- 1 | from .socket import command_send, async_command_send 2 | from dataclasses import dataclass 3 | from enum import StrEnum 4 | from typing import TYPE_CHECKING 5 | 6 | if TYPE_CHECKING: 7 | from typing import Optional 8 | from .obj.window import WindowRule,WindowIdentity 9 | from .obj.workspace import WorkspaceIdentity 10 | 11 | class Direction(StrEnum): 12 | """Direction enum""" 13 | UP = "u" 14 | DOWN = "d" 15 | LEFT = "l" 16 | RIGHT = "r" 17 | 18 | @dataclass 19 | class ResizeParam: 20 | value:str 21 | exact:bool = False 22 | 23 | def pair(self,y:'ResizeParam'): 24 | return "exact "if self.exact else "" + self.value + " " + y.value 25 | 26 | @staticmethod 27 | def exact_px(value:int): 28 | return ResizeParam(f"{value}",exact=True) 29 | 30 | @staticmethod 31 | def exact_percent(value:float): 32 | return ResizeParam(f"{value}%",exact=True) 33 | 34 | @staticmethod 35 | def relative_px(value:int): 36 | return ResizeParam(f"{'+' if value>=0 else '-'}{value}") 37 | 38 | @staticmethod 39 | def relative_percent(value:float): 40 | return ResizeParam(f"{'+' if value>=0 else '-'}{value}%") 41 | 42 | def _dispatch(cmd:'str'): 43 | """sends a dispatch command""" 44 | return command_send(f"dispatch {cmd}",check_ok=True) 45 | 46 | def dispatch_exec(cmd:'str', rules:'list[WindowRule]'): 47 | """executes a shell command""" 48 | return _dispatch(f"exec [{','.join(r.rule for r in rules)}] {cmd}") 49 | 50 | def dispatch_execr(cmd:'str'): 51 | """executes a raw shell command (does not support rules)""" 52 | return _dispatch(f"execr {cmd}") 53 | 54 | def dispatch_pass(window:'WindowIdentity'): 55 | """passes the key (with mods) to a specified window. Can be used as a workaround to global keybinds not working on Wayland.""" 56 | return _dispatch(f"pass {window.identifier}") 57 | 58 | def dispatch_kill_active(): 59 | """closes (not kills) the active window""" 60 | return _dispatch("killactive") 61 | 62 | def dispatch_close_window(window:'WindowIdentity'): 63 | """closes a specified window""" 64 | return _dispatch(f"closewindow {window.identifier}") 65 | 66 | def dispatch_workspace(w:'WorkspaceIdentity'): 67 | """changes the workspace""" 68 | return _dispatch(f"workspace {w.identifier}") 69 | 70 | def dispatch_move_to_workspace(w:'WorkspaceIdentity',window:'Optional[WindowIdentity]'=None): 71 | """moves the focused window (or the specified window) to a workspace""" 72 | return _dispatch(f"movetoworkspace {w.identifier}{','+window.identifier if window else ''}") 73 | 74 | def dispatch_move_to_workspace_silent(w:'WorkspaceIdentity',window:'Optional[WindowIdentity]'=None): 75 | """moves the focused window (or the specified window) to a workspace without switching to workspace""" 76 | return _dispatch(f"movetoworkspacesilent {w.identifier}{','+window.identifier if window else ''}") 77 | 78 | def dispatch_toggle_floating(window:'Optional[WindowIdentity]'=None): 79 | """toggles the floating state of a window, if no window is specified, the active window is used.""" 80 | return _dispatch(f"togglefloating{' '+window.identifier if window else ''}") 81 | 82 | def dispatch_set_floating(window:'Optional[WindowIdentity]'=None): 83 | """sets the current window’s floating state to true""" 84 | return _dispatch(f"setfloating{' '+window.identifier if window else ''}") 85 | 86 | def dispatch_set_tiled(window:'Optional[WindowIdentity]'=None): 87 | """sets the current window’s floating state to false""" 88 | return _dispatch(f"settiled{' '+window.identifier if window else ''}") 89 | 90 | def dispatch_fullscreen(alter_window_state:bool=False): 91 | """toggles the focused window’s fullscreen state""" 92 | return _dispatch(f"fullscreen {2 if alter_window_state else 0}") 93 | 94 | def dispatch_maximize(): 95 | """toggles the focused window’s fullscreen state to maximize the window. This is different from fullscreen as it does not cover the entire screen.""" 96 | return _dispatch("fullscreen 1") 97 | 98 | def dispatch_fake_fullscreen(): 99 | """toggles the focused window’s internal fullscreen state without altering the geometry""" 100 | return _dispatch("fakefullscreen") 101 | 102 | def dispatch_dpms(state:bool,monitor_name:'str'=None): 103 | """sets the DPMS state of all monitors""" 104 | return _dispatch(f"dpms {'on' if state else 'off'}{' '+monitor_name if monitor_name else ''}") 105 | 106 | def dispatch_toggle_dpms(monitor_name:'str'=None): 107 | """toggles the DPMS state of all monitors""" 108 | return _dispatch(f"dpms toggle{' '+monitor_name if monitor_name else ''}") 109 | 110 | def dispatch_pin(window:'Optional[WindowIdentity]'=None): 111 | """pins a window (i.e. show it on all workspaces) note: floating only""" 112 | return _dispatch(f"pin{' '+window.identifier if window else ''}") 113 | 114 | def dispatch_move_focus(direction:'Direction'): 115 | """moves the focus in the specified direction""" 116 | return _dispatch(f"movefocus {direction.value}") 117 | 118 | def dispatch_move_window(direction:'Direction', monitor:'Optional[str]'=None, silent:bool=False): 119 | """moves the active window in a direction or to a monitor. For floating windows, moves the window to the screen edge in that direction""" 120 | return _dispatch(f"movewindow {direction.value}{' mon:'+monitor if monitor else ''}{' silent' if silent else ''}") 121 | 122 | def dispatch_swap_window(direction:'Direction'): 123 | """swaps the active window with another window in the given direction""" 124 | return _dispatch(f"swapwindow {direction.value}") 125 | 126 | def dispatch_center_window(respect_reserved:bool=False): 127 | """center the active window note: floating only""" 128 | return _dispatch(f"centerwindow{' 1' if respect_reserved else ''}") 129 | 130 | def dispatch_resize_active(x:ResizeParam,y:ResizeParam): 131 | """resizes the active window""" 132 | return _dispatch(f"resizeactive {x.pair(y)}") 133 | 134 | def dispatch_move_active(x:ResizeParam ,y:ResizeParam): 135 | """moves the active window""" 136 | return _dispatch(f"moveactive {x.pair(y)}") 137 | 138 | def dispatch_resize_window_pixel(x:ResizeParam, y:ResizeParam, regex:str): 139 | """resizes a selected window""" 140 | return _dispatch(f"resizewindow {x.pair(y)},{regex}") 141 | 142 | def dispatch_move_window_pixel(x:ResizeParam, y:ResizeParam, regex:str): 143 | """moves a selected window""" 144 | return _dispatch(f"movewindow {x.pair(y)},{regex}") 145 | 146 | def dispatch_cycle_next(prev:bool, tiled_only:bool, floating_only:bool): 147 | """focuses the next window on a workspace""" 148 | return _dispatch(f"cyclewindow {'prev' if prev else 'next'}{' tiled' if tiled_only else ''}{' floatingonly' if floating_only else ''}") 149 | 150 | def dispatch_swap_next(prev:bool): 151 | """swaps the focused window with the next window on a workspace""" 152 | return _dispatch(f"swapwindow {'prev' if prev else 'next'}") 153 | 154 | def dispatch_focus_window(window:'WindowIdentity'): 155 | """focuses the first window matching""" 156 | return _dispatch(f"focuswindow {window.identifier}") 157 | 158 | def dispatch_focus_monitor(monitor_name:'str'): 159 | """focuses the monitor""" 160 | return _dispatch(f"focusmonitor {monitor_name}") 161 | 162 | def dispatch_split_ratio(ratio:float): 163 | """changes the split ratio""" 164 | return _dispatch(f"splitratio {ratio}") 165 | 166 | def dispatch_toggle_opaque(): 167 | """toggles the current window to always be opaque. Will override the opaque window rules.""" 168 | return _dispatch("toggleopaque") 169 | 170 | def dispatch_cursor_to_top_left(): 171 | """moves the cursor to the top left corner of the screen""" 172 | return _dispatch("movecursortocorner 3") 173 | 174 | def dispatch_cursor_to_top_right(): 175 | """moves the cursor to the top right corner of the screen""" 176 | return _dispatch("movecursortocorner 2") 177 | 178 | def dispatch_cursor_to_bottom_left(): 179 | """moves the cursor to the bottom left corner of the screen""" 180 | return _dispatch("movecursortocorner 0") 181 | 182 | def dispatch_cursor_to_bottom_right(): 183 | """moves the cursor to the bottom right corner of the screen""" 184 | return _dispatch("movecursortocorner 1") 185 | 186 | def dispatch_move_cursor(x:int,y:int): 187 | """moves the cursor to the specified coordinates""" 188 | return _dispatch(f"movecursor {x} {y}") 189 | 190 | def dispatch_rename_workspace(id:str,name:str): 191 | """renames a workspace""" 192 | return _dispatch(f"renameworkspace {id} {name}") 193 | 194 | def dispatch_exit(): 195 | """exits the compositor with no questions asked.""" 196 | return _dispatch("exit") 197 | 198 | def dispatch_force_render_reload(): 199 | """forces the renderer to reload all resources and outputs""" 200 | return _dispatch("forcerenderreload") 201 | 202 | def dispatch_move_current_workspace_to_monitor(monitor_name:str): 203 | """moves the current workspace to the specified monitor""" 204 | return _dispatch(f"movecurrentworkspacetomonitor {monitor_name}") 205 | 206 | def dispatch_focus_workspace_on_current_monitor(w:'WorkspaceIdentity'): 207 | """Focuses the requested workspace on the current monitor, swapping the current workspace to a different monitor if necessary. If you want XMonad/Qtile-style workspace switching, replace workspace in your config with this.""" 208 | return _dispatch(f"focusworkspaceoncurrentmonitor {w.identifier}") 209 | 210 | def dispatch_move_workspace_to_monitor(w:'WorkspaceIdentity',monitor_name:str): 211 | """Moves a workspace to a monitor""" 212 | return _dispatch(f"moveworkspacetomonitor {w.identifier} {monitor_name}") 213 | 214 | def dispatch_swap_active_workspaces(monitor1_name:str, monitor2_name:str): 215 | """Swaps the active workspaces between two monitors""" 216 | return _dispatch(f"swapactiveworkspaces {monitor1_name} {monitor2_name}") 217 | 218 | def dispatch_alter_z_top(window:'Optional[WindowIdentity]'=None): 219 | """Modify the window stack order of the active or specified window to be on top. Note: this cannot be used to move a floating window behind a tiled one.""" 220 | return _dispatch(f"alterzorder top{','+window.identifier if window else ''}") 221 | 222 | def dispatch_alter_z_bottom(window:'Optional[WindowIdentity]'=None): 223 | """Modify the window stack order of the active or specified window to be on bottom. Note: this cannot be used to move a floating window behind a tiled one.""" 224 | return _dispatch(f"alterzorder bottom{','+window.identifier if window else ''}") 225 | 226 | def dispatch_toggle_special_workspace(name:'Optional[str]'=None): 227 | """toggles a special workspace on/off""" 228 | return _dispatch(f"togglespecialworkspace{' '+name if name else ''}") 229 | 230 | def dispatch_focus_urgent_or_last(): 231 | """Focuses the urgent window or the last window""" 232 | return _dispatch("focusurgentorlast") 233 | 234 | def dispatch_toggle_group(): 235 | """toggles the current active window into a group""" 236 | return _dispatch("togglegroup") 237 | 238 | def dispatch_change_group_active(prev:'bool', index:'Optional[int]'=None): 239 | """switches to the next window in a group.""" 240 | if index: 241 | return _dispatch(f"changegroup {index}") 242 | return _dispatch(f"changegroup {'b' if prev else 'f'}") 243 | 244 | def dispatch_focus_current_or_last(): 245 | """Switch focus from current to previously focused window""" 246 | return _dispatch("focuscurrentorlast") 247 | 248 | def dispatch_lock_groups(): 249 | """Locks the groups (all groups will not accept new windows)""" 250 | return _dispatch("lockgroups lock") 251 | 252 | def dispatch_unlock_groups(): 253 | """Unlocks the groups (all groups will accept new windows)""" 254 | return _dispatch("lockgroups unlock") 255 | 256 | def dispatch_toggle_lock_groups(): 257 | """Toggles the lock state of all groups""" 258 | return _dispatch("lockgroups toggle") 259 | 260 | def dispatch_lock_active_group(): 261 | """Locks the active group (the group will not accept new windows)""" 262 | return _dispatch("lockactivegroup lock") 263 | 264 | def dispatch_unlock_active_group(): 265 | """Unlocks the active group (the group will accept new windows)""" 266 | return _dispatch("lockactivegroup unlock") 267 | 268 | def dispatch_toggle_lock_active_group(): 269 | """Toggles the lock state of the active group""" 270 | return _dispatch("lockactivegroup toggle") 271 | 272 | def dispatch_move_into_group(direction:'Direction'): 273 | """Moves the active window into a group in a specified direction. No-op if there is no group in the specified direction.""" 274 | return _dispatch(f"moveintogroup {direction.value}") 275 | 276 | def dispatch_move_out_of_group(window:'Optional[WindowIdentity]'=None): 277 | """Moves the active window out of a group. No-op if not in a group""" 278 | return _dispatch(f"moveoutofgroup{' '+window.identifier if window else ''}") 279 | 280 | def dispatch_move_window_or_group(direction:'Direction'): 281 | """Behaves as moveintogroup if there is a group in the given direction. Behaves as moveoutofgroup if there is no group in the given direction relative to the active group. Otherwise behaves like movewindow.""" 282 | return _dispatch(f"movewindoworgroup {direction.value}") 283 | 284 | def dispatch_move_group_window(back:'bool'): 285 | """Swaps the active window with the next or previous in a group""" 286 | return _dispatch(f"movegroupwindow {'b' if back else 'nevergonnagiveyouup'}") 287 | 288 | def dispatch_deny_window_from_group(): 289 | """Prohibit the active window from becoming or being inserted into group""" 290 | return _dispatch("denywindowfromgroup on") 291 | 292 | def dispatch_allow_window_into_group(): 293 | """Allow the active window to become or be inserted into group""" 294 | return _dispatch("denywindowintogroup off") 295 | 296 | def dispatch_toggle_deny_window_into_group(): 297 | """Toggle the deny state of the active window from becoming or being inserted into group""" 298 | return _dispatch("denywindowintogroup toggle") 299 | 300 | def dispatch_enable_ignore_group_lock(): 301 | """Temporarily disable binds:ignore_group_lock""" 302 | return _dispatch("setignoregrouplock on") 303 | 304 | def dispatch_disable_ignore_group_lock(): 305 | """enables binds:ignore_group_lock""" 306 | return _dispatch("setignoregrouplock off") 307 | 308 | def dispatch_toggle_ignore_group_lock(): 309 | """toggles binds:ignore_group_lock""" 310 | return _dispatch("setignoregrouplock toggle") 311 | 312 | def dispatch_global(name:str): 313 | """Executes a Global Shortcut using the GlobalShortcuts portal""" 314 | return _dispatch(f"global {name}") 315 | 316 | def dispatch_submap(name:str): 317 | """Change the current mapping group. See """ 318 | return _dispatch(f"submap {name}") 319 | 320 | def dispatch_submap_reset(): 321 | """Reset the current mapping group""" 322 | return _dispatch("submap reset") 323 | 324 | -------------------------------------------------------------------------------- /hyprland/events.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from .socket import EventListener 3 | import asyncio 4 | 5 | class Events: 6 | def __init__(self): 7 | self.events = {} 8 | self.listener = None 9 | 10 | async def async_connect(self): 11 | self.listener = EventListener() 12 | async for event in self.listener.start(): 13 | if ">>" in event: 14 | event_name, args = event.split(">>") 15 | args = args.split(",") 16 | await self.emit(event_name, *args) 17 | else: 18 | await self.emit(event) 19 | 20 | def on(self, event: Optional[str] = None): 21 | def decorator(callback): 22 | self.add_handle(event, callback) 23 | return decorator 24 | 25 | def add_handle(self, event: str, callback: callable): 26 | if self.events.get(event): 27 | self.events[event].append(callback) 28 | else: 29 | self.events[event] = [callback] 30 | print(self.events) 31 | 32 | def remove_handle(self, event: str, callback: callable): 33 | if self.events.get(event): 34 | self.events[event].remove(callback) 35 | else: 36 | raise AttributeError(f'Event {event} not found') 37 | 38 | async def emit(self, event: str, *args, **kwargs): 39 | if event in self.events: 40 | for callback in self.events[event]: 41 | if asyncio.iscoroutinefunction(callback): 42 | await callback(*args, **kwargs) 43 | else: 44 | callback(*args, **kwargs) 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /hyprland/gen/_util.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from ..socket import command_send 3 | 4 | # class decorator 5 | def section(name:str): 6 | def decorator(cls): 7 | cls.__section_name = name 8 | return cls 9 | return decorator 10 | 11 | def unparse_values(value): 12 | if isinstance(value,tuple): 13 | return " ".join(*value) 14 | if isinstance(value,list): # mods 15 | return " ".join(*value) 16 | if value is int or value is float: 17 | return value 18 | if isinstance(value,bool): 19 | return "true" if value else "false" 20 | else: 21 | return value 22 | 23 | class Section: 24 | _section_name : str 25 | _section_map : dict 26 | 27 | _section_key : str 28 | 29 | def __setattr__(self, name: str, value: Any, ignore:bool = False) -> None: 30 | if not ignore and name in self._section_map: 31 | info = self._section_map[name] 32 | key = self._section_key + info["name"] 33 | cmd = f"keyword {key} {unparse_values(value)}" 34 | command_send(cmd,return_json=False,check_ok=True) 35 | super().__setattr__(name,value) 36 | else: 37 | super().__setattr__(name,value) 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /hyprland/gen/variables.py: -------------------------------------------------------------------------------- 1 | from ._util import Section 2 | KEYS = {'General': 'general:', 'Decoration': 'decoration:', 'Blur': 'decoration:blur:', 'Animations': 'animations:', 'Input': 'input:', 'Touchpad': 'input:touchpad:', 'Touchdevice': 'input:touchdevice:', 'Tablet': 'input:tablet:', 'PerDeviceInputConfig': 'per_device_input_config:', 'Gestures': 'gestures:', 'Group': 'group:', 'Groupbar': 'group:groupbar:', 'Misc': 'misc:', 'Binds': 'binds:', 'Xwayland': 'xwayland:', 'Opengl': 'opengl:', 'Cursor': 'cursor:', 'Debug': 'debug:', 'More': 'more:'} 3 | 4 | class General(Section): 5 | _section_name = "General" 6 | _section_map = {"sensitivity": {"name": "sensitivity", "description": "mouse sensitivity (legacy, may cause bugs if not 1, prefer `input:sensitivity`)", "type": "float", "default": "1.0"}, "border_size": {"name": "border_size", "description": "size of the border around windows", "type": "int", "default": "1"}, "no_border_on_floating": {"name": "no_border_on_floating", "description": "disable borders for floating windows", "type": "bool", "default": "false"}, "gaps_in": {"name": "gaps_in", "description": "gaps between windows, also supports css style gaps (top, right, bottom, left -> 5,10,15,20)", "type": "int", "default": "5"}, "gaps_out": {"name": "gaps_out", "description": "gaps between windows and monitor edges, also supports css style gaps (top, right, bottom, left -> 5,10,15,20)", "type": "int", "default": "20"}, "gaps_workspaces": {"name": "gaps_workspaces", "description": "gaps between workspaces. Stacks with gaps_out.", "type": "int", "default": "0"}, "col_inactive_border": {"name": "col.inactive_border", "description": "border color for inactive windows", "type": "gradient", "default": "0xff444444"}, "col_active_border": {"name": "col.active_border", "description": "border color for the active window", "type": "gradient", "default": "0xffffffff"}, "col_nogroup_border": {"name": "col.nogroup_border", "description": "inactive border color for window that cannot be added to a group (see `denywindowfromgroup` dispatcher)", "type": "gradient", "default": "0xffffaaff"}, "col_nogroup_border_active": {"name": "col.nogroup_border_active", "description": "active border color for window that cannot be added to a group", "type": "gradient", "default": "0xffff00ff"}, "layout": {"name": "layout", "description": "which layout to use. [dwindle/master]", "type": "str", "default": "dwindle"}, "no_focus_fallback": {"name": "no_focus_fallback", "description": "if true, will not fall back to the next available window when moving focus in a direction where no window was found", "type": "bool", "default": "false"}, "apply_sens_to_raw": {"name": "apply_sens_to_raw", "description": "if on, will also apply the sensitivity to raw mouse output (e.g. sensitivity in games) **NOTICE:** ***really*** not recommended.", "type": "bool", "default": "false"}, "resize_on_border": {"name": "resize_on_border", "description": "enables resizing windows by clicking and dragging on borders and gaps", "type": "bool", "default": "false"}, "extend_border_grab_area": {"name": "extend_border_grab_area", "description": "extends the area around the border where you can click and drag on, only used when `general:resize_on_border` is on.", "type": "int", "default": "15"}, "hover_icon_on_border": {"name": "hover_icon_on_border", "description": "show a cursor icon when hovering over borders, only used when `general:resize_on_border` is on.", "type": "bool", "default": "true"}, "allow_tearing": {"name": "allow_tearing", "description": "master switch for allowing tearing to occur. See [the Tearing page](../Tearing).", "type": "bool", "default": "false"}, "resize_corner": {"name": "resize_corner", "description": "force floating windows to use a specific corner when being resized (1-4 going clockwise from top left, 0 to disable)", "type": "int", "default": "0"}} 7 | _section_key = "general:" 8 | 9 | sensitivity: float = "1.0" 10 | # mouse sensitivity (legacy, may cause bugs if not 1, prefer `input:sensitivity`) 11 | border_size: int = "1" 12 | # size of the border around windows 13 | no_border_on_floating: bool = "False" 14 | # disable borders for floating windows 15 | gaps_in: int = "5" 16 | # gaps between windows, also supports css style gaps (top, right, bottom, left -> 5,10,15,20) 17 | gaps_out: int = "20" 18 | # gaps between windows and monitor edges, also supports css style gaps (top, right, bottom, left -> 5,10,15,20) 19 | gaps_workspaces: int = "0" 20 | # gaps between workspaces. Stacks with gaps_out. 21 | col_inactive_border: str = "0xff444444" 22 | # border color for inactive windows 23 | col_active_border: str = "0xffffffff" 24 | # border color for the active window 25 | col_nogroup_border: str = "0xffffaaff" 26 | # inactive border color for window that cannot be added to a group (see `denywindowfromgroup` dispatcher) 27 | col_nogroup_border_active: str = "0xffff00ff" 28 | # active border color for window that cannot be added to a group 29 | layout: str = "dwindle" 30 | # which layout to use. [dwindle/master] 31 | no_focus_fallback: bool = "False" 32 | # if true, will not fall back to the next available window when moving focus in a direction where no window was found 33 | apply_sens_to_raw: bool = "False" 34 | # if on, will also apply the sensitivity to raw mouse output (e.g. sensitivity in games) **NOTICE:** ***really*** not recommended. 35 | resize_on_border: bool = "False" 36 | # enables resizing windows by clicking and dragging on borders and gaps 37 | extend_border_grab_area: int = "15" 38 | # extends the area around the border where you can click and drag on, only used when `general:resize_on_border` is on. 39 | hover_icon_on_border: bool = "True" 40 | # show a cursor icon when hovering over borders, only used when `general:resize_on_border` is on. 41 | allow_tearing: bool = "False" 42 | # master switch for allowing tearing to occur. See [the Tearing page](../Tearing). 43 | resize_corner: int = "0" 44 | # force floating windows to use a specific corner when being resized (1-4 going clockwise from top left, 0 to disable) 45 | 46 | class Decoration(Section): 47 | _section_name = "Decoration" 48 | _section_map = {"rounding": {"name": "rounding", "description": "rounded corners' radius (in layout px)", "type": "int", "default": "0"}, "active_opacity": {"name": "active_opacity", "description": "opacity of active windows. [0.0 - 1.0]", "type": "float", "default": "1.0"}, "inactive_opacity": {"name": "inactive_opacity", "description": "opacity of inactive windows. [0.0 - 1.0]", "type": "float", "default": "1.0"}, "fullscreen_opacity": {"name": "fullscreen_opacity", "description": "opacity of fullscreen windows. [0.0 - 1.0]", "type": "float", "default": "1.0"}, "drop_shadow": {"name": "drop_shadow", "description": "enable drop shadows on windows", "type": "bool", "default": "true"}, "shadow_range": {"name": "shadow_range", "description": "Shadow range (\"size\") in layout px", "type": "int", "default": "4"}, "shadow_render_power": {"name": "shadow_render_power", "description": "in what power to render the falloff (more power, the faster the falloff) [1 - 4]", "type": "int", "default": "3"}, "shadow_ignore_window": {"name": "shadow_ignore_window", "description": "if true, the shadow will not be rendered behind the window itself, only around it.", "type": "bool", "default": "true"}, "col_shadow": {"name": "col.shadow", "description": "shadow's color. Alpha dictates shadow's opacity.", "type": "color", "default": "0xee1a1a1a"}, "col_shadow_inactive": {"name": "col.shadow_inactive", "description": "inactive shadow color. (if not set, will fall back to col.shadow)", "type": "color", "default": "unset"}, "shadow_offset": {"name": "shadow_offset", "description": "shadow's rendering offset.", "type": "vec2", "default": "[0, 0]"}, "shadow_scale": {"name": "shadow_scale", "description": "shadow's scale. [0.0 - 1.0]", "type": "float", "default": "1.0"}, "dim_inactive": {"name": "dim_inactive", "description": "enables dimming of inactive windows", "type": "bool", "default": "false"}, "dim_strength": {"name": "dim_strength", "description": "how much inactive windows should be dimmed [0.0 - 1.0]", "type": "float", "default": "0.5"}, "dim_special": {"name": "dim_special", "description": "how much to dim the rest of the screen by when a special workspace is open. [0.0 - 1.0]", "type": "float", "default": "0.2"}, "dim_around": {"name": "dim_around", "description": "how much the `dimaround` window rule should dim by. [0.0 - 1.0]", "type": "float", "default": "0.4"}, "screen_shader": {"name": "screen_shader", "description": "a path to a custom shader to be applied at the end of rendering. See `examples/screenShader.frag` for an example.", "type": "str", "default": "\\[\\[Empty\\]\\]"}} 49 | _section_key = "decoration:" 50 | 51 | rounding: int = "0" 52 | # rounded corners' radius (in layout px) 53 | active_opacity: float = "1.0" 54 | # opacity of active windows. [0.0 - 1.0] 55 | inactive_opacity: float = "1.0" 56 | # opacity of inactive windows. [0.0 - 1.0] 57 | fullscreen_opacity: float = "1.0" 58 | # opacity of fullscreen windows. [0.0 - 1.0] 59 | drop_shadow: bool = "True" 60 | # enable drop shadows on windows 61 | shadow_range: int = "4" 62 | # Shadow range ("size") in layout px 63 | shadow_render_power: int = "3" 64 | # in what power to render the falloff (more power, the faster the falloff) [1 - 4] 65 | shadow_ignore_window: bool = "True" 66 | # if true, the shadow will not be rendered behind the window itself, only around it. 67 | col_shadow: str = "0xee1a1a1a" 68 | # shadow's color. Alpha dictates shadow's opacity. 69 | col_shadow_inactive: str = "unset" 70 | # inactive shadow color. (if not set, will fall back to col.shadow) 71 | shadow_offset: tuple = "(0.0, 0.0)" 72 | # shadow's rendering offset. 73 | shadow_scale: float = "1.0" 74 | # shadow's scale. [0.0 - 1.0] 75 | dim_inactive: bool = "False" 76 | # enables dimming of inactive windows 77 | dim_strength: float = "0.5" 78 | # how much inactive windows should be dimmed [0.0 - 1.0] 79 | dim_special: float = "0.2" 80 | # how much to dim the rest of the screen by when a special workspace is open. [0.0 - 1.0] 81 | dim_around: float = "0.4" 82 | # how much the `dimaround` window rule should dim by. [0.0 - 1.0] 83 | screen_shader: str = None 84 | # a path to a custom shader to be applied at the end of rendering. See `examples/screenShader.frag` for an example. 85 | 86 | class Blur(Section): 87 | _section_name = "Blur" 88 | _section_map = {"enabled": {"name": "enabled", "description": "enable kawase window background blur", "type": "bool", "default": "true"}, "size": {"name": "size", "description": "blur size (distance)", "type": "int", "default": "8"}, "passes": {"name": "passes", "description": "the amount of passes to perform", "type": "int", "default": "1"}, "ignore_opacity": {"name": "ignore_opacity", "description": "make the blur layer ignore the opacity of the window", "type": "bool", "default": "false"}, "new_optimizations": {"name": "new_optimizations", "description": "whether to enable further optimizations to the blur. Recommended to leave on, as it will massively improve performance.", "type": "bool", "default": "true"}, "xray": {"name": "xray", "description": "if enabled, floating windows will ignore tiled windows in their blur. Only available if blur_new_optimizations is true. Will reduce overhead on floating blur significantly.", "type": "bool", "default": "false"}, "noise": {"name": "noise", "description": "how much noise to apply. [0.0 - 1.0]", "type": "float", "default": "0.0117"}, "contrast": {"name": "contrast", "description": "contrast modulation for blur. [0.0 - 2.0]", "type": "float", "default": "0.8916"}, "brightness": {"name": "brightness", "description": "brightness modulation for blur. [0.0 - 2.0]", "type": "float", "default": "0.8172"}, "vibrancy": {"name": "vibrancy", "description": "Increase saturation of blurred colors. [0.0 - 1.0]", "type": "float", "default": "0.1696"}, "vibrancy_darkness": {"name": "vibrancy_darkness", "description": "How strong the effect of `vibrancy` is on dark areas . [0.0 - 1.0]", "type": "float", "default": "0.0"}, "special": {"name": "special", "description": "whether to blur behind the special workspace (note: expensive)", "type": "bool", "default": "false"}, "popups": {"name": "popups", "description": "whether to blur popups (e.g. right-click menus)", "type": "bool", "default": "false"}, "popups_ignorealpha": {"name": "popups_ignorealpha", "description": "works like ignorealpha in layer rules. If pixel opacity is below set value, will not blur. [0.0 - 1.0]", "type": "float", "default": "0.2"}} 89 | _section_key = "decoration:blur:" 90 | 91 | enabled: bool = "True" 92 | # enable kawase window background blur 93 | size: int = "8" 94 | # blur size (distance) 95 | passes: int = "1" 96 | # the amount of passes to perform 97 | ignore_opacity: bool = "False" 98 | # make the blur layer ignore the opacity of the window 99 | new_optimizations: bool = "True" 100 | # whether to enable further optimizations to the blur. Recommended to leave on, as it will massively improve performance. 101 | xray: bool = "False" 102 | # if enabled, floating windows will ignore tiled windows in their blur. Only available if blur_new_optimizations is true. Will reduce overhead on floating blur significantly. 103 | noise: float = "0.0117" 104 | # how much noise to apply. [0.0 - 1.0] 105 | contrast: float = "0.8916" 106 | # contrast modulation for blur. [0.0 - 2.0] 107 | brightness: float = "0.8172" 108 | # brightness modulation for blur. [0.0 - 2.0] 109 | vibrancy: float = "0.1696" 110 | # Increase saturation of blurred colors. [0.0 - 1.0] 111 | vibrancy_darkness: float = "0.0" 112 | # How strong the effect of `vibrancy` is on dark areas . [0.0 - 1.0] 113 | special: bool = "False" 114 | # whether to blur behind the special workspace (note: expensive) 115 | popups: bool = "False" 116 | # whether to blur popups (e.g. right-click menus) 117 | popups_ignorealpha: float = "0.2" 118 | # works like ignorealpha in layer rules. If pixel opacity is below set value, will not blur. [0.0 - 1.0] 119 | 120 | class Animations(Section): 121 | _section_name = "Animations" 122 | _section_map = {"enabled": {"name": "enabled", "description": "enable animations", "type": "bool", "default": "true"}, "first_launch_animation": {"name": "first_launch_animation", "description": "enable first launch animation", "type": "bool", "default": "true"}} 123 | _section_key = "animations:" 124 | 125 | enabled: bool = "True" 126 | # enable animations 127 | first_launch_animation: bool = "True" 128 | # enable first launch animation 129 | 130 | class Input(Section): 131 | _section_name = "Input" 132 | _section_map = {"kb_model": {"name": "kb_model", "description": "Appropriate XKB keymap parameter. See the note below.", "type": "str", "default": "\\[\\[Empty\\]\\]"}, "kb_layout": {"name": "kb_layout", "description": "Appropriate XKB keymap parameter", "type": "str", "default": "us"}, "kb_variant": {"name": "kb_variant", "description": "Appropriate XKB keymap parameter", "type": "str", "default": "\\[\\[Empty\\]\\]"}, "kb_options": {"name": "kb_options", "description": "Appropriate XKB keymap parameter", "type": "str", "default": "\\[\\[Empty\\]\\]"}, "kb_rules": {"name": "kb_rules", "description": "Appropriate XKB keymap parameter", "type": "str", "default": "\\[\\[Empty\\]\\]"}, "kb_file": {"name": "kb_file", "description": "If you prefer, you can use a path to your custom .xkb file.", "type": "str", "default": "\\[\\[Empty\\]\\]"}, "numlock_by_default": {"name": "numlock_by_default", "description": "Engage numlock by default.", "type": "bool", "default": "false"}, "resolve_binds_by_sym": {"name": "resolve_binds_by_sym", "description": "Determines how keybinds act when multiple layouts are used. If false, keybinds will always act as if the first specified layout is active. If true, keybinds specified by symbols are activated when you type the respective symbol with the current layout.", "type": "bool", "default": "false"}, "repeat_rate": {"name": "repeat_rate", "description": "The repeat rate for held-down keys, in repeats per second.", "type": "int", "default": "25"}, "repeat_delay": {"name": "repeat_delay", "description": "Delay before a held-down key is repeated, in milliseconds.", "type": "int", "default": "600"}, "sensitivity": {"name": "sensitivity", "description": "Sets the mouse input sensitivity. Value is clamped to the range -1.0 to 1.0. [libinput#pointer-acceleration](https://wayland.freedesktop.org/libinput/doc/latest/pointer-acceleration.html#pointer-acceleration)", "type": "float", "default": "0.0"}, "accel_profile": {"name": "accel_profile", "description": "Sets the cursor acceleration profile. Can be one of `adaptive`, `flat`. Can also be `custom`, see [below](#custom-accel-profiles). Leave empty to use `libinput`'s default mode for your input device. [libinput#pointer-acceleration](https://wayland.freedesktop.org/libinput/doc/latest/pointer-acceleration.html#pointer-acceleration) [adaptive/flat/custom]", "type": "str", "default": "\\[\\[Empty\\]\\]"}, "force_no_accel": {"name": "force_no_accel", "description": "Force no cursor acceleration. This bypasses most of your pointer settings to get as raw of a signal as possible. **Enabling this is not recommended due to potential cursor desynchronization.**", "type": "bool", "default": "false"}, "left_handed": {"name": "left_handed", "description": "Switches RMB and LMB", "type": "bool", "default": "false"}, "scroll_points": {"name": "scroll_points", "description": "Sets the scroll acceleration profile, when `accel_profile` is set to `custom`. Has to be in the form ` `. Leave empty to have a flat scroll curve.", "type": "str", "default": "\\[\\[Empty\\]\\]"}, "scroll_method": {"name": "scroll_method", "description": "Sets the scroll method. Can be one of `2fg` (2 fingers), `edge`, `on_button_down`, `no_scroll`. [libinput#scrolling](https://wayland.freedesktop.org/libinput/doc/latest/scrolling.html) [2fg/edge/on_button_down/no_scroll]", "type": "str", "default": "\\[\\[Empty\\]\\]"}, "scroll_button": {"name": "scroll_button", "description": "Sets the scroll button. Has to be an int, cannot be a string. Check `wev` if you have any doubts regarding the ID. 0 means default.", "type": "int", "default": "0"}, "scroll_button_lock": {"name": "scroll_button_lock", "description": "If the scroll button lock is enabled, the button does not need to be held down. Pressing and releasing the button toggles the button lock, which logically holds the button down or releases it. While the button is logically held down, motion events are converted to scroll events.", "type": "bool", "default": "0"}, "scroll_factor": {"name": "scroll_factor", "description": "Multiplier added to scroll movement for external mice. Note that there is a separate setting for [touchpad scroll_factor](#touchpad).", "type": "float", "default": "1.0"}, "natural_scroll": {"name": "natural_scroll", "description": "Inverts scrolling direction. When enabled, scrolling moves content directly, rather than manipulating a scrollbar.", "type": "bool", "default": "false"}, "follow_mouse": {"name": "follow_mouse", "description": "Specify if and how cursor movement should affect window focus. See the note below. [0/1/2/3]", "type": "int", "default": "1"}, "mouse_refocus": {"name": "mouse_refocus", "description": "If disabled, mouse focus won't switch to the hovered window unless the mouse crosses a window boundary when `follow_mouse=1`.", "type": "bool", "default": "true"}, "float_switch_override_focus": {"name": "float_switch_override_focus", "description": "If enabled (1 or 2), focus will change to the window under the cursor when changing from tiled-to-floating and vice versa. If 2, focus will also follow mouse on float-to-float switches.", "type": "int", "default": "1"}, "special_fallthrough": {"name": "special_fallthrough", "description": "if enabled, having only floating windows in the special workspace will not block focusing windows in the regular workspace.", "type": "bool", "default": "false"}, "off_window_axis_events": {"name": "off_window_axis_events", "description": "Handles axis events around (gaps/border for tiled, dragarea/border for floated) a focused window. `0` ignores axis events `1` sends out-of-bound coordinates `2` fakes pointer coordinates to the closest point inside the window `3` warps the cursor to the closest point inside the window", "type": "int", "default": "1"}} 133 | _section_key = "input:" 134 | 135 | kb_model: str = None 136 | # Appropriate XKB keymap parameter. See the note below. 137 | kb_layout: str = "us" 138 | # Appropriate XKB keymap parameter 139 | kb_variant: str = None 140 | # Appropriate XKB keymap parameter 141 | kb_options: str = None 142 | # Appropriate XKB keymap parameter 143 | kb_rules: str = None 144 | # Appropriate XKB keymap parameter 145 | kb_file: str = None 146 | # If you prefer, you can use a path to your custom .xkb file. 147 | numlock_by_default: bool = "False" 148 | # Engage numlock by default. 149 | resolve_binds_by_sym: bool = "False" 150 | # Determines how keybinds act when multiple layouts are used. If false, keybinds will always act as if the first specified layout is active. If true, keybinds specified by symbols are activated when you type the respective symbol with the current layout. 151 | repeat_rate: int = "25" 152 | # The repeat rate for held-down keys, in repeats per second. 153 | repeat_delay: int = "600" 154 | # Delay before a held-down key is repeated, in milliseconds. 155 | sensitivity: float = "0.0" 156 | # Sets the mouse input sensitivity. Value is clamped to the range -1.0 to 1.0. [libinput#pointer-acceleration](https://wayland.freedesktop.org/libinput/doc/latest/pointer-acceleration.html#pointer-acceleration) 157 | accel_profile: str = None 158 | # Sets the cursor acceleration profile. Can be one of `adaptive`, `flat`. Can also be `custom`, see [below](#custom-accel-profiles). Leave empty to use `libinput`'s default mode for your input device. [libinput#pointer-acceleration](https://wayland.freedesktop.org/libinput/doc/latest/pointer-acceleration.html#pointer-acceleration) [adaptive/flat/custom] 159 | force_no_accel: bool = "False" 160 | # Force no cursor acceleration. This bypasses most of your pointer settings to get as raw of a signal as possible. **Enabling this is not recommended due to potential cursor desynchronization.** 161 | left_handed: bool = "False" 162 | # Switches RMB and LMB 163 | scroll_points: str = None 164 | # Sets the scroll acceleration profile, when `accel_profile` is set to `custom`. Has to be in the form ` `. Leave empty to have a flat scroll curve. 165 | scroll_method: str = None 166 | # Sets the scroll method. Can be one of `2fg` (2 fingers), `edge`, `on_button_down`, `no_scroll`. [libinput#scrolling](https://wayland.freedesktop.org/libinput/doc/latest/scrolling.html) [2fg/edge/on_button_down/no_scroll] 167 | scroll_button: int = "0" 168 | # Sets the scroll button. Has to be an int, cannot be a string. Check `wev` if you have any doubts regarding the ID. 0 means default. 169 | scroll_button_lock: bool = "False" 170 | # If the scroll button lock is enabled, the button does not need to be held down. Pressing and releasing the button toggles the button lock, which logically holds the button down or releases it. While the button is logically held down, motion events are converted to scroll events. 171 | scroll_factor: float = "1.0" 172 | # Multiplier added to scroll movement for external mice. Note that there is a separate setting for [touchpad scroll_factor](#touchpad). 173 | natural_scroll: bool = "False" 174 | # Inverts scrolling direction. When enabled, scrolling moves content directly, rather than manipulating a scrollbar. 175 | follow_mouse: int = "1" 176 | # Specify if and how cursor movement should affect window focus. See the note below. [0/1/2/3] 177 | mouse_refocus: bool = "True" 178 | # If disabled, mouse focus won't switch to the hovered window unless the mouse crosses a window boundary when `follow_mouse=1`. 179 | float_switch_override_focus: int = "1" 180 | # If enabled (1 or 2), focus will change to the window under the cursor when changing from tiled-to-floating and vice versa. If 2, focus will also follow mouse on float-to-float switches. 181 | special_fallthrough: bool = "False" 182 | # if enabled, having only floating windows in the special workspace will not block focusing windows in the regular workspace. 183 | off_window_axis_events: int = "1" 184 | # Handles axis events around (gaps/border for tiled, dragarea/border for floated) a focused window. `0` ignores axis events `1` sends out-of-bound coordinates `2` fakes pointer coordinates to the closest point inside the window `3` warps the cursor to the closest point inside the window 185 | 186 | class Touchpad(Section): 187 | _section_name = "Touchpad" 188 | _section_map = {"disable_while_typing": {"name": "disable_while_typing", "description": "Disable the touchpad while typing.", "type": "bool", "default": "true"}, "natural_scroll": {"name": "natural_scroll", "description": "Inverts scrolling direction. When enabled, scrolling moves content directly, rather than manipulating a scrollbar.", "type": "bool", "default": "false"}, "scroll_factor": {"name": "scroll_factor", "description": "Multiplier applied to the amount of scroll movement.", "type": "float", "default": "1.0"}, "middle_button_emulation": {"name": "middle_button_emulation", "description": "Sending LMB and RMB simultaneously will be interpreted as a middle click. This disables any touchpad area that would normally send a middle click based on location. [libinput#middle-button-emulation](https://wayland.freedesktop.org/libinput/doc/latest/middle-button-emulation.html)", "type": "bool", "default": "false"}, "tap_button_map": {"name": "tap_button_map", "description": "Sets the tap button mapping for touchpad button emulation. Can be one of `lrm` (default) or `lmr` (Left, Middle, Right Buttons). [lrm/lmr]", "type": "str", "default": "\\[\\[Empty\\]\\]"}, "clickfinger_behavior": {"name": "clickfinger_behavior", "description": "Button presses with 1, 2, or 3 fingers will be mapped to LMB, RMB, and MMB respectively. This disables interpretation of clicks based on location on the touchpad. [libinput#clickfinger-behavior](https://wayland.freedesktop.org/libinput/doc/latest/clickpad-softbuttons.html#clickfinger-behavior)", "type": "bool", "default": "false"}, "tap_to_click": {"name": "tap-to-click", "description": "Tapping on the touchpad with 1, 2, or 3 fingers will send LMB, RMB, and MMB respectively.", "type": "bool", "default": "true"}, "drag_lock": {"name": "drag_lock", "description": "When enabled, lifting the finger off for a short time while dragging will not drop the dragged item. [libinput#tap-and-drag](https://wayland.freedesktop.org/libinput/doc/latest/tapping.html#tap-and-drag)", "type": "bool", "default": "false"}, "tap_and_drag": {"name": "tap-and-drag", "description": "Sets the tap and drag mode for the touchpad", "type": "bool", "default": "false"}} 189 | _section_key = "input:touchpad:" 190 | 191 | disable_while_typing: bool = "True" 192 | # Disable the touchpad while typing. 193 | natural_scroll: bool = "False" 194 | # Inverts scrolling direction. When enabled, scrolling moves content directly, rather than manipulating a scrollbar. 195 | scroll_factor: float = "1.0" 196 | # Multiplier applied to the amount of scroll movement. 197 | middle_button_emulation: bool = "False" 198 | # Sending LMB and RMB simultaneously will be interpreted as a middle click. This disables any touchpad area that would normally send a middle click based on location. [libinput#middle-button-emulation](https://wayland.freedesktop.org/libinput/doc/latest/middle-button-emulation.html) 199 | tap_button_map: str = None 200 | # Sets the tap button mapping for touchpad button emulation. Can be one of `lrm` (default) or `lmr` (Left, Middle, Right Buttons). [lrm/lmr] 201 | clickfinger_behavior: bool = "False" 202 | # Button presses with 1, 2, or 3 fingers will be mapped to LMB, RMB, and MMB respectively. This disables interpretation of clicks based on location on the touchpad. [libinput#clickfinger-behavior](https://wayland.freedesktop.org/libinput/doc/latest/clickpad-softbuttons.html#clickfinger-behavior) 203 | tap_to_click: bool = "True" 204 | # Tapping on the touchpad with 1, 2, or 3 fingers will send LMB, RMB, and MMB respectively. 205 | drag_lock: bool = "False" 206 | # When enabled, lifting the finger off for a short time while dragging will not drop the dragged item. [libinput#tap-and-drag](https://wayland.freedesktop.org/libinput/doc/latest/tapping.html#tap-and-drag) 207 | tap_and_drag: bool = "False" 208 | # Sets the tap and drag mode for the touchpad 209 | 210 | class Touchdevice(Section): 211 | _section_name = "Touchdevice" 212 | _section_map = {"transform": {"name": "transform", "description": "Transform the input from touchdevices. The possible transformations are the same as [those of the monitors](../Monitors/#rotating)", "type": "int", "default": "0"}, "output": {"name": "output", "description": "The monitor to bind touch devices. The default is auto-detection. To stop auto-detection, use an empty string or the \"\\[\\[Empty\\]\\]\" value.", "type": "string", "default": "\\[\\[Auto\\]\\]"}, "enabled": {"name": "enabled", "description": "Whether input is enabled for touch devices.", "type": "bool", "default": "true"}} 213 | _section_key = "input:touchdevice:" 214 | 215 | transform: int = "0" 216 | # Transform the input from touchdevices. The possible transformations are the same as [those of the monitors](../Monitors/#rotating) 217 | output: str = "auto" 218 | # The monitor to bind touch devices. The default is auto-detection. To stop auto-detection, use an empty string or the "\[\[Empty\]\]" value. 219 | enabled: bool = "True" 220 | # Whether input is enabled for touch devices. 221 | 222 | class Tablet(Section): 223 | _section_name = "Tablet" 224 | _section_map = {"transform": {"name": "transform", "description": "transform the input from tablets. The possible transformations are the same as [those of the monitors](../Monitors/#rotating)", "type": "int", "default": "0"}, "output": {"name": "output", "description": "the monitor to bind tablets. Empty means unbound.", "type": "string", "default": "\\[\\[Empty\\]\\]"}, "region_position": {"name": "region_position", "description": "position of the mapped region in monitor layout.", "type": "vec2", "default": "[0, 0]"}, "region_size": {"name": "region_size", "description": "size of the mapped region. When this variable is set, tablet input will be mapped to the region. [0, 0] or invalid size means unset.", "type": "vec2", "default": "[0, 0]"}, "relative_input": {"name": "relative_input", "description": "whether the input should be relative", "type": "bool", "default": "false"}, "left_handed": {"name": "left_handed", "description": "if enabled, the tablet will be rotated 180 degrees", "type": "bool", "default": "false"}, "active_area_size": {"name": "active_area_size", "description": "size of tablet's active area in mm", "type": "vec2", "default": "[0, 0]"}, "active_area_position": {"name": "active_area_position", "description": "position of the active area in mm", "type": "vec2", "default": "[0, 0]"}} 225 | _section_key = "input:tablet:" 226 | 227 | transform: int = "0" 228 | # transform the input from tablets. The possible transformations are the same as [those of the monitors](../Monitors/#rotating) 229 | output: str = None 230 | # the monitor to bind tablets. Empty means unbound. 231 | region_position: tuple = "(0.0, 0.0)" 232 | # position of the mapped region in monitor layout. 233 | region_size: tuple = "(0.0, 0.0)" 234 | # size of the mapped region. When this variable is set, tablet input will be mapped to the region. [0, 0] or invalid size means unset. 235 | relative_input: bool = "False" 236 | # whether the input should be relative 237 | left_handed: bool = "False" 238 | # if enabled, the tablet will be rotated 180 degrees 239 | active_area_size: tuple = "(0.0, 0.0)" 240 | # size of tablet's active area in mm 241 | active_area_position: tuple = "(0.0, 0.0)" 242 | # position of the active area in mm 243 | 244 | class PerDeviceInputConfig(Section): 245 | _section_name = "Per-device input config" 246 | _section_map = {} 247 | _section_key = "per_device_input_config:" 248 | 249 | 250 | class Gestures(Section): 251 | _section_name = "Gestures" 252 | _section_map = {"workspace_swipe": {"name": "workspace_swipe", "description": "enable workspace swipe gesture on touchpad", "type": "bool", "default": "false"}, "workspace_swipe_fingers": {"name": "workspace_swipe_fingers", "description": "how many fingers for the touchpad gesture", "type": "int", "default": "3"}, "workspace_swipe_distance": {"name": "workspace_swipe_distance", "description": "in px, the distance of the touchpad gesture", "type": "int", "default": "300"}, "workspace_swipe_touch": {"name": "workspace_swipe_touch", "description": "enable workspace swiping from the edge of a touchscreen", "type": "bool", "default": "false"}, "workspace_swipe_invert": {"name": "workspace_swipe_invert", "description": "invert the direction", "type": "bool", "default": "true"}, "workspace_swipe_min_speed_to_force": {"name": "workspace_swipe_min_speed_to_force", "description": "minimum speed in px per timepoint to force the change ignoring `cancel_ratio`. Setting to `0` will disable this mechanic.", "type": "int", "default": "30"}, "workspace_swipe_cancel_ratio": {"name": "workspace_swipe_cancel_ratio", "description": "how much the swipe has to proceed in order to commence it. (0.7 -> if > 0.7 * distance, switch, if less, revert) [0.0 - 1.0]", "type": "float", "default": "0.5"}, "workspace_swipe_create_new": {"name": "workspace_swipe_create_new", "description": "whether a swipe right on the last workspace should create a new one.", "type": "bool", "default": "true"}, "workspace_swipe_direction_lock": {"name": "workspace_swipe_direction_lock", "description": "if enabled, switching direction will be locked when you swipe past the `direction_lock_threshold` (touchpad only).", "type": "bool", "default": "true"}, "workspace_swipe_direction_lock_threshold": {"name": "workspace_swipe_direction_lock_threshold", "description": "in px, the distance to swipe before direction lock activates (touchpad only).", "type": "int", "default": "10"}, "workspace_swipe_forever": {"name": "workspace_swipe_forever", "description": "if enabled, swiping will not clamp at the neighboring workspaces but continue to the further ones.", "type": "bool", "default": "false"}, "workspace_swipe_use_r": {"name": "workspace_swipe_use_r", "description": "if enabled, swiping will use the `r` prefix instead of the `m` prefix for finding workspaces.", "type": "bool", "default": "false"}} 253 | _section_key = "gestures:" 254 | 255 | workspace_swipe: bool = "False" 256 | # enable workspace swipe gesture on touchpad 257 | workspace_swipe_fingers: int = "3" 258 | # how many fingers for the touchpad gesture 259 | workspace_swipe_distance: int = "300" 260 | # in px, the distance of the touchpad gesture 261 | workspace_swipe_touch: bool = "False" 262 | # enable workspace swiping from the edge of a touchscreen 263 | workspace_swipe_invert: bool = "True" 264 | # invert the direction 265 | workspace_swipe_min_speed_to_force: int = "30" 266 | # minimum speed in px per timepoint to force the change ignoring `cancel_ratio`. Setting to `0` will disable this mechanic. 267 | workspace_swipe_cancel_ratio: float = "0.5" 268 | # how much the swipe has to proceed in order to commence it. (0.7 -> if > 0.7 * distance, switch, if less, revert) [0.0 - 1.0] 269 | workspace_swipe_create_new: bool = "True" 270 | # whether a swipe right on the last workspace should create a new one. 271 | workspace_swipe_direction_lock: bool = "True" 272 | # if enabled, switching direction will be locked when you swipe past the `direction_lock_threshold` (touchpad only). 273 | workspace_swipe_direction_lock_threshold: int = "10" 274 | # in px, the distance to swipe before direction lock activates (touchpad only). 275 | workspace_swipe_forever: bool = "False" 276 | # if enabled, swiping will not clamp at the neighboring workspaces but continue to the further ones. 277 | workspace_swipe_use_r: bool = "False" 278 | # if enabled, swiping will use the `r` prefix instead of the `m` prefix for finding workspaces. 279 | 280 | class Group(Section): 281 | _section_name = "Group" 282 | _section_map = {"insert_after_current": {"name": "insert_after_current", "description": "whether new windows in a group spawn after current or at group tail", "type": "bool", "default": "true"}, "focus_removed_window": {"name": "focus_removed_window", "description": "whether Hyprland should focus on the window that has just been moved out of the group", "type": "bool", "default": "true"}, "col_border_active": {"name": "col.border_active", "description": "active group border color", "type": "gradient", "default": "0x66ffff00"}, "col_border_inactive": {"name": "col.border_inactive", "description": "inactive (out of focus) group border color", "type": "gradient", "default": "0x66777700"}, "col_border_locked_active": {"name": "col.border_locked_active", "description": "active locked group border color", "type": "gradient", "default": "0x66ff5500"}, "col_border_locked_inactive": {"name": "col.border_locked_inactive", "description": "inactive locked group border color", "type": "gradient", "default": "0x66775500"}} 283 | _section_key = "group:" 284 | 285 | insert_after_current: bool = "True" 286 | # whether new windows in a group spawn after current or at group tail 287 | focus_removed_window: bool = "True" 288 | # whether Hyprland should focus on the window that has just been moved out of the group 289 | col_border_active: str = "0x66ffff00" 290 | # active group border color 291 | col_border_inactive: str = "0x66777700" 292 | # inactive (out of focus) group border color 293 | col_border_locked_active: str = "0x66ff5500" 294 | # active locked group border color 295 | col_border_locked_inactive: str = "0x66775500" 296 | # inactive locked group border color 297 | 298 | class Groupbar(Section): 299 | _section_name = "Groupbar" 300 | _section_map = {"enabled": {"name": "enabled", "description": "enables groupbars", "type": "bool", "default": "true"}, "font_family": {"name": "font_family", "description": "font used to display groupbar titles, use `misc:font_family` if not specified", "type": "string", "default": "[[Empty]]"}, "font_size": {"name": "font_size", "description": "font size of groupbar title", "type": "int", "default": "8"}, "gradients": {"name": "gradients", "description": "enables gradients", "type": "bool", "default": "true"}, "height": {"name": "height", "description": "height of the groupbar", "type": "int", "default": "14"}, "stacked": {"name": "stacked", "description": "render the groupbar as a vertical stack", "type": "bool", "default": "false"}, "priority": {"name": "priority", "description": "sets the decoration priority for groupbars", "type": "int", "default": "3"}, "render_titles": {"name": "render_titles", "description": "whether to render titles in the group bar decoration", "type": "bool", "default": "true"}, "scrolling": {"name": "scrolling", "description": "whether scrolling in the groupbar changes group active window", "type": "bool", "default": "true"}, "text_color": {"name": "text_color", "description": "controls the group bar text color", "type": "color", "default": "0xffffffff"}, "col_active": {"name": "col.active", "description": "active group border color", "type": "gradient", "default": "0x66ffff00"}, "col_inactive": {"name": "col.inactive", "description": "inactive (out of focus) group border color", "type": "gradient", "default": "0x66777700"}, "col_locked_active": {"name": "col.locked_active", "description": "active locked group border color", "type": "gradient", "default": "0x66ff5500"}, "col_locked_inactive": {"name": "col.locked_inactive", "description": "inactive locked group border color", "type": "gradient", "default": "0x66775500"}} 301 | _section_key = "group:groupbar:" 302 | 303 | enabled: bool = "True" 304 | # enables groupbars 305 | font_family: str = "[[Empty]]" 306 | # font used to display groupbar titles, use `misc:font_family` if not specified 307 | font_size: int = "8" 308 | # font size of groupbar title 309 | gradients: bool = "True" 310 | # enables gradients 311 | height: int = "14" 312 | # height of the groupbar 313 | stacked: bool = "False" 314 | # render the groupbar as a vertical stack 315 | priority: int = "3" 316 | # sets the decoration priority for groupbars 317 | render_titles: bool = "True" 318 | # whether to render titles in the group bar decoration 319 | scrolling: bool = "True" 320 | # whether scrolling in the groupbar changes group active window 321 | text_color: str = "0xffffffff" 322 | # controls the group bar text color 323 | col_active: str = "0x66ffff00" 324 | # active group border color 325 | col_inactive: str = "0x66777700" 326 | # inactive (out of focus) group border color 327 | col_locked_active: str = "0x66ff5500" 328 | # active locked group border color 329 | col_locked_inactive: str = "0x66775500" 330 | # inactive locked group border color 331 | 332 | class Misc(Section): 333 | _section_name = "Misc" 334 | _section_map = {"disable_hyprland_logo": {"name": "disable_hyprland_logo", "description": "disables the random Hyprland logo / anime girl background. :(", "type": "bool", "default": "false"}, "disable_splash_rendering": {"name": "disable_splash_rendering", "description": "disables the Hyprland splash rendering. (requires a monitor reload to take effect)", "type": "bool", "default": "false"}, "col_splash": {"name": "col.splash", "description": "Changes the color of the splash text (requires a monitor reload to take effect).", "type": "color", "default": "0xffffffff"}, "font_family": {"name": "font_family", "description": "Set the global default font to render the text including debug fps/notification, config error messages and etc., selected from system fonts.", "type": "string", "default": "Sans"}, "splash_font_family": {"name": "splash_font_family", "description": "Changes the font used to render the splash text, selected from system fonts (requires a monitor reload to take effect).", "type": "string", "default": "[[Empty]]"}, "force_default_wallpaper": {"name": "force_default_wallpaper", "description": "Enforce any of the 3 default wallpapers. Setting this to `0` or `1` disables the anime background. `-1` means \"random\". [-1/0/1/2]", "type": "int", "default": "-1"}, "vfr": {"name": "vfr", "description": "controls the VFR status of Hyprland. Heavily recommended to leave enabled to conserve resources.", "type": "bool", "default": "true"}, "vrr": {"name": "vrr", "description": "controls the VRR (Adaptive Sync) of your monitors. 0 - off, 1 - on, 2 - fullscreen only [0/1/2]", "type": "int", "default": "0"}, "mouse_move_enables_dpms": {"name": "mouse_move_enables_dpms", "description": "If DPMS is set to off, wake up the monitors if the mouse moves.", "type": "bool", "default": "false"}, "key_press_enables_dpms": {"name": "key_press_enables_dpms", "description": "If DPMS is set to off, wake up the monitors if a key is pressed.", "type": "bool", "default": "false"}, "always_follow_on_dnd": {"name": "always_follow_on_dnd", "description": "Will make mouse focus follow the mouse when drag and dropping. Recommended to leave it enabled, especially for people using focus follows mouse at 0.", "type": "bool", "default": "true"}, "layers_hog_keyboard_focus": {"name": "layers_hog_keyboard_focus", "description": "If true, will make keyboard-interactive layers keep their focus on mouse move (e.g. wofi, bemenu)", "type": "bool", "default": "true"}, "animate_manual_resizes": {"name": "animate_manual_resizes", "description": "If true, will animate manual window resizes/moves", "type": "bool", "default": "false"}, "animate_mouse_windowdragging": {"name": "animate_mouse_windowdragging", "description": "If true, will animate windows being dragged by mouse, note that this can cause weird behavior on some curves", "type": "bool", "default": "false"}, "disable_autoreload": {"name": "disable_autoreload", "description": "If true, the config will not reload automatically on save, and instead needs to be reloaded with `hyprctl reload`. Might save on battery.", "type": "bool", "default": "false"}, "enable_swallow": {"name": "enable_swallow", "description": "Enable window swallowing", "type": "bool", "default": "false"}, "swallow_regex": {"name": "swallow_regex", "description": "The *class* regex to be used for windows that should be swallowed (usually, a terminal). To know more about the list of regex which can be used [use this cheatsheet](https://github.com/ziishaned/learn-regex/blob/master/README.md).", "type": "str", "default": "\\[\\[Empty\\]\\]"}, "swallow_exception_regex": {"name": "swallow_exception_regex", "description": "The *title* regex to be used for windows that should *not* be swallowed by the windows specified in swallow_regex (e.g. wev). The regex is matched against the parent (e.g. Kitty) window's title on the assumption that it changes to whatever process it's running.", "type": "str", "default": "\\[\\[Empty\\]\\]"}, "focus_on_activate": {"name": "focus_on_activate", "description": "Whether Hyprland should focus an app that requests to be focused (an `activate` request)", "type": "bool", "default": "false"}, "no_direct_scanout": {"name": "no_direct_scanout", "description": "Disables direct scanout. Direct scanout attempts to reduce lag when there is only one fullscreen application on a screen (e.g. game). It is also recommended to set this to true if the fullscreen application shows graphical glitches.", "type": "bool", "default": "true"}, "mouse_move_focuses_monitor": {"name": "mouse_move_focuses_monitor", "description": "Whether mouse moving into a different monitor should focus it", "type": "bool", "default": "true"}, "suppress_portal_warnings": {"name": "suppress_portal_warnings", "description": "disables warnings about incompatible portal implementations.", "type": "bool", "default": "false"}, "render_ahead_of_time": {"name": "render_ahead_of_time", "description": "[Warning: buggy] starts rendering _before_ your monitor displays a frame in order to lower latency", "type": "bool", "default": "false"}, "render_ahead_safezone": {"name": "render_ahead_safezone", "description": "how many ms of safezone to add to rendering ahead of time. Recommended 1-2.", "type": "int", "default": "1"}, "allow_session_lock_restore": {"name": "allow_session_lock_restore", "description": "if true, will allow you to restart a lockscreen app in case it crashes (red screen of death)", "type": "bool", "default": "false"}, "background_color": {"name": "background_color", "description": "change the background color. (requires enabled `disable_hyprland_logo`)", "type": "color", "default": "0x111111"}, "close_special_on_empty": {"name": "close_special_on_empty", "description": "close the special workspace if the last window is removed", "type": "bool", "default": "true"}, "new_window_takes_over_fullscreen": {"name": "new_window_takes_over_fullscreen", "description": "if there is a fullscreen window, whether a new tiled window opened should replace the fullscreen one or stay behind. 0 - behind, 1 - takes over, 2 - unfullscreen the current fullscreen window [0/1/2]", "type": "int", "default": "0"}, "initial_workspace_tracking": {"name": "initial_workspace_tracking", "description": "if enabled, windows will open on the workspace they were invoked on. 0 - disabled, 1 - single-shot, 2 - persistent (all children too)", "type": "int", "default": "1"}, "middle_click_paste": {"name": "middle_click_paste", "description": "whether to enable middle-click-paste (aka primary selection)", "type": "bool", "default": "true"}} 335 | _section_key = "misc:" 336 | 337 | disable_hyprland_logo: bool = "False" 338 | # disables the random Hyprland logo / anime girl background. :( 339 | disable_splash_rendering: bool = "False" 340 | # disables the Hyprland splash rendering. (requires a monitor reload to take effect) 341 | col_splash: str = "0xffffffff" 342 | # Changes the color of the splash text (requires a monitor reload to take effect). 343 | font_family: str = "Sans" 344 | # Set the global default font to render the text including debug fps/notification, config error messages and etc., selected from system fonts. 345 | splash_font_family: str = "[[Empty]]" 346 | # Changes the font used to render the splash text, selected from system fonts (requires a monitor reload to take effect). 347 | force_default_wallpaper: int = "-1" 348 | # Enforce any of the 3 default wallpapers. Setting this to `0` or `1` disables the anime background. `-1` means "random". [-1/0/1/2] 349 | vfr: bool = "True" 350 | # controls the VFR status of Hyprland. Heavily recommended to leave enabled to conserve resources. 351 | vrr: int = "0" 352 | # controls the VRR (Adaptive Sync) of your monitors. 0 - off, 1 - on, 2 - fullscreen only [0/1/2] 353 | mouse_move_enables_dpms: bool = "False" 354 | # If DPMS is set to off, wake up the monitors if the mouse moves. 355 | key_press_enables_dpms: bool = "False" 356 | # If DPMS is set to off, wake up the monitors if a key is pressed. 357 | always_follow_on_dnd: bool = "True" 358 | # Will make mouse focus follow the mouse when drag and dropping. Recommended to leave it enabled, especially for people using focus follows mouse at 0. 359 | layers_hog_keyboard_focus: bool = "True" 360 | # If true, will make keyboard-interactive layers keep their focus on mouse move (e.g. wofi, bemenu) 361 | animate_manual_resizes: bool = "False" 362 | # If true, will animate manual window resizes/moves 363 | animate_mouse_windowdragging: bool = "False" 364 | # If true, will animate windows being dragged by mouse, note that this can cause weird behavior on some curves 365 | disable_autoreload: bool = "False" 366 | # If true, the config will not reload automatically on save, and instead needs to be reloaded with `hyprctl reload`. Might save on battery. 367 | enable_swallow: bool = "False" 368 | # Enable window swallowing 369 | swallow_regex: str = None 370 | # The *class* regex to be used for windows that should be swallowed (usually, a terminal). To know more about the list of regex which can be used [use this cheatsheet](https://github.com/ziishaned/learn-regex/blob/master/README.md). 371 | swallow_exception_regex: str = None 372 | # The *title* regex to be used for windows that should *not* be swallowed by the windows specified in swallow_regex (e.g. wev). The regex is matched against the parent (e.g. Kitty) window's title on the assumption that it changes to whatever process it's running. 373 | focus_on_activate: bool = "False" 374 | # Whether Hyprland should focus an app that requests to be focused (an `activate` request) 375 | no_direct_scanout: bool = "True" 376 | # Disables direct scanout. Direct scanout attempts to reduce lag when there is only one fullscreen application on a screen (e.g. game). It is also recommended to set this to true if the fullscreen application shows graphical glitches. 377 | mouse_move_focuses_monitor: bool = "True" 378 | # Whether mouse moving into a different monitor should focus it 379 | suppress_portal_warnings: bool = "False" 380 | # disables warnings about incompatible portal implementations. 381 | render_ahead_of_time: bool = "False" 382 | # [Warning: buggy] starts rendering _before_ your monitor displays a frame in order to lower latency 383 | render_ahead_safezone: int = "1" 384 | # how many ms of safezone to add to rendering ahead of time. Recommended 1-2. 385 | allow_session_lock_restore: bool = "False" 386 | # if true, will allow you to restart a lockscreen app in case it crashes (red screen of death) 387 | background_color: str = "0x111111" 388 | # change the background color. (requires enabled `disable_hyprland_logo`) 389 | close_special_on_empty: bool = "True" 390 | # close the special workspace if the last window is removed 391 | new_window_takes_over_fullscreen: int = "0" 392 | # if there is a fullscreen window, whether a new tiled window opened should replace the fullscreen one or stay behind. 0 - behind, 1 - takes over, 2 - unfullscreen the current fullscreen window [0/1/2] 393 | initial_workspace_tracking: int = "1" 394 | # if enabled, windows will open on the workspace they were invoked on. 0 - disabled, 1 - single-shot, 2 - persistent (all children too) 395 | middle_click_paste: bool = "True" 396 | # whether to enable middle-click-paste (aka primary selection) 397 | 398 | class Binds(Section): 399 | _section_name = "Binds" 400 | _section_map = {"pass_mouse_when_bound": {"name": "pass_mouse_when_bound", "description": "if disabled, will not pass the mouse events to apps / dragging windows around if a keybind has been triggered.", "type": "bool", "default": "false"}, "scroll_event_delay": {"name": "scroll_event_delay", "description": "in ms, how many ms to wait after a scroll event to allow passing another one for the binds.", "type": "int", "default": "300"}, "workspace_back_and_forth": {"name": "workspace_back_and_forth", "description": "If enabled, an attempt to switch to the currently focused workspace will instead switch to the previous workspace. Akin to i3's _auto_back_and_forth_.", "type": "bool", "default": "false"}, "allow_workspace_cycles": {"name": "allow_workspace_cycles", "description": "If enabled, workspaces don't forget their previous workspace, so cycles can be created by switching to the first workspace in a sequence, then endlessly going to the previous workspace.", "type": "bool", "default": "false"}, "workspace_center_on": {"name": "workspace_center_on", "description": "Whether switching workspaces should center the cursor on the workspace (0) or on the last active window for that workspace (1)", "type": "int", "default": "0"}, "focus_preferred_method": {"name": "focus_preferred_method", "description": "sets the preferred focus finding method when using `focuswindow`/`movewindow`/etc with a direction. 0 - history (recent have priority), 1 - length (longer shared edges have priority)", "type": "int", "default": "0"}, "ignore_group_lock": {"name": "ignore_group_lock", "description": "If enabled, dispatchers like `moveintogroup`, `moveoutofgroup` and `movewindoworgroup` will ignore lock per group.", "type": "bool", "default": "false"}, "movefocus_cycles_fullscreen": {"name": "movefocus_cycles_fullscreen", "description": "If enabled, when on a fullscreen window, `movefocus` will cycle fullscreen, if not, it will move the focus in a direction.", "type": "bool", "default": "true"}, "disable_keybind_grabbing": {"name": "disable_keybind_grabbing", "description": "If enabled, apps that request keybinds to be disabled (e.g. VMs) will not be able to do so.", "type": "bool", "default": "false"}, "window_direction_monitor_fallback": {"name": "window_direction_monitor_fallback", "description": "If enabled, moving a window or focus over the edge of a monitor with a direction will move it to the next monitor in that direction.", "type": "bool", "default": "true"}} 401 | _section_key = "binds:" 402 | 403 | pass_mouse_when_bound: bool = "False" 404 | # if disabled, will not pass the mouse events to apps / dragging windows around if a keybind has been triggered. 405 | scroll_event_delay: int = "300" 406 | # in ms, how many ms to wait after a scroll event to allow passing another one for the binds. 407 | workspace_back_and_forth: bool = "False" 408 | # If enabled, an attempt to switch to the currently focused workspace will instead switch to the previous workspace. Akin to i3's _auto_back_and_forth_. 409 | allow_workspace_cycles: bool = "False" 410 | # If enabled, workspaces don't forget their previous workspace, so cycles can be created by switching to the first workspace in a sequence, then endlessly going to the previous workspace. 411 | workspace_center_on: int = "0" 412 | # Whether switching workspaces should center the cursor on the workspace (0) or on the last active window for that workspace (1) 413 | focus_preferred_method: int = "0" 414 | # sets the preferred focus finding method when using `focuswindow`/`movewindow`/etc with a direction. 0 - history (recent have priority), 1 - length (longer shared edges have priority) 415 | ignore_group_lock: bool = "False" 416 | # If enabled, dispatchers like `moveintogroup`, `moveoutofgroup` and `movewindoworgroup` will ignore lock per group. 417 | movefocus_cycles_fullscreen: bool = "True" 418 | # If enabled, when on a fullscreen window, `movefocus` will cycle fullscreen, if not, it will move the focus in a direction. 419 | disable_keybind_grabbing: bool = "False" 420 | # If enabled, apps that request keybinds to be disabled (e.g. VMs) will not be able to do so. 421 | window_direction_monitor_fallback: bool = "True" 422 | # If enabled, moving a window or focus over the edge of a monitor with a direction will move it to the next monitor in that direction. 423 | 424 | class Xwayland(Section): 425 | _section_name = "XWayland" 426 | _section_map = {"use_nearest_neighbor": {"name": "use_nearest_neighbor", "description": "uses the nearest neigbor filtering for xwayland apps, making them pixelated rather than blurry", "type": "bool", "default": "true"}, "force_zero_scaling": {"name": "force_zero_scaling", "description": "forces a scale of 1 on xwayland windows on scaled displays.", "type": "bool", "default": "false"}} 427 | _section_key = "xwayland:" 428 | 429 | use_nearest_neighbor: bool = "True" 430 | # uses the nearest neigbor filtering for xwayland apps, making them pixelated rather than blurry 431 | force_zero_scaling: bool = "False" 432 | # forces a scale of 1 on xwayland windows on scaled displays. 433 | 434 | class Opengl(Section): 435 | _section_name = "OpenGL" 436 | _section_map = {"nvidia_anti_flicker": {"name": "nvidia_anti_flicker", "description": "reduces flickering on nvidia at the cost of possible frame drops on lower-end GPUs. On non-nvidia, this is ignored.", "type": "bool", "default": "true"}, "force_introspection": {"name": "force_introspection", "description": "forces introspection at all times. Introspection is aimed at reducing GPU usage in certain cases, but might cause graphical glitches on nvidia. 0 - nothing, 1 - force always on, 2 - force always on if nvidia", "type": "int", "default": "2"}} 437 | _section_key = "opengl:" 438 | 439 | nvidia_anti_flicker: bool = "True" 440 | # reduces flickering on nvidia at the cost of possible frame drops on lower-end GPUs. On non-nvidia, this is ignored. 441 | force_introspection: int = "2" 442 | # forces introspection at all times. Introspection is aimed at reducing GPU usage in certain cases, but might cause graphical glitches on nvidia. 0 - nothing, 1 - force always on, 2 - force always on if nvidia 443 | 444 | class Cursor(Section): 445 | _section_name = "Cursor" 446 | _section_map = {"no_hardware_cursors": {"name": "no_hardware_cursors", "description": "disables hardware cursors", "type": "bool", "default": "false"}, "hotspot_padding": {"name": "hotspot_padding", "description": "the padding, in logical px, between screen edges and the cursor", "type": "int", "default": "1"}, "inactive_timeout": {"name": "inactive_timeout", "description": "in seconds, after how many seconds of cursor's inactivity to hide it. Set to `0` for never.", "type": "int", "default": "0"}, "no_warps": {"name": "no_warps", "description": "if true, will not warp the cursor in many cases (focusing, keybinds, etc)", "type": "bool", "default": "false"}, "default_monitor": {"name": "default_monitor", "description": "the name of a default monitor for the cursor to be set to on startup (see `hyprctl monitors` for names)", "type": "str", "default": "[[EMPTY]]"}, "zoom_factor": {"name": "zoom_factor", "description": "the factor to zoom by around the cursor. Like a magnifying glass. Minimum 1.0 (meaning no zoom)", "type": "float", "default": "1.0"}, "zoom_rigid": {"name": "zoom_rigid", "description": "whether the zoom should follow the cursor rigidly (cursor is always centered if it can be) or loosely", "type": "bool", "default": "false"}, "enable_hyprcursor": {"name": "enable_hyprcursor", "description": "whether to enable hyprcursor support", "type": "bool", "default": "true"}, "hide_on_key_press": {"name": "hide_on_key_press", "description": "Hides the cursor when you press any key until the mouse is moved.", "type": "bool", "default": "false"}, "hide_on_touch": {"name": "hide_on_touch", "description": "Hides the cursor when the last input was a touch input until a mouse input is done.", "type": "bool", "default": "false"}} 447 | _section_key = "cursor:" 448 | 449 | no_hardware_cursors: bool = "False" 450 | # disables hardware cursors 451 | hotspot_padding: int = "1" 452 | # the padding, in logical px, between screen edges and the cursor 453 | inactive_timeout: int = "0" 454 | # in seconds, after how many seconds of cursor's inactivity to hide it. Set to `0` for never. 455 | no_warps: bool = "False" 456 | # if true, will not warp the cursor in many cases (focusing, keybinds, etc) 457 | default_monitor: str = None 458 | # the name of a default monitor for the cursor to be set to on startup (see `hyprctl monitors` for names) 459 | zoom_factor: float = "1.0" 460 | # the factor to zoom by around the cursor. Like a magnifying glass. Minimum 1.0 (meaning no zoom) 461 | zoom_rigid: bool = "False" 462 | # whether the zoom should follow the cursor rigidly (cursor is always centered if it can be) or loosely 463 | enable_hyprcursor: bool = "True" 464 | # whether to enable hyprcursor support 465 | hide_on_key_press: bool = "False" 466 | # Hides the cursor when you press any key until the mouse is moved. 467 | hide_on_touch: bool = "False" 468 | # Hides the cursor when the last input was a touch input until a mouse input is done. 469 | 470 | class Debug(Section): 471 | _section_name = "Debug" 472 | _section_map = {"overlay": {"name": "overlay", "description": "print the debug performance overlay. Disable VFR for accurate results.", "type": "bool", "default": "false"}, "damage_blink": {"name": "damage_blink", "description": "(epilepsy warning!) flash areas updated with damage tracking", "type": "bool", "default": "false"}, "disable_logs": {"name": "disable_logs", "description": "disable logging to a file", "type": "bool", "default": "true"}, "disable_time": {"name": "disable_time", "description": "disables time logging", "type": "bool", "default": "true"}, "damage_tracking": {"name": "damage_tracking", "description": "redraw only the needed bits of the display. Do **not** change. (default: full - 2) monitor - 1, none - 0", "type": "int", "default": "2"}, "enable_stdout_logs": {"name": "enable_stdout_logs", "description": "enables logging to stdout", "type": "bool", "default": "false"}, "manual_crash": {"name": "manual_crash", "description": "set to 1 and then back to 0 to crash Hyprland.", "type": "int", "default": "0"}, "suppress_errors": {"name": "suppress_errors", "description": "if true, do not display config file parsing errors.", "type": "bool", "default": "false"}, "watchdog_timeout": {"name": "watchdog_timeout", "description": "sets the timeout in seconds for watchdog to abort processing of a signal of the main thread. Set to 0 to disable.", "type": "int", "default": "5"}, "disable_scale_checks": {"name": "disable_scale_checks", "description": "disables verification of the scale factors. Will result in pixel alignment and rounding errors.", "type": "bool", "default": "false"}, "error_limit": {"name": "error_limit", "description": "limits the number of displayed config file parsing errors.", "type": "int", "default": "5"}, "error_position": {"name": "error_position", "description": "sets the position of the error bar. top - 0, bottom - 1", "type": "int", "default": "0"}, "colored_stdout_logs": {"name": "colored_stdout_logs", "description": "enables colors in the stdout logs.", "type": "bool", "default": "true"}} 473 | _section_key = "debug:" 474 | 475 | overlay: bool = "False" 476 | # print the debug performance overlay. Disable VFR for accurate results. 477 | damage_blink: bool = "False" 478 | # (epilepsy warning!) flash areas updated with damage tracking 479 | disable_logs: bool = "True" 480 | # disable logging to a file 481 | disable_time: bool = "True" 482 | # disables time logging 483 | damage_tracking: int = "2" 484 | # redraw only the needed bits of the display. Do **not** change. (default: full - 2) monitor - 1, none - 0 485 | enable_stdout_logs: bool = "False" 486 | # enables logging to stdout 487 | manual_crash: int = "0" 488 | # set to 1 and then back to 0 to crash Hyprland. 489 | suppress_errors: bool = "False" 490 | # if true, do not display config file parsing errors. 491 | watchdog_timeout: int = "5" 492 | # sets the timeout in seconds for watchdog to abort processing of a signal of the main thread. Set to 0 to disable. 493 | disable_scale_checks: bool = "False" 494 | # disables verification of the scale factors. Will result in pixel alignment and rounding errors. 495 | error_limit: int = "5" 496 | # limits the number of displayed config file parsing errors. 497 | error_position: int = "0" 498 | # sets the position of the error bar. top - 0, bottom - 1 499 | colored_stdout_logs: bool = "True" 500 | # enables colors in the stdout logs. 501 | 502 | class More(Section): 503 | _section_name = "More" 504 | _section_map = {} 505 | _section_key = "more:" 506 | 507 | 508 | class Default: 509 | general: General = General() 510 | decoration: Decoration = Decoration() 511 | blur: Blur = Blur() 512 | animations: Animations = Animations() 513 | input: Input = Input() 514 | touchpad: Touchpad = Touchpad() 515 | touchdevice: Touchdevice = Touchdevice() 516 | tablet: Tablet = Tablet() 517 | per_device_input_config: PerDeviceInputConfig = PerDeviceInputConfig() 518 | gestures: Gestures = Gestures() 519 | group: Group = Group() 520 | groupbar: Groupbar = Groupbar() 521 | misc: Misc = Misc() 522 | binds: Binds = Binds() 523 | xwayland: Xwayland = Xwayland() 524 | opengl: Opengl = Opengl() 525 | cursor: Cursor = Cursor() 526 | debug: Debug = Debug() 527 | more: More = More() 528 | -------------------------------------------------------------------------------- /hyprland/info.py: -------------------------------------------------------------------------------- 1 | from .socket import command_send, async_command_send 2 | 3 | from .obj.monitor import Monitor 4 | from .obj.workspace import Workspace 5 | from .obj.window import Window, WindowIdentity 6 | 7 | from typing import Optional 8 | 9 | def fetch_version()->str: 10 | return command_send("version")["tag"] 11 | 12 | def fetch_monitors(all:bool=False, id:Optional[int]=None)->list[Monitor]: 13 | """lists active outputs, 'monitors all' lists active and inactive outputs""" 14 | 15 | monitors = [] 16 | 17 | for data in command_send("monitors all" if all else "monitors"): 18 | 19 | monitors.append(Monitor.from_json(data)) 20 | 21 | return monitors 22 | 23 | def fetch_workspaces(id:Optional[int]=None)->list[Workspace] | Workspace: 24 | """lists all workspaces""" 25 | workspaces = [] 26 | 27 | for data in command_send("workspaces"): 28 | if id and data["id"] == id: 29 | return Workspace.from_json(data) 30 | workspaces.append(Workspace.from_json(data)) 31 | 32 | return workspaces 33 | 34 | def fetch_active_workspace()->Workspace: 35 | """gets the active workspace""" 36 | 37 | return Workspace.from_json(command_send("activeworkspace")) 38 | 39 | def fetch_workspace_rules()->list: 40 | """gets the list of defined workspace rules""" 41 | 42 | return command_send("workspacerules") 43 | 44 | def fetch_clients(window:Optional[WindowIdentity]=None)->list[Window] | Window: 45 | """lists all windows""" 46 | windows = [] 47 | for data in command_send("clients"): 48 | if window and data[window.key] == window.value: 49 | return Window.from_json(data) 50 | windows.append(Window.from_json(data)) 51 | return windows 52 | 53 | def fetch_devices()->list: 54 | """lists all connected keyboards and mice""" 55 | return command_send("devices") 56 | 57 | def fetch_decorations(window:WindowIdentity)->list: 58 | """lists all decorations on a window""" 59 | return command_send(f"decorations {window.identifier}") 60 | 61 | def fetch_binds()->list: 62 | """lists all registered binds""" 63 | return command_send("binds") 64 | 65 | def fetch_active_window()->Window: 66 | """gets the active window""" 67 | return Window.from_json(command_send("activewindow")) 68 | 69 | def fetch_layers()->dict: 70 | """lists all the layers""" 71 | return command_send("layers") 72 | 73 | def fetch_splash()->str: 74 | """the current random splash""" 75 | return command_send("splash") 76 | 77 | def fetch_option(key:str)->dict: 78 | """gets the config option status (values)""" 79 | return command_send(f"getoption {key}") 80 | 81 | def fetch_cursor_pos() -> list[int, int]: 82 | """gets the current cursor position in global layout coordinates""" 83 | return command_send("cursorpos") 84 | 85 | def fetch_animations() -> list: 86 | """gets the currently configured info about animations and beziers""" 87 | return command_send("animations") 88 | 89 | def fetch_instances() -> list: 90 | """lists all running instances of Hyprland with their info""" 91 | return command_send("instances") 92 | 93 | def fetch_layouts() -> list: 94 | """lists all layouts available (including from plugins)""" 95 | return command_send("layouts") 96 | 97 | def fetch_config_errors() -> list: 98 | """lists all current config parsing errors""" 99 | return command_send("configerrors") 100 | 101 | def fetch_rolling_log() -> str: 102 | """tail of the hyprland log""" 103 | return command_send("rollinglog",return_json=False) 104 | 105 | def fetch_locked_state() -> bool: 106 | """whether the current session is locked""" 107 | return command_send("locked", return_json=False) -------------------------------------------------------------------------------- /hyprland/obj/monitor.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from ..obj.workspace import Workspace 7 | 8 | from ..socket import command_send 9 | 10 | @dataclass 11 | class Monitor: 12 | id:int 13 | name:str 14 | description:str 15 | make:str 16 | model:str 17 | serial:str 18 | width:int 19 | height:int 20 | refresh_rate:float 21 | x:int 22 | y:int 23 | active_workspace_id:int 24 | active_workspace_name:str 25 | special_workspace_id:int 26 | special_workspace_name:str 27 | reserved:list 28 | scale:float 29 | transform:int 30 | focused:bool 31 | dpms_status:bool 32 | vrr:bool 33 | actively_tearing:bool 34 | disabled:bool 35 | current_format:str 36 | available_modes:list[str] 37 | 38 | def fetch_active_workspace(self)->'Workspace': 39 | for data in command_send("workspaces"): 40 | if id and data["id"] == self.active_workspace_id: 41 | return Workspace.from_json(data) 42 | 43 | def fetch_special_workspace(self)->'Workspace': 44 | for data in command_send("workspaces"): 45 | if id and data["id"] == self.special_workspace_id: 46 | return Workspace.from_json(data) 47 | 48 | @staticmethod 49 | def from_json(data:dict): 50 | return Monitor( 51 | id=data["id"], 52 | name=data["name"], 53 | description=data["description"], 54 | make=data["make"], 55 | model=data["model"], 56 | serial=data["serial"], 57 | width=data["width"], 58 | height=data["height"], 59 | refresh_rate=data["refreshRate"], 60 | x=data["x"], 61 | y=data["y"], 62 | active_workspace_id=data["activeWorkspace"]["id"], 63 | active_workspace_name=data["activeWorkspace"]["name"], 64 | special_workspace_id=data["specialWorkspace"]["id"], 65 | special_workspace_name=data["specialWorkspace"]["name"], 66 | reserved=data["reserved"], 67 | scale=data["scale"], 68 | transform=data["transform"], 69 | focused=data["focused"], 70 | dpms_status=data["dpmsStatus"], 71 | vrr=data["vrr"], 72 | actively_tearing=data["activelyTearing"], 73 | disabled=data["disabled"], 74 | current_format=data["currentFormat"], 75 | available_modes=data["availableModes"] 76 | ) -------------------------------------------------------------------------------- /hyprland/obj/window.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from ..socket import command_send 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from .workspace import WorkspaceIdentity 7 | from .workspace import Workspace 8 | from .monitor import Monitor 9 | 10 | @dataclass 11 | class WindowRule: 12 | rule : str 13 | 14 | # static rules 15 | 16 | @staticmethod 17 | def float(): 18 | """floats a window""" 19 | return WindowRule("float") 20 | 21 | @staticmethod 22 | def tile(): 23 | """tiles a window""" 24 | return WindowRule("tile") 25 | 26 | @staticmethod 27 | def fullscreen(): 28 | """fullscreens a window""" 29 | return WindowRule("fullscreen") 30 | 31 | @staticmethod 32 | def fake_fullscreen(): 33 | """fakefullscreens a window""" 34 | return WindowRule("fakefullscreen") 35 | 36 | @staticmethod 37 | def maximize(): 38 | """maximizes a window""" 39 | return WindowRule("maximize") 40 | 41 | @staticmethod 42 | def move(x:str, y:str): 43 | """moves a floating window (x,y -> int or %, e.g. 20% or 100. 44 | You are also allowed to do 100%- for the right/bottom anchor, e.g. 100%-20. 45 | In addition, the option supports the subtraction of the window size with 100%-w-, e.g. 100%-w-20. 46 | This results in a gap at the right/bottom edge of the screen to the window with the defined subtracted size). 47 | Additionally, you can also do cursor [x] [y] where x and y are either pixels or percent. 48 | Percent is calculated from the window’s size. 49 | Specify onscreen before other parameters to force the window into the screen (e.g. move onscreen cursor 50% 50%)""" 50 | return WindowRule(f"move {x} {y}") 51 | 52 | @staticmethod 53 | def size(x:str, y:str): 54 | """resizes a floating window (x,y -> int or %, e.g. 20% or 100)""" 55 | return WindowRule(f"size {x} {y}") 56 | 57 | @staticmethod 58 | def center(respect_reserved:bool=False): 59 | """centers a floating window""" 60 | return WindowRule(f"center {1 if respect_reserved else 0}") 61 | 62 | @staticmethod 63 | def pseudo(): 64 | """pseudotiles a window""" 65 | return WindowRule("pseudo") 66 | 67 | @staticmethod 68 | def monitor(id:str): 69 | """moves a window to a specific monitor""" 70 | return WindowRule(f"monitor {id}") 71 | 72 | @staticmethod 73 | def workspace(w:'WorkspaceIdentity', silent:bool=False): 74 | """sets the workspace on which a window should open""" 75 | return WindowRule(f"workspace {w.identifier}{' silent' if silent else ''}") 76 | 77 | @staticmethod 78 | def unset_workspace_rules(): 79 | """unsets the workspace rule""" 80 | return WindowRule("workspace unset") 81 | 82 | @staticmethod 83 | def no_focus(): 84 | """disables focus to the window""" 85 | return WindowRule("nofocus") 86 | 87 | @staticmethod 88 | def no_initial_focus(): 89 | """disables initial focus to the window""" 90 | return WindowRule("noinitialfocus") 91 | 92 | @staticmethod 93 | def force_input(): 94 | """forces an XWayland window to receive input, even if it requests not to do so. (Might fix issues like e.g. Game Launchers not receiving focus for some reason)""" 95 | return WindowRule("forceinput") 96 | 97 | @staticmethod 98 | def window_dance(): 99 | """orces an XWayland window to never refocus, used for games/applications like Rhythm Doctor""" 100 | return WindowRule("windowdance") 101 | 102 | @staticmethod 103 | def pin(): 104 | """pins the window (i.e. show it on all workspaces) note: floating only""" 105 | return WindowRule("pin") 106 | 107 | @staticmethod 108 | def unset(params:str): 109 | """removes all previously set rules for the given parameters. Please note it has to match EXACTLY.""" 110 | return WindowRule(f"unset {params}") 111 | 112 | @staticmethod 113 | def no_max_size(): 114 | """removes max size limitations. Especially useful with windows that report invalid max sizes (e.g. winecfg)""" 115 | return WindowRule("nomaxsize") 116 | 117 | @staticmethod 118 | def stay_focused(): 119 | """forces focus on the window as long as it’s visible""" 120 | return WindowRule("stayfocused") 121 | 122 | @staticmethod 123 | def group(options:str): 124 | """set window group properties. See the note below.""" 125 | return WindowRule(f"group {options}") 126 | 127 | @staticmethod 128 | def suppress_events(fullscreen:bool=False, maximize:bool=False, activate:bool=False, activatefocus:bool=False): 129 | """ignores specific events from the window. Events are space separated, and can be: fullscreen,maximize,activate,activatefocus""" 130 | return WindowRule(f"suppress_events {','.join([x for x in [fullscreen, maximize, activate, activatefocus] if x])}") 131 | 132 | 133 | # dynamic rules 134 | 135 | @staticmethod 136 | def opacity(opacity:int, inactive_opacity:int=None, fullscreen_opacity:int=None): 137 | """additional opacity multiplier. Options for a: float -> sets an overall opacity OR float float -> sets activeopacity and inactiveopacity respectively, OR float float float -> sets activeopacity, inactiveopacity and fullscreenopacity respectively.""" 138 | if inactive_opacity and fullscreen_opacity: 139 | return WindowRule(f"opacity {opacity} {inactive_opacity} {fullscreen_opacity}") 140 | elif inactive_opacity: 141 | return WindowRule(f"opacity {opacity} {inactive_opacity}") 142 | else: 143 | return WindowRule(f"opacity {opacity}") 144 | 145 | @staticmethod 146 | def opaque(): 147 | """forces the window to be opaque (can be toggled with the toggleopaque dispatcher)""" 148 | return WindowRule("opaque") 149 | 150 | @staticmethod 151 | def force_rgbx(): 152 | """makes Hyprland ignore the alpha channel of all the window’s surfaces, effectively making it actually, fully 100% opaque""" 153 | return WindowRule("forcergbx") 154 | 155 | @staticmethod 156 | def animation(style:str,opt:str=None): 157 | """orces an animation onto a window, with a selected opt. Opt is optional.""" 158 | return WindowRule(f"animation {style}{' '+opt if opt else ''}") 159 | 160 | @staticmethod 161 | def rounding(x:int): 162 | """forces the application to have X pixels of rounding, ignoring the set default (in decoration:rounding). Has to be an int.""" 163 | return WindowRule(f"rounding {x}") 164 | 165 | @staticmethod 166 | def min_size(x:int, y:int): 167 | """sets the minimum size (x,y -> int)""" 168 | return WindowRule(f"minsize {x} {y}") 169 | 170 | @staticmethod 171 | def max_size(x:int, y:int): 172 | """sets the maximum size (x,y -> int)""" 173 | return WindowRule(f"maxsize {x} {y}") 174 | 175 | @staticmethod 176 | def no_blur(): 177 | """disables blur for the window""" 178 | return WindowRule("noblur") 179 | 180 | @staticmethod 181 | def no_border(): 182 | """disables borders for the window""" 183 | return WindowRule("noborder") 184 | 185 | @staticmethod 186 | def border_size(x:int): 187 | """sets the border""" 188 | return WindowRule(f"bordersize {x}") 189 | 190 | @staticmethod 191 | def no_dim(): 192 | """disables window dimming for the window""" 193 | return WindowRule("nodim") 194 | 195 | @staticmethod 196 | def no_shadow(): 197 | """disables shadow for the window""" 198 | return WindowRule("noshadow") 199 | 200 | @staticmethod 201 | def no_animations(): 202 | """disables animations for the window""" 203 | return WindowRule("noanimations") 204 | 205 | @staticmethod 206 | def keep_aspect_ratio(): 207 | """forces aspect ratio when resizing window with the mouse""" 208 | return WindowRule("keepaspectratio") 209 | 210 | @staticmethod 211 | def focus_on_activate(focus:bool): 212 | """whether Hyprland should focus an app that requests to be focused (an activate request)""" 213 | return WindowRule(f"focusonactivate {int(focus)}") 214 | 215 | @staticmethod 216 | def border_color(color:str): 217 | """sets the border color""" 218 | return WindowRule(f"bordercolor {color}") 219 | 220 | @staticmethod 221 | def idle_inhibit(mode:str): 222 | """sets an idle inhibit rule for the window. If active, apps like hypridle will not fire. Modes: none, always, focus, fullscreen""" 223 | return WindowRule(f"idleinhibit {mode}") 224 | 225 | @staticmethod 226 | def dim_around(): 227 | """dims everything around the window . Please note this rule is meant for floating windows and using it on tiled ones may result in strange behavior.""" 228 | return WindowRule("dimaround") 229 | 230 | @staticmethod 231 | def xray(enabled:bool): 232 | """sets blur xray mode for the window""" 233 | return WindowRule(f"xray {int(enabled)}") 234 | 235 | @staticmethod 236 | def xray_unset(): 237 | """unsets xray mode for the window""" 238 | return WindowRule("xray unset") 239 | 240 | @staticmethod 241 | def immediate(): 242 | """forces the window to allow to be torn.""" 243 | return WindowRule("immediate") 244 | 245 | @staticmethod 246 | def nearest_neighbor(): 247 | """forces the window to use the nearest neigbor filtering.""" 248 | return WindowRule("nearestneighbor") 249 | 250 | @dataclass 251 | class WindowIdentity: 252 | key:str = None 253 | value = None 254 | identifier:str="" 255 | 256 | def __post_init__(self): 257 | if self.key and self.value: 258 | self.identifier = f"{self.key}:{self.value}" 259 | elif self.identifier: 260 | self.key, self.value = self.identifier.split(":") 261 | else: 262 | raise AttributeError("WindowIdentity must have a key and value or an identifier") 263 | 264 | @staticmethod 265 | def from_window(window): 266 | return WindowIdentity(key="address", value=window.address) 267 | 268 | @staticmethod 269 | def from_address(address:int): 270 | return WindowIdentity(key="address", value=address) 271 | 272 | @staticmethod 273 | def from_title(title:str): 274 | return WindowIdentity(key="title", value=title) 275 | 276 | @staticmethod 277 | def from_class_name(class_name:str): 278 | return WindowIdentity(key="class", value=class_name) 279 | 280 | @staticmethod 281 | def from_pid(pid:int): 282 | return WindowIdentity(key="pid", value=pid) 283 | 284 | @dataclass 285 | class Window: 286 | address:int 287 | mapped:bool 288 | hidden:bool 289 | at:list[int, int] 290 | size:list[int, int] 291 | workspace_id:int 292 | workspace_name:str 293 | floating:bool 294 | monitor_id:int 295 | class_name:str 296 | title:str 297 | initial_class_name:str 298 | initial_title:str 299 | pid:int 300 | xwayland:bool 301 | fullscreen:bool 302 | fullscreen_mode:int 303 | fake_fullscreen:bool 304 | grouped:list 305 | swallowing:int 306 | focus_history_id:int 307 | 308 | def fetch_workspace(self)->'Workspace': 309 | for data in command_send("workspaces"): 310 | if id and data["id"] == self.workspace_id: 311 | return Workspace.from_json(data) 312 | 313 | def fetch_monitor(self)->'Monitor': 314 | for data in command_send("monitors all"): 315 | if data["id"] == self.monitor_id: 316 | return Monitor.from_json(data) 317 | 318 | def fetch_decorations(self): 319 | ... 320 | 321 | def identifier(self)->'WindowIdentity': 322 | return WindowIdentity.from_window(self) 323 | 324 | @staticmethod 325 | def from_json(data:dict): 326 | return Window( 327 | address=data["address"], 328 | mapped=data["mapped"], 329 | hidden=data["hidden"], 330 | at=data["at"], 331 | size=data["size"], 332 | workspace_id=data["workspace"]["id"], 333 | workspace_name=data["workspace"]["name"], 334 | floating=data["floating"], 335 | monitor_id=data["monitor"], 336 | class_name=data["class"], 337 | title=data["title"], 338 | initial_class_name=data["initialClass"], 339 | initial_title=data["initialTitle"], 340 | pid=data["pid"], 341 | xwayland=data["xwayland"], 342 | fullscreen=data["fullscreen"], 343 | fullscreen_mode=data["fullscreenMode"], 344 | fake_fullscreen=data["fakeFullscreen"], 345 | grouped=data["grouped"], 346 | swallowing=data["swallowing"], 347 | focus_history_id=data["focusHistoryID"] 348 | ) 349 | 350 | -------------------------------------------------------------------------------- /hyprland/obj/workspace.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from .monitor import Monitor 3 | from .window import Window 4 | 5 | from ..socket import command_send 6 | 7 | @dataclass 8 | class WorkspaceIdentity: 9 | key:str = None 10 | value = None 11 | identifier:str="" 12 | 13 | def __post_init__(self): 14 | if self.key and self.value: 15 | self.identifier = f"{self.key}:{self.value}" 16 | else: 17 | raise AttributeError("WorkspaceIdentity must have a key and value or an identifier") 18 | 19 | @staticmethod 20 | def from_id(id:int): 21 | return WorkspaceIdentity(key="id", value=id) 22 | 23 | @staticmethod 24 | def from_name(name:str): 25 | return WorkspaceIdentity(key="name", value=name) 26 | 27 | @staticmethod 28 | def from_special_name(name:str): 29 | return WorkspaceIdentity(key="special", value=name) 30 | 31 | @staticmethod 32 | def relative_monitor(offset:int=0,include_empty:bool=False): 33 | return WorkspaceIdentity(identifier=f"{'r' if include_empty else 'm'}{offset}") 34 | 35 | @staticmethod 36 | def absolute_monitor(offset:int=0,include_empty:bool=False): 37 | return WorkspaceIdentity(identifier=f"{'r' if include_empty else 'm'}~{offset}") 38 | 39 | @staticmethod 40 | def relative(offset:int=0,include_empty:bool=False): 41 | return WorkspaceIdentity(identifier=f"{'r' if include_empty else 'w'}{offset}") 42 | 43 | @staticmethod 44 | def absolute(offset:int=0,include_empty:bool=False): 45 | return WorkspaceIdentity(identifier=f"{'r' if include_empty else 'w'}~{offset}") 46 | 47 | @staticmethod 48 | def next(count:int=1): 49 | return WorkspaceIdentity(identifier=f"+{count}") 50 | 51 | @staticmethod 52 | def previous(count:int=1): 53 | return WorkspaceIdentity(identifier=f"-{count}") 54 | 55 | @staticmethod 56 | def first_empty_monitor(): 57 | return WorkspaceIdentity(identifier="emptym") 58 | 59 | @staticmethod 60 | def first_empty(): 61 | return WorkspaceIdentity(identifier="empty") 62 | 63 | @staticmethod 64 | def next_empty_monitor(): 65 | return WorkspaceIdentity(identifier=f"emptynm") 66 | 67 | @staticmethod 68 | def next_empty(): 69 | return WorkspaceIdentity(identifier=f"emptyn") 70 | 71 | 72 | @dataclass 73 | class Workspace: 74 | id:int 75 | name:str 76 | monitor_id:int 77 | windows:int 78 | has_fullscreen:bool = False 79 | last_active_window_id:str = None 80 | last_active_window_title:str = None 81 | 82 | # def fetch_monitor(self)->Monitor: 83 | # return Monitor.from_id(self._monitor_id) 84 | 85 | def fetch_windows(self)->list[Window]: 86 | ... 87 | 88 | def fetch_last_active_window(self)->Window: 89 | ... 90 | 91 | @staticmethod 92 | def from_json(data:dict): 93 | return Workspace( 94 | id=data["id"], 95 | name=data["name"], 96 | windows=data["windows"], 97 | monitor_id=data["monitorID"], 98 | has_fullscreen=data["hasfullscreen"], 99 | last_active_window_id=data["lastwindow"], 100 | last_active_window_title=data["lastwindowtitle"] 101 | ) 102 | 103 | @staticmethod 104 | def from_id(id:int): 105 | for data in command_send("workspaces"): 106 | if id and data["id"] == id: 107 | return Workspace.from_json(data) -------------------------------------------------------------------------------- /hyprland/socket.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import socket 3 | import os 4 | import json 5 | 6 | class UnknownRequest(Exception): 7 | ... 8 | 9 | def command_send(cmd:str, return_json = True, check_ok = False): 10 | with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock: 11 | sock.connect(f"{os.getenv('XDG_RUNTIME_DIR')}/hypr/{os.getenv('HYPRLAND_INSTANCE_SIGNATURE')}/.socket.sock") 12 | if return_json: 13 | cmd = f"[j]/{cmd}" 14 | sock.send(cmd.encode()) 15 | resp = sock.recv(8192) 16 | 17 | while True: 18 | new_data = sock.recv(8192) 19 | if not new_data: 20 | break 21 | resp += new_data 22 | 23 | match resp: 24 | case b'ok' if check_ok: 25 | return True 26 | case b'unknown request': 27 | raise UnknownRequest(f'{cmd.encode()!r} : {resp}') 28 | case _: 29 | 30 | if check_ok and resp != b'ok': 31 | raise Exception(f"Command failed: {cmd.encode()!r} : {resp}") 32 | 33 | if return_json and not check_ok: 34 | return json.loads(resp.decode()) 35 | 36 | return resp.decode() 37 | 38 | async def async_command_send(cmd:str, return_json = True): 39 | reader, writer = await asyncio.open_unix_connection(f"{os.getenv('XDG_RUNTIME_DIR')}/hypr/{os.getenv('HYPRLAND_INSTANCE_SIGNATURE')}/.socket.sock") 40 | if return_json: 41 | cmd = f"[j]/{cmd}" 42 | writer.write(cmd.encode()) 43 | await writer.drain() 44 | resp = await reader.read(8192) 45 | 46 | while True: 47 | new_data = await reader.read(8192) 48 | if not new_data: 49 | break 50 | resp += new_data 51 | 52 | writer.close() 53 | 54 | match resp: 55 | case b'unknown request': 56 | raise UnknownRequest(f'{cmd.encode()!r} : {resp}') 57 | case _: 58 | if return_json: 59 | return json.loads(resp.decode()) 60 | return resp.decode() 61 | 62 | class EventListener: 63 | async def start(self): 64 | reader, _ = await asyncio.open_unix_connection(f"{os.getenv('XDG_RUNTIME_DIR')}/hypr/{os.getenv('HYPRLAND_INSTANCE_SIGNATURE')}/.socket2.sock") 65 | yield "connect" 66 | 67 | buffer = b"" 68 | while True: 69 | new_data = await reader.read(8192) 70 | if not new_data: 71 | break 72 | buffer += new_data 73 | while b"\n" in buffer: 74 | data, buffer = buffer.split(b"\n", 1) 75 | yield data.decode("utf-8") -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | variables: 2 | python3 ./parse.py variables ./hyprland/gen/variables.py 3 | 4 | 5 | -------------------------------------------------------------------------------- /parse.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | 4 | MODS = ["SHIFT","CTRL","ALT","META","SUPER"] # [TODO] find a proper list of valid modifiers 5 | 6 | def parse_value(val:str,t:str): 7 | match t.lower(): 8 | case "int": 9 | return int(val) 10 | case "float": 11 | return float(val) 12 | case "bool": 13 | if val.isnumeric(): 14 | return bool(int(val)) 15 | match val.lower(): 16 | case "true": 17 | return True 18 | case "false": 19 | return False 20 | case "yes": 21 | return True 22 | case "no": 23 | return False 24 | case "on": 25 | return True 26 | case "off": 27 | return False 28 | case "vec2": 29 | if val.startswith("(") and val.endswith(")"): 30 | val = val[1:-1] 31 | if val.startswith("[") and val.endswith("]"): 32 | val = val[1:-1] 33 | if "," in val: 34 | return tuple([float(x) for x in val.split(",")]) 35 | return tuple([float(x) for x in val.split()]) 36 | case "mod": 37 | mods = [] 38 | buf = "" 39 | for char in val.upper(): 40 | buf += char 41 | if buf in MODS: 42 | mods.append(buf) 43 | buf = "" 44 | return mods 45 | 46 | # case "color": 47 | # ... 48 | # case "gradient": 49 | # ... 50 | 51 | case _: 52 | return val 53 | 54 | def string_value(val): 55 | if isinstance(val,bool) or isinstance(val,int) or isinstance(val,float): 56 | return str(val) 57 | elif isinstance(val,tuple): 58 | return f"({','.join([str(x) for x in val])})" 59 | elif isinstance(val,list): 60 | return f"[{','.join([str(x) for x in val])}]" 61 | elif val == "\\[\\[Empty\\]\\]" or val == "[[EMPTY]]": 62 | return None 63 | elif val == "\\[\\[Auto\\]\\]" or val == "[[AUTO]]": 64 | return '"auto"' 65 | else: 66 | return f'"{val}"' 67 | 68 | def standardize_name(name:str): 69 | return name.lower().replace(" ","_").replace("-","_").replace(".","_") 70 | 71 | def gen_variables(): 72 | path = 'hyprland-wiki/pages/Configuring/Variables.md' 73 | 74 | dat = {} 75 | keys = {} 76 | 77 | with open(path, 'r') as f: 78 | raw = f.readlines() 79 | 80 | in_sections = False 81 | 82 | current_section = None 83 | 84 | ignore = False 85 | 86 | for line in raw: 87 | if ignore: 88 | if line.startswith("{{< /callout >}}"): 89 | ignore = False 90 | continue 91 | if line.startswith("{{< callout type=info >}}"): 92 | ignore = True 93 | if line.startswith('## Sections'): 94 | in_sections = True 95 | continue 96 | 97 | if in_sections: 98 | if line.startswith('### '): 99 | current_section = line[4:-1] 100 | dat[current_section] = [] 101 | table_heads = [] 102 | keys[current_section.title().replace(" ","").replace("-","")] = f"{current_section.replace(' ','_').replace('-','_').lower()}:" 103 | 104 | if line.startswith('#### '): 105 | current_section = line[5:-1] 106 | dat[current_section] = [] 107 | table_heads = [] 108 | keys[current_section.title().replace(" ","").replace("-","")] = f"{current_section.replace(' ','_').replace('-','_').lower()}:" 109 | 110 | if line.lower().startswith("_subcategory"): 111 | key = line[14:-2].strip(":") 112 | parent = key.split(":")[0].replace(' ','_').replace('-','_').lower() 113 | 114 | keys[current_section.title().replace(" ","").replace("-","")] = f"{parent}:{keys[current_section.title().replace(" ","").replace("-","")]}" 115 | 116 | if line.startswith('|'): 117 | if not table_heads: 118 | table_heads = [x.strip() for x in line[1:-1].split('|')[:-1]] 119 | else: 120 | table_data = [x.strip() for x in line[1:-1].split('|')] 121 | if table_data[0] == "---": 122 | continue 123 | if len(table_data) < len(table_heads): 124 | continue 125 | dat[current_section].append({table_heads[i]:table_data[i] for i in range(len(table_heads))}) 126 | 127 | return dat, keys 128 | 129 | def gen_python(variables:dict, keys: dict): 130 | out = f"from ._util import Section\nKEYS = {keys}\n\n" 131 | for section in variables: 132 | out += f"class {section.title().replace(" ","").replace("-","")}(Section):\n" 133 | section_map = {} 134 | 135 | part = "" 136 | 137 | for variable in variables[section]: 138 | val = parse_value(variable['default'],variable['type']) 139 | section_map[standardize_name(variable['name'])] = variable 140 | part += f""" {standardize_name(variable['name'])}: {type(val).__name__} = {string_value(val)}\n # {variable['description']}\n""" 141 | 142 | out += " _section_name = " + f'"{section}"' + "\n" 143 | out += " _section_map = " + json.dumps(section_map) + "\n" 144 | out += f' _section_key = "{keys[section.title().replace(" ","").replace("-","")]}"\n\n' 145 | 146 | out += part + "\n" 147 | 148 | out += """class Default:\n""" 149 | for section in variables: 150 | out += f" {section.replace(' ','_').replace('-','_').lower()}: {section.title().replace(' ','').replace('-','')} = {section.title().replace(' ','').replace('-','')}()\n" 151 | 152 | return out 153 | 154 | if __name__ == "__main__": 155 | name = sys.argv[1:][0].lower() 156 | target = None 157 | if len(sys.argv[1:]) >= 2: 158 | target = sys.argv[1:][1] 159 | 160 | match name: 161 | case "variables": 162 | if target: 163 | with open(target, 'w') as f: 164 | f.write(gen_python(*gen_variables())) 165 | else: 166 | print(gen_python(gen_variables())) 167 | case _: 168 | print("Invalid argument") 169 | sys.exit(1) 170 | --------------------------------------------------------------------------------