feat(hyprland): launch-or-focus binds (item 17)
All checks were successful
Check / eval (push) Successful in 3m4s
All checks were successful
Check / eval (push) Successful in 3m4s
nomarchy.launchOrFocus (opt-in, commented template example): each entry gives a key that FOCUSES the app's existing window (case-insensitive class match via jq + a "(?i)" focuswindow regex) or launches it when none is open. One shared nomarchy-focus-or-launch script; a bind whose command was removed from the suite fires a "not installed" toast instead of failing silently. The same entries render into the SUPER+? cheatsheet through prettyKeys — bind and documentation stay one source. README row added. Verified: V0; V1 — a scratch downstream with two entries renders both binds (default command = lowercased class; custom mods/command/desc honored), the script passes bash -n with a usage guard, and the cheatsheet carries both rows. Focus behaviour needs a session -> V3 (HARDWARE-QUEUE). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -229,6 +229,7 @@ two tables below are split along exactly that line.
|
|||||||
| `nomarchy.mime.enable` | `true` | Default "open with" associations (PDF/image/video/text/browser/directory); entries for apps you removed are skipped, so it degrades with the suite |
|
| `nomarchy.mime.enable` | `true` | Default "open with" associations (PDF/image/video/text/browser/directory); entries for apps you removed are skipped, so it degrades with the suite |
|
||||||
| `nomarchy.monitors` | `[]` | Declarative per-output layout → Hyprland `monitor` rules (applied on hotplug); `,preferred,auto,1` wildcard kept as fallback |
|
| `nomarchy.monitors` | `[]` | Declarative per-output layout → Hyprland `monitor` rules (applied on hotplug); `,preferred,auto,1` wildcard kept as fallback |
|
||||||
| `nomarchy.displayProfiles` | `{}` | Named layouts for the same outputs (docked/undocked/…), switched from System › Display › Profiles: instant via hyprctl, persisted in-flake (`settings.displayProfile`), baked over `nomarchy.monitors` at the next rebuild. The menu's Auto-switch row (`settings.displayProfileAuto`) makes plugging/unplugging outputs apply the matching profile automatically |
|
| `nomarchy.displayProfiles` | `{}` | Named layouts for the same outputs (docked/undocked/…), switched from System › Display › Profiles: instant via hyprctl, persisted in-flake (`settings.displayProfile`), baked over `nomarchy.monitors` at the next rebuild. The menu's Auto-switch row (`settings.displayProfileAuto`) makes plugging/unplugging outputs apply the matching profile automatically |
|
||||||
|
| `nomarchy.launchOrFocus` | `[]` | Launch-or-focus binds: `SUPER+<key>` focuses the app's window (case-insensitive class match) or launches it; entries land in the SUPER+? cheatsheet, and a bind whose app was removed notifies instead of failing silently |
|
||||||
| `nomarchy.themesDir` | Nomarchy's `themes/` | Where per-theme app overrides are probed |
|
| `nomarchy.themesDir` | Nomarchy's `themes/` | Where per-theme app overrides are probed |
|
||||||
| `nomarchy.package` | overlay's `nomarchy-theme-sync` | The theme/state tool package, overridable if you fork it |
|
| `nomarchy.package` | overlay's `nomarchy-theme-sync` | The theme/state tool package, overridable if you fork it |
|
||||||
|
|
||||||
|
|||||||
@@ -214,11 +214,6 @@ the *same* outputs. **Plan (designed 2026-07-05, iteration #27):**
|
|||||||
- **Non-goals:** no kanshi (fights Hyprland's output management —
|
- **Non-goals:** no kanshi (fights Hyprland's output management —
|
||||||
decided in ROADMAP), nwg-displays stays a find-the-values helper.
|
decided in ROADMAP), nwg-displays stays a find-the-values helper.
|
||||||
|
|
||||||
### 17. Launch-or-focus UX scripts
|
|
||||||
ROADMAP § launch-or-focus. Hyprland dispatch scripts: a bind launches an
|
|
||||||
app or focuses its existing window. Curate which apps get binds;
|
|
||||||
cheatsheet entries via keybinds.nix.
|
|
||||||
|
|
||||||
### 18. "nomarchy" control center + first-boot welcome `[big]`
|
### 18. "nomarchy" control center + first-boot welcome `[big]`
|
||||||
ROADMAP § control center. A single front-end over the common toggles on
|
ROADMAP § control center. A single front-end over the common toggles on
|
||||||
the same `nomarchy-theme-sync` surface, + a first-boot guided
|
the same `nomarchy-theme-sync` surface, + a first-boot guided
|
||||||
|
|||||||
@@ -167,6 +167,12 @@ QA machine), the **T14s** (webcam case).
|
|||||||
(tooltips, fastfetch labels) is visible on summer-day +
|
(tooltips, fastfetch labels) is visible on summer-day +
|
||||||
flexoki-light, kanagawa floats are lighter than the bg (upstream
|
flexoki-light, kanagawa floats are lighter than the bg (upstream
|
||||||
sumiInk4). Anything that reads worse than before → reopen 28b.
|
sumiInk4). Anything that reads worse than before → reopen 28b.
|
||||||
|
- [ ] **Launch-or-focus (item 17)** — uncomment the template's
|
||||||
|
`nomarchy.launchOrFocus` firefox example (with firefox
|
||||||
|
installed), `home-update` + reload: SUPER+B launches firefox
|
||||||
|
when closed, FOCUSES the existing window when open (also from
|
||||||
|
another workspace); with firefox removed the bind fires a
|
||||||
|
"not installed" toast; the row shows under SUPER+?.
|
||||||
- [ ] **Themed greeter + console (item 16)** — after `sys-rebuild` +
|
- [ ] **Themed greeter + console (item 16)** — after `sys-rebuild` +
|
||||||
logout: tuigreet renders in theme colors (dark container, accent
|
logout: tuigreet renders in theme colors (dark container, accent
|
||||||
border, themed prompt/time) and a raw tty (CTRL+ALT+F2) shows the
|
border, themed prompt/time) and a raw tty (CTRL+ALT+F2) shows the
|
||||||
|
|||||||
@@ -17,6 +17,28 @@ Template:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-07-05 — launch-or-focus binds (iteration #39, item 17)
|
||||||
|
- **Task:** BACKLOG NEXT#17 (the UI review is Bernardo-gated; back to
|
||||||
|
the queue head). Item deleted.
|
||||||
|
- **Did:** nomarchy.launchOrFocus (listOf submodule: key/mods/class/
|
||||||
|
command/desc; opt-in, template example) → hyprland.nix generates
|
||||||
|
binds through a shared nomarchy-focus-or-launch script (jq
|
||||||
|
case-insensitive class match → focuswindow "(?i)" regex, else
|
||||||
|
launch; uninstalled command → toast, so binds survive suite edits);
|
||||||
|
rofi.nix renders the same entries into the SUPER+? cheatsheet via
|
||||||
|
prettyKeys (single source). README row.
|
||||||
|
- **Verified:** V0; V1 — scratch downstream with 2 entries: both
|
||||||
|
binds rendered (default command = lowercased class; custom
|
||||||
|
mods/command/desc honored), tool bash -n + usage exit 64,
|
||||||
|
cheatsheet rows present. Focus behaviour needs a session → V3
|
||||||
|
queued.
|
||||||
|
- **Pending:** V3. Queue after this: #18 control center [big, needs
|
||||||
|
the form-factor Decision], #19 Look&Feel menu (gated on ≥3
|
||||||
|
entries), #20 [human], LATER tier.
|
||||||
|
- **Next suggestion:** a QA sweep (#18/#19/#20 are blocked-ish) — or
|
||||||
|
item 19's precondition check (does Look & Feel earn 3 entries
|
||||||
|
yet?).
|
||||||
|
|
||||||
## 2026-07-05 — placeholder dim + latte review + backdrop probe (iteration #38, item 28c)
|
## 2026-07-05 — placeholder dim + latte review + backdrop probe (iteration #38, item 28c)
|
||||||
- **Task:** item 28 slice (c) — the summer-rasi quickie + the light-
|
- **Task:** item 28 slice (c) — the summer-rasi quickie + the light-
|
||||||
theme capture.
|
theme capture.
|
||||||
|
|||||||
@@ -31,6 +31,34 @@ let
|
|||||||
${pkgs.swayosd}/bin/swayosd-client --custom-icon "$icon" --custom-progress "$2"
|
${pkgs.swayosd}/bin/swayosd-client --custom-icon "$icon" --custom-progress "$2"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
# Launch-or-focus (nomarchy.launchOrFocus, item 17): focus the app's
|
||||||
|
# window if one exists (case-insensitive class match), launch it
|
||||||
|
# otherwise. Self-gates: an uninstalled command notifies instead of
|
||||||
|
# failing silently, so a bind survives the app being removed from the
|
||||||
|
# suite. rofi.nix renders the same entries into the SUPER+? cheatsheet.
|
||||||
|
focusOrLaunch = pkgs.writeShellScriptBin "nomarchy-focus-or-launch" ''
|
||||||
|
class="''${1:-}"; shift || true
|
||||||
|
[ -n "$class" ] && [ $# -gt 0 ] || {
|
||||||
|
echo "usage: nomarchy-focus-or-launch <class> <command...>" >&2; exit 64
|
||||||
|
}
|
||||||
|
if hyprctl clients -j 2>/dev/null \
|
||||||
|
| ${pkgs.jq}/bin/jq -e --arg c "$class" \
|
||||||
|
'any(.[]; (.class | ascii_downcase) == ($c | ascii_downcase))' >/dev/null; then
|
||||||
|
exec hyprctl dispatch focuswindow "class:(?i)^$class\$"
|
||||||
|
fi
|
||||||
|
command -v "$1" >/dev/null 2>&1 \
|
||||||
|
|| { notify-send "Launch" "$1 is not installed."; exit 0; }
|
||||||
|
exec "$@"
|
||||||
|
'';
|
||||||
|
lofEntries = map
|
||||||
|
(e: e // {
|
||||||
|
command = if e.command == "" then lib.toLower e.class else e.command;
|
||||||
|
})
|
||||||
|
config.nomarchy.launchOrFocus;
|
||||||
|
lofBinds = map
|
||||||
|
(e: "${e.mods}, ${e.key}, exec, ${focusOrLaunch}/bin/nomarchy-focus-or-launch ${e.class} ${e.command}")
|
||||||
|
lofEntries;
|
||||||
|
|
||||||
# SUPER+1..9 / SUPER+SHIFT+1..9 workspace binds, generated.
|
# SUPER+1..9 / SUPER+SHIFT+1..9 workspace binds, generated.
|
||||||
workspaceBinds = builtins.concatLists (builtins.genList
|
workspaceBinds = builtins.concatLists (builtins.genList
|
||||||
(i:
|
(i:
|
||||||
@@ -393,7 +421,8 @@ in
|
|||||||
bind = map mkBind (keybinds.binds
|
bind = map mkBind (keybinds.binds
|
||||||
++ lib.optionals (lib.hasInfix "," config.nomarchy.keyboard.layout)
|
++ lib.optionals (lib.hasInfix "," config.nomarchy.keyboard.layout)
|
||||||
keybinds.multiLayoutBinds)
|
keybinds.multiLayoutBinds)
|
||||||
++ workspaceBinds;
|
++ workspaceBinds
|
||||||
|
++ lofBinds;
|
||||||
|
|
||||||
# Media keys via swayosd-client: it performs the action AND shows
|
# Media keys via swayosd-client: it performs the action AND shows
|
||||||
# the on-screen display (the nomarchy.osd module). e = repeat,
|
# the on-screen display (the nomarchy.osd module). e = repeat,
|
||||||
|
|||||||
@@ -237,6 +237,52 @@ in
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
launchOrFocus = lib.mkOption {
|
||||||
|
type = lib.types.listOf (lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
key = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
example = "B";
|
||||||
|
description = "The key (with `mods`) that focuses-or-launches the app.";
|
||||||
|
};
|
||||||
|
mods = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "$mod";
|
||||||
|
description = "Modifier string, Hyprland syntax (\"$mod\", \"$mod SHIFT\").";
|
||||||
|
};
|
||||||
|
class = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
example = "firefox";
|
||||||
|
description = "Window class to focus (case-insensitive; see `hyprctl clients`).";
|
||||||
|
};
|
||||||
|
command = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "";
|
||||||
|
description = "Command to launch when no window matches (defaults to the class).";
|
||||||
|
};
|
||||||
|
desc = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "";
|
||||||
|
description = "Cheatsheet label (defaults to \"Focus or launch <command>\").";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = [ ];
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
[
|
||||||
|
{ key = "B"; class = "firefox"; }
|
||||||
|
{ key = "O"; class = "obsidian"; }
|
||||||
|
]
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
Launch-or-focus binds: the key focuses the app's existing window
|
||||||
|
(case-insensitive class match) or launches it if none is open. Each
|
||||||
|
entry generates a Hyprland bind AND a SUPER+? cheatsheet row. The
|
||||||
|
launcher self-gates: a bind whose command isn't installed notifies
|
||||||
|
instead of failing silently.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
displayProfiles = lib.mkOption {
|
displayProfiles = lib.mkOption {
|
||||||
type = lib.types.attrsOf (lib.types.listOf monitorType);
|
type = lib.types.attrsOf (lib.types.listOf monitorType);
|
||||||
default = { };
|
default = { };
|
||||||
|
|||||||
@@ -125,6 +125,12 @@ let
|
|||||||
map (b: padRight 22 (prettyKeys b) + b.desc) (keybinds.binds
|
map (b: padRight 22 (prettyKeys b) + b.desc) (keybinds.binds
|
||||||
++ lib.optionals (lib.hasInfix "," cfg.keyboard.layout)
|
++ lib.optionals (lib.hasInfix "," cfg.keyboard.layout)
|
||||||
keybinds.multiLayoutBinds)
|
keybinds.multiLayoutBinds)
|
||||||
|
# Launch-or-focus binds (nomarchy.launchOrFocus, generated in
|
||||||
|
# hyprland.nix) — same renderer, so they land in the cheatsheet too.
|
||||||
|
++ map (e: padRight 22 (prettyKeys e)
|
||||||
|
+ (if e.desc != "" then e.desc
|
||||||
|
else "Focus or launch ${if e.command == "" then lib.toLower e.class else e.command}"))
|
||||||
|
cfg.launchOrFocus
|
||||||
++ map (e: padRight 22 e.keys + e.desc) keybinds.extra;
|
++ map (e: padRight 22 e.keys + e.desc) keybinds.extra;
|
||||||
cheatsheetFile = pkgs.writeText "nomarchy-keybinds.txt"
|
cheatsheetFile = pkgs.writeText "nomarchy-keybinds.txt"
|
||||||
(lib.concatStringsSep "\n" cheatRows + "\n");
|
(lib.concatStringsSep "\n" cheatRows + "\n");
|
||||||
|
|||||||
@@ -34,6 +34,11 @@
|
|||||||
# { name = "HDMI-A-1"; position = "auto-right"; }
|
# { name = "HDMI-A-1"; position = "auto-right"; }
|
||||||
# ]; # names from `hyprctl devices`
|
# ]; # names from `hyprctl devices`
|
||||||
#
|
#
|
||||||
|
# nomarchy.launchOrFocus = [ # SUPER+<key> focuses the app's window,
|
||||||
|
# { key = "B"; class = "firefox"; } # or launches it if none is open;
|
||||||
|
# { key = "O"; class = "obsidian"; }# rows appear in the SUPER+? cheatsheet
|
||||||
|
# ]; # (class from `hyprctl clients`)
|
||||||
|
#
|
||||||
# nomarchy.displayProfiles = { # named layouts for the SAME outputs —
|
# nomarchy.displayProfiles = { # named layouts for the SAME outputs —
|
||||||
# docked = [ # switch: System › Display › Profiles
|
# docked = [ # switch: System › Display › Profiles
|
||||||
# { name = "eDP-1"; resolution = "disable"; }
|
# { name = "eDP-1"; resolution = "disable"; }
|
||||||
|
|||||||
Reference in New Issue
Block a user