mirror of
https://github.com/outbackdingo/hyprcosmic-comp.git
synced 2026-08-25 14:53:22 +00:00
shell: place a window from its app id, not from where you were standing
Hyprland's `windowrule = workspace 4, class:^(vivaldi)$` has no COSMIC
counterpart, and the missing piece is small: map_window already accepts a
workspace other than the active one, because activation tokens use that
path. This adds a second source for the same decision.
`window_rules` is a new CosmicCompConfig key rather than an addition to
com.system76.CosmicSettings.WindowRules. That component belongs to
cosmic-settings-daemon and holds one kind of rule -- tiling exceptions,
`{ appid, title }` with no action, because the action is implied. There is
nowhere in it to say *which workspace*, and putting one there would mean
forking a second upstream crate to add a field cosmic-settings' own UI
could not show. `preserve_split` set the precedent for keys the fork adds.
The matching half is deliberately the same shape as the exceptions: two
RegexSets, app ids and titles, a rule matching when the same index matches
in both. A regex that works in a tiling exception works here. The third
vector is what an exception list does not need -- exceptions answer yes or
no, a rule has to say where -- and it is index-aligned with the two sets,
which is why an invalid expression is skipped from all three together.
Precedence
An activation token wins. It is an application saying where it wants this
window right now -- a file manager reopening a document where you launched
it from -- and that outranks a rule written months ago. So the rules are
consulted only when nothing else claimed a workspace.
`was_activated` stays keyed to the token rather than to the resulting
handle. It raises the urgency hint on the target workspace, and a window
that went exactly where the config said is not worth flagging; one that
turned up somewhere unexpected is. Without splitting the two apart, every
rule-placed window would have made its workspace blink.
That also means a rule never switches you to the workspace it used, which
is Hyprland's `silent` and is now unconditional.
Missing targets miss
Workspaces are resolved per output, because a WorkspaceSet is, so
"workspace 4" is the fourth workspace of the screen the window would have
opened on. A rule naming a workspace that does not exist returns None and
the window opens where it would have anyway: `workspace 4` with three
workspaces must not conjure a fourth, and a name only exists because a
`workspace` line created it.
Live
The key has an arm in the config watcher, so an edit applies to the next
window that opens. Nothing moves -- windows already open stay put, since
the rules are only read at map time. That is the opposite of
pinned_workspaces, which is read once at startup and lands at next login.
Not verified: nothing was compiled here. This half is built by
packages.yml; the cosmic-conf job covers the parser that writes the key.
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
//! Rules that decide where a window opens, from its app id and title.
|
||||
//!
|
||||
//! COSMIC already has a `com.system76.CosmicSettings.WindowRules` component,
|
||||
//! but it belongs to cosmic-settings-daemon and holds exactly one kind of rule:
|
||||
//! `tiling_exception_defaults` / `tiling_exception_custom`, both lists of
|
||||
//! `{ appid, title }` with no action attached, because the action is implied.
|
||||
//! There is nowhere in it to say *which workspace*, and extending it would mean
|
||||
//! forking a second upstream crate to add a field cosmic-settings' own UI would
|
||||
//! not know how to show.
|
||||
//!
|
||||
//! So this lives on `CosmicCompConfig` instead, next to the other keys the fork
|
||||
//! adds. The matching half is deliberately identical to the exceptions: `appid`
|
||||
//! and `title` are regular expressions, matched with the same `RegexSet` the
|
||||
//! tiling exceptions use, so one mental model covers both.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Which workspace a rule sends its window to.
|
||||
///
|
||||
/// A name is not a nicety here. Workspace numbers shift the moment an empty
|
||||
/// workspace is collected, so a rule written against a number is a rule that
|
||||
/// can quietly start pointing somewhere else; a name pins it to the workspace
|
||||
/// the user actually meant. Both are accepted because Hyprland's own
|
||||
/// `windowrule = workspace 4` is written with a number.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum WorkspaceTarget {
|
||||
/// 1-based, as the user counts them.
|
||||
Index(u32),
|
||||
/// Matched against `Workspace::name`, which is what `pinned_workspaces`
|
||||
/// sets and what waybar shows.
|
||||
Name(String),
|
||||
}
|
||||
|
||||
/// One rule: match a window, then place it.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct WindowRule {
|
||||
/// Regular expression matched against the window's app id.
|
||||
pub app_id: String,
|
||||
/// Regular expression matched against the window's title. An empty
|
||||
/// expression matches everything, which is what "no title condition" means.
|
||||
pub title: String,
|
||||
/// Where a matching window opens.
|
||||
pub workspace: WorkspaceTarget,
|
||||
}
|
||||
Reference in New Issue
Block a user