Files
gitops 43507504f4 cosmic-conf: windowrule, and the rules it refuses
windowrule = workspace name:web, class:^(vivaldi|firefox)$
    windowrule = workspace 1,        class:^(kitty)$, title:^(dev)$

Pairs with the workspace lines from the previous commit: those make a
named workspace exist, this sends an application to it. `windowrulev2` is
accepted as the same key, since that is the spelling configs in the wild
are written with.

Hyprland's windowrule has around forty actions. One of them is
implemented, because one of them has a COSMIC counterpart, and the other
thirty-nine fail with an explanation rather than parsing into nothing. A
rule that looks right and silently does not fire is the worst outcome
available -- the window opens in the wrong place and there is nothing to
read that says why. So `float` points at the tiling exceptions in
cosmic-settings, which is where floating actually lives; the rest say that
only `workspace` exists here. Matchers that ask about window state --
floating:, fullscreen:, onworkspace: -- say that matching happens once, as
the window opens, so only what the window arrives with can be tested.

Three things the parser has to get right

`+1` is not workspace 1. u32::from_str accepts a leading sign, so
Hyprland's relative target would have parsed as an absolute one and sent
the window somewhere the rule never asked for. The digits are checked
before parsing rather than the result checked after.

`silent` is stripped from the end, not parsed as one word among several,
because a workspace name may contain spaces. `workspace = 2, name:web and
mail` is a legal declaration, so `workspace name:web and mail` has to be a
legal rule.

A rule with neither class nor title is refused. Both are regular
expressions and an empty one matches everything, so the rule that looks
like it does nothing would in fact send the entire session to one
workspace.

Order is the semantics

Rules are emitted in the order they were written and not deduplicated: two
rules can differ only in their title and both be wanted, and the
compositor takes the first that matches, so a file that reads top to
bottom has to be written top to bottom.

The regex dependency

Expressions are compiled here so a broken one is a diagnostic against the
line that wrote it, with the crate's own message pointing at the offending
character, rather than a warning in the compositor log nobody reads.
Pinned to the same major cosmic-comp matches with, so what compiles here
compiles there. Cargo.lock carries the five new entries at the versions
cosmic-comp already resolved.

Verified

24 unit tests in the new module and 6 in resolve, covering the sign trap,
the name with spaces, both spellings of the key, the order of the emitted
list, variable expansion, and that each refused action and matcher carries
its explanation. Not verified here: nothing is compiled on this machine.
2026-08-11 08:22:03 +07:00

207 lines
10 KiB
Plaintext

# HyprCosmic configuration, in Hyprland's idiom.
#
# Compiled into COSMIC's config tree by `cosmic-conf apply`. The file wins:
# every key here overwrites whatever COSMIC's own settings UI last stored, so
# edit this rather than the GUI for anything it covers.
# --- Theme --------------------------------------------------------------
#
# `cosmic-conf import-theme <theme>/hypr.theme --out ~/.config/hyprcosmic/theme.conf`
# turns a HyDE theme into conf keys. Sourcing it rather than pasting it in
# keeps the two apart: re-importing overwrites theme.conf and cannot touch the
# keybindings below, and anything you want to override can simply be repeated
# later in this file, since the last assignment to a key wins.
#
# Commented out because a `source` pointing at a file that does not exist is a
# hard error, and no theme is imported yet. Uncomment it once you have run the
# command above -- import-theme will remind you.
#
# source = ~/.config/hyprcosmic/theme.conf
$mainMod = SUPER
# --- Tiling -------------------------------------------------------------
#
# COSMIC ships with autotile off, so a new window opens floating, at whatever
# size the application asked for, on top of what you were already looking at.
# This is the setting that makes window placement automatic: each new window
# takes a share of the screen instead.
#
# It applies immediately and everywhere. `autotile_behavior` is a separate
# COSMIC key that defaults to Global, which retiles workspaces that already
# exist; the other value, PerWorkspace, arms only workspaces created from now
# on. There is no conf key for it because the default is the one worth having
# -- if you want the other, set it in cosmic-settings and this file will not
# fight you over it.
#
# Super+Y still toggles tiling for the current workspace on its own, so a
# workspace you want to keep floating does not need this turned off.
#
# Gaps are here rather than in the theme block because they are only visible
# once windows tile: with autotile off nothing is laid out, so nothing has a
# gap. gaps_out is doubled so the screen edge reads as deliberate margin
# rather than as one more seam.
$gap = 4
general {
autotile = true
preserve_split = true
gaps_in = $gap
gaps_out = $gap * 2
}
# --- Workspaces ----------------------------------------------------------
#
# COSMIC's workspaces are dynamic and there is no setting that turns that off:
# it keeps exactly one trailing empty workspace and collects every other empty
# one the moment its last window closes. So "workspace 4 is the browser" is only
# true while the browser is open, and the number moves under you as workspaces
# come and go.
#
# These lines declare a fixed set instead, which is what a Hyprland user expects.
# A declared workspace is pinned, and pinning is precisely what exempts it from
# that collection, so it stays at its number whether or not anything is on it.
#
# The dynamic workspace is not lost. Declaring four leaves you with 1-4 always
# present and a fifth appearing the moment you use it, then a sixth after that,
# exactly as Hyprland does.
#
# Two things worth knowing before you edit:
#
# * The index is required and workspaces below it are created too, because
# COSMIC restores them by position rather than by number. `workspace = 4`
# on its own gives you four workspaces, three of them unnamed.
# * This is the one key in this file that is not live. The compositor reads
# it once at startup, so an edit applies at your next login. Everything
# else here takes effect as soon as you save.
#
# `name:` is what waybar and anything else reading ext-workspace will show.
# `tiling:` overrides `general:autotile` for that workspace alone; leave it out
# and the workspace follows the setting above. Hyprland's `monitor:` is not
# accepted -- COSMIC matches a workspace to a monitor by EDID rather than by
# name, and there is no way to write an EDID down here. Saying so is better
# than accepting the parameter and quietly ignoring it.
#
# Commented out because a fixed set is a preference, not an improvement, and
# leaving these off gives you COSMIC's stock behaviour. Uncomment to opt in.
#
# workspace = 1, name:term
# workspace = 2, name:web
# workspace = 3, name:code
# workspace = 4, name:chat
# --- Window rules --------------------------------------------------------
#
# Where an application opens, decided from what it is rather than from where
# you happened to be standing. `windowrule = workspace 4, class:^(vivaldi)$`
# means the browser lands on workspace 4 no matter which one is in front of you.
#
# Rules are matched once, as the window opens, and the first one that matches
# wins -- so put the specific ones above the general ones. `class:` and `title:`
# are regular expressions; give both and both have to match. A rule with neither
# is refused, since it would match every window in the session.
#
# These pair with the workspace lines above. `workspace name:web` is worth
# preferring over `workspace 2`: a name follows the workspace if you renumber
# it, and only exists because you declared it, so a typo fails loudly rather
# than sending the window to whatever happens to be second.
#
# A rule never switches you to the workspace it used -- Hyprland's `silent`,
# always on. The word is accepted so pasted-in configs keep working.
#
# Only `workspace` is supported. Hyprland's float, size, move, opacity and the
# rest have nothing in COSMIC to project onto, and matchers that ask about
# window state -- floating:, fullscreen:, onworkspace: -- cannot be answered at
# the moment a window opens. Both fail with an explanation rather than parsing
# and doing nothing. Floating for a particular application is a tiling
# exception, which lives in COSMIC's own settings rather than in this file.
#
# Unlike the workspace lines, these are live: save the file and the next window
# to open obeys them.
#
# windowrule = workspace name:web, class:^(vivaldi|firefox)$
# windowrule = workspace name:code, class:^(codium|code)$
# windowrule = workspace name:chat, class:^(discord|Element)$
# windowrule = workspace 1, class:^(kitty)$, title:^(dev)$
# --- Input ---------------------------------------------------------------
#
# Focus follows the mouse, which COSMIC supports but ships turned off. Hyprland
# spells it `input:follow_mouse`, and that spelling is what this file accepts;
# `general:focus_follows_cursor` is the same setting under COSMIC's own name,
# and setting both is not an error -- whichever comes last in the file wins.
#
# Autoraise comes with it and is not a separate key. cosmic-comp raises a
# window as part of focusing it, so a floating window under the pointer comes
# to the front on its own. Tiled windows do not overlap, so there is nothing
# there to raise.
#
# The delay is what stops the focus from skating across every window between
# where the pointer started and where it stopped -- moving the mouse to a menu
# on the far side of the screen should not hand focus to whatever it crossed on
# the way. 250ms is COSMIC's own default and is kept rather than shortened,
# because the failure it prevents is more annoying than the wait.
#
# Only 0 and 1 mean anything here. Hyprland's 2 and 3 separate pointer focus
# from keyboard focus, which cosmic-comp cannot do -- it has one focus. Those
# values are rejected with an explanation rather than rounded to 1.
input {
follow_mouse = 1
follow_mouse_delay = 250
}
# --- Launcher -----------------------------------------------------------
#
# The hyprcosmic profile does not start cosmic-launcher or cosmic-app-library,
# which leaves COSMIC's stock Super, Super+/ and Super+A bindings pointing at
# nothing. These take them over with rofi. Written to the Shortcuts `custom`
# key, which cosmic-comp merges over `defaults`, so the system file is not
# touched and reverting is a matter of deleting these lines and re-applying.
# Tap Super on its own to open the launcher.
# The key field is deliberately empty: COSMIC supports
# modifier-only bindings, which Hyprland's `bind` cannot express.
bind = $mainMod, , exec, rofi -show drun
bind = $mainMod, slash, exec, rofi -show drun
bind = $mainMod, A, exec, rofi -show drun
# Was System(WorkspaceOverview); cosmic-workspaces is not running either.
# rofi's window mode is the nearest thing that still shows every open window.
bind = $mainMod, W, exec, rofi -show window
# Terminal, in the Hyprland idiom. Super+T also still works — cosmic-comp
# handles System(Terminal) itself, so that binding never went dead.
bind = $mainMod, Return, exec, cosmic-term
# --- Session ------------------------------------------------------------
#
# The hyprcosmic profile disables cosmic-panel, and COSMIC's power applet lives
# in that panel, so a HyprCosmic session had no logout, reboot or shutdown
# anywhere -- the only way out was `systemctl reboot` from a terminal. This is
# that way out. The same script backs waybar's power button, so the two cannot
# drift apart, and it asks for confirmation before anything that ends the
# session.
#
# Super+Shift+E is Hyprland's own spelling for "exit". The menu also offers
# lock and suspend, which is why the binding is not named after logout.
bind = $mainMod SHIFT, E, exec, hyprcosmic-powermenu
# --- Help ---------------------------------------------------------------
#
# Every shortcut in the session, in one searchable window, with the command
# each one runs. cosmic-settings can show these, but it is where you go to
# change a binding, not where you go to remember one -- and the hyprcosmic
# profile does not put it one click away.
#
# The script reads COSMIC's own Shortcuts files rather than this one. That is
# deliberate and it matters: this file declares six bindings and the session
# answers to 122, the rest being COSMIC defaults there is no reason to restate
# here. A reference built from this file alone would look complete and be
# missing every window, workspace and media key on the machine.
#
# Super+Shift+/ is Super+? on this keyboard, which is the usual spelling for
# help, and it is one of the few chords in that corner COSMIC leaves free --
# Super+K and Super+I are both focus actions in the defaults. The same script
# backs waybar's keyboard button.
bind = $mainMod SHIFT, slash, exec, hyprcosmic-keybinds