2 Commits

Author SHA1 Message Date
8d82429025 Restore hyprexpo on Hyprland Lua branch 2026-04-28 14:53:28 -07:00
9983a94421 Implement Hyprland Lua migration 2026-04-28 12:31:42 -07:00
7 changed files with 175 additions and 117 deletions

View File

@@ -78,8 +78,7 @@ verifier mode.
useful focus.
- [x] Add "move to empty workspace on monitor in direction" without requiring
`Hyper+Ctrl`.
- [x] Route directional focus in monocle through deterministic Lua cycling.
- [ ] Live-verify directional focus in monocle behaves predictably.
- [ ] Verify directional focus in monocle behaves predictably.
## 4. Script Elimination Priority
@@ -150,8 +149,6 @@ branch exposes `hl.plugin.hyprexpo.expo(...)`, so the Lua config can invoke
- [x] Implement minimize other windows of current workspace class.
- [x] Implement restore windows of focused class.
- [x] Decide hidden workspace naming/state model for minimized windows.
- [x] Hydrate minimized-window state from the hidden workspace on restore/picker
paths.
## 8. Class-Aware Workflows
@@ -202,17 +199,12 @@ needs a live readback check.
- [x] hyprNStack flake build check.
- [x] hyprexpo Lua-branch flake build check.
- [x] `ryzen-shine` system dry-run.
- [x] `just switch` activates successfully and deploys branch-owned
`~/.config/hypr/hyprland.lua`.
- [x] Re-run checks after Hyprland/Lua input confirmation.
- [ ] Try live compositor smoke test again after version bump.
- [x] Document `--verify-config` caveats for Lua rule/plugin-specific config.
- [x] Eventually run `just switch` only when the branch is coherent enough for a
- [ ] Eventually run `just switch` only when the branch is coherent enough for a
live test.
Live-smoke note: this Hyprland binary exposes `--verify-config` but no
`--headless` CLI flag. `just switch` now installs the Lua branch binary and
deploys `hyprland.lua`, but the currently running compositor remains the old
0.53 process until the Hyprland session is restarted. A true compositor smoke
test still needs a session restart or a nested Wayland session that avoids
startup side effects.
`--headless` CLI flag. A true compositor smoke test still needs either a nested
Wayland session that avoids startup side effects or an intentional `just switch`.

View File

@@ -10,7 +10,6 @@ local max_workspace = 9
local scratchpad_top_margin = 60
local columns_layout = "nStack"
local monocle_layout = "monocle"
local minimized_workspace = "special:minimized"
local current_layout = columns_layout
local workspace_layouts = {}
local minimized_windows = {}
@@ -219,19 +218,6 @@ local function is_scratchpad_window(window)
return false
end
local function is_minimized_workspace(workspace)
if not workspace then
return false
end
local name = tostring(workspace.name or "")
return name == minimized_workspace or name == "minimized" or (workspace.special and name:find("minimized", 1, true) ~= nil)
end
local function is_minimized_window(window)
return window and is_minimized_workspace(window.workspace)
end
local function is_normal_window(window)
return window
and window.mapped ~= false
@@ -239,7 +225,6 @@ local function is_normal_window(window)
and window.workspace
and is_normal_workspace(window.workspace)
and not is_scratchpad_window(window)
and not is_minimized_window(window)
end
local function tiled_windows(workspace)
@@ -404,19 +389,6 @@ local function monocle_prev()
end
end
local function focus_direction(direction)
if current_layout == monocle_layout then
if direction == "up" or direction == "left" then
monocle_prev()
else
monocle_next()
end
return
end
hl.dsp.focus({ direction = direction })()
end
local function focus_workspace(workspace_id)
hl.dsp.focus({ workspace = tostring(workspace_id), on_current_monitor = true })()
end
@@ -627,36 +599,6 @@ local function remove_minimized_window(target)
minimized_windows = remaining
end
local function add_minimized_window(window)
if not window or not window.address then
return
end
remove_minimized_window(window)
minimized_windows[#minimized_windows + 1] = window
end
local function hydrate_minimized_windows()
local by_address = {}
local hydrated = {}
for _, window in ipairs(minimized_windows) do
if window and window.address and not by_address[window.address] then
by_address[window.address] = true
hydrated[#hydrated + 1] = window
end
end
for _, window in ipairs(hl.get_windows()) do
if window and window.address and is_minimized_window(window) and not by_address[window.address] then
by_address[window.address] = true
hydrated[#hydrated + 1] = window
end
end
minimized_windows = hydrated
end
local function window_workspace_name(window)
return window and window.workspace and window.workspace.name or ""
end
@@ -751,9 +693,9 @@ local function adopt_matching_scratchpad_window(window)
for name, def in pairs(scratchpads) do
if scratchpad_window_matches(window, def) then
if scratchpad_pending[name] then
local pending = scratchpad_pending[name]
local target_monitor = scratchpad_pending[name]
scratchpad_pending[name] = nil
show_scratchpad_window(name, window, pending.workspace or active_workspace(), pending.monitor or hl.get_active_monitor())
show_scratchpad_window(name, window, nil, target_monitor)
elseif scratchpad_is_visible(window) then
schedule_scratchpad_geometry(name, window, hl.get_active_monitor())
end
@@ -762,11 +704,9 @@ local function adopt_matching_scratchpad_window(window)
end
local function current_minimized_windows()
hydrate_minimized_windows()
local windows = {}
for _, window in ipairs(minimized_windows) do
if window and window.address and is_minimized_window(window) then
if window and window.address then
windows[#windows + 1] = window
end
end
@@ -992,8 +932,8 @@ local function minimize_active_window()
return
end
add_minimized_window(window)
move_window_to_workspace(minimized_workspace, false, window)
minimized_windows[#minimized_windows + 1] = window
move_window_to_workspace("special:minimized", false, window)
end
local function restore_last_minimized()
@@ -1002,11 +942,9 @@ local function restore_last_minimized()
return
end
hydrate_minimized_windows()
while #minimized_windows > 0 do
local window = table.remove(minimized_windows)
if window and window.address and is_minimized_window(window) then
if window and window.address then
restore_minimized_window(window, workspace)
hl.dsp.focus({ window = window })()
return
@@ -1020,8 +958,6 @@ local function restore_all_minimized()
return
end
hydrate_minimized_windows()
while #minimized_windows > 0 do
restore_minimized_window(table.remove(minimized_windows), workspace)
end
@@ -1036,8 +972,8 @@ local function minimize_other_classes()
for _, window in ipairs(tiled_windows(workspace)) do
if window ~= focused and window.class ~= focused.class then
add_minimized_window(window)
move_window_to_workspace(minimized_workspace, false, window)
minimized_windows[#minimized_windows + 1] = window
move_window_to_workspace("special:minimized", false, window)
end
end
end
@@ -1049,11 +985,9 @@ local function restore_focused_class()
return
end
hydrate_minimized_windows()
local remaining = {}
for _, window in ipairs(minimized_windows) do
if window and window.class == focused.class and is_minimized_window(window) then
if window and window.class == focused.class then
restore_minimized_window(window, workspace)
else
remaining[#remaining + 1] = window
@@ -1074,10 +1008,7 @@ local function toggle_scratchpad(name)
local windows = matching_scratchpad_windows(name)
if #windows == 0 then
scratchpad_pending[name] = {
monitor = hl.get_active_monitor(),
workspace = active_workspace(),
}
scratchpad_pending[name] = hl.get_active_monitor()
hl.exec_cmd(def.command)
return
end
@@ -1248,18 +1179,10 @@ bind(main_mod .. " + SHIFT + B", function()
enter_window_picker("replace")
end)
bind(main_mod .. " + W", function()
focus_direction("up")
end)
bind(main_mod .. " + S", function()
focus_direction("down")
end)
bind(main_mod .. " + A", function()
focus_direction("left")
end)
bind(main_mod .. " + D", function()
focus_direction("right")
end)
bind(main_mod .. " + W", hl.dsp.focus({ direction = "up" }))
bind(main_mod .. " + S", hl.dsp.focus({ direction = "down" }))
bind(main_mod .. " + A", hl.dsp.focus({ direction = "left" }))
bind(main_mod .. " + D", hl.dsp.focus({ direction = "right" }))
bind(main_mod .. " + SHIFT + W", hl.dsp.window.swap({ direction = "up" }))
bind(main_mod .. " + SHIFT + S", hl.dsp.window.swap({ direction = "down" }))

View File

@@ -10,6 +10,7 @@ makeEnable config "myModules.code" true {
antigravity
claude-code
codex
codex-desktop
gemini-cli
happy-coder
opencode

28
nixos/flake.lock generated
View File

@@ -186,6 +186,29 @@
"type": "github"
}
},
"codex-desktop-linux": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1777351752,
"narHash": "sha256-kwdZPCidd9kPYASk6fUPcDfg2uDQ9NzwtYqLlwwzFVk=",
"owner": "ilysenko",
"repo": "codex-desktop-linux",
"rev": "40fd7a8bd6f229e23194881b972fddb2dc42c4c8",
"type": "github"
},
"original": {
"owner": "ilysenko",
"repo": "codex-desktop-linux",
"type": "github"
}
},
"coqui-tts-streamer": {
"inputs": {
"flake-utils": [
@@ -1115,11 +1138,11 @@
]
},
"locked": {
"lastModified": 1777413654,
"lastModified": 1777412965,
"narHash": "sha256-lVGYGUWf9ynV5lR8QAygAfmYRkko1btIe26UcQ1bGXw=",
"owner": "colonelpanic8",
"repo": "hyprland-plugins",
"rev": "ff36c04b270c26fcd53e623fc688e4eb41672897",
"rev": "fd5a23b732d881233941402984cbc4903e6ed927",
"type": "github"
},
"original": {
@@ -2128,6 +2151,7 @@
"caelestia-shell": "caelestia-shell",
"claude-code-nix": "claude-code-nix",
"codex-cli-nix": "codex-cli-nix",
"codex-desktop-linux": "codex-desktop-linux",
"coqui-tts-streamer": "coqui-tts-streamer",
"flake-utils": "flake-utils",
"git-blame-rank": "git-blame-rank",

View File

@@ -218,6 +218,14 @@
};
};
codex-desktop-linux = {
url = "github:ilysenko/codex-desktop-linux";
inputs = {
nixpkgs.follows = "nixpkgs";
flake-utils.follows = "flake-utils";
};
};
claude-code-nix = {
url = "github:sadjow/claude-code-nix";
inputs = {

View File

@@ -39,15 +39,7 @@ let
home-manager.sharedModules = [
inputs.hyprscratch.homeModules.default
({ config, ... }: {
xdg.configFile."hypr" = {
force = true;
source =
if cfg.useLuaConfigBranch
then ../dotfiles/config/hypr
else config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/dotfiles/dotfiles/config/hypr";
};
{
services.kanshi = {
enable = true;
systemdTarget = "graphical-session.target";
@@ -147,7 +139,7 @@ let
};
};
};
})
}
];
# Hyprland-specific packages

View File

@@ -109,8 +109,126 @@
(import ./emacs-overlay.nix)
(import ../nix-shared/overlays)
# Use codex and claude-code from dedicated flakes with cachix
(final: prev: {
codex = inputs.codex-cli-nix.packages.${prev.stdenv.hostPlatform.system}.default;
(final: prev: let
system = prev.stdenv.hostPlatform.system;
codexDmg = final.fetchurl {
url = "https://persistent.oaistatic.com/codex-app-prod/Codex.dmg";
hash = "sha256-hxuafsEAmx1OQvjh8riI7Y4QxvZXemBrjpRHT8Bh034=";
};
codexDesktopLibPath = final.lib.makeLibraryPath (with final; [
alsa-lib
atk
at-spi2-atk
at-spi2-core
cairo
cups
dbus
expat
gdk-pixbuf
glib
gtk3
libdrm
libgbm
libglvnd
libX11
libxcb
libXcomposite
libxcursor
libXdamage
libXext
libXfixes
libxi
libxkbcommon
libXrandr
libxscrnsaver
libxtst
mesa
nspr
nss
pango
systemd
wayland
]);
codexDesktopInstaller = final.writeShellApplication {
name = "codex-desktop-installer";
runtimeInputs = with final; [
bash
curl
gcc
gnumake
nodejs
p7zip
patchelf
python3
unzip
];
text = ''
set -euo pipefail
root_dir="$(pwd)"
workdir="$(mktemp -d)"
source_dir="$workdir/source"
cleanup() {
rm -rf "$workdir"
}
trap cleanup EXIT
mkdir -p "$source_dir"
cp -R ${inputs.codex-desktop-linux.outPath}/. "$source_dir"
chmod -R u+w "$source_dir"
cp ${codexDmg} "$source_dir/Codex.dmg"
chmod +x "$source_dir/install.sh"
cd "$source_dir"
export CODEX_INSTALL_DIR="''${CODEX_INSTALL_DIR:-$root_dir/codex-app}"
bash "$source_dir/install.sh" "$source_dir/Codex.dmg" "$@"
install_dir="''${CODEX_INSTALL_DIR:-$root_dir/codex-app}"
if [ -f "$install_dir/electron" ]; then
patchelf --set-interpreter "$(cat ${final.stdenv.cc}/nix-support/dynamic-linker)" \
--set-rpath "$install_dir:${codexDesktopLibPath}" \
"$install_dir/electron"
if [ -f "$install_dir/chrome_crashpad_handler" ]; then
patchelf --set-interpreter "$(cat ${final.stdenv.cc}/nix-support/dynamic-linker)" \
"$install_dir/chrome_crashpad_handler" || true
fi
if [ -f "$install_dir/chrome-sandbox" ]; then
patchelf --set-interpreter "$(cat ${final.stdenv.cc}/nix-support/dynamic-linker)" \
"$install_dir/chrome-sandbox" || true
fi
find "$install_dir" -maxdepth 1 -name "*.so*" -type f | while read -r so; do
patchelf --set-rpath "${codexDesktopLibPath}" "$so" 2>/dev/null || true
done
fi
'';
};
in {
codex = inputs.codex-cli-nix.packages.${system}.default;
codex-desktop-installer = codexDesktopInstaller;
codex-desktop = final.writeShellApplication {
name = "codex-desktop";
runtimeInputs = [
final.codex
final.codex-desktop-installer
final.coreutils
final.python3
];
text = ''
install_root="''${CODEX_DESKTOP_HOME:-''${XDG_DATA_HOME:-$HOME/.local/share}/codex-desktop-linux}"
install_dir="''${CODEX_INSTALL_DIR:-$install_root/codex-app}"
if [ ! -x "$install_dir/start.sh" ]; then
mkdir -p "$install_root"
CODEX_INSTALL_DIR="$install_dir" codex-desktop-installer
fi
export CODEX_CLI_PATH="''${CODEX_CLI_PATH:-$(command -v codex)}"
exec "$install_dir/start.sh" "$@"
'';
};
claude-code = inputs.claude-code-nix.packages.${prev.stdenv.hostPlatform.system}.default;
git-sync-rs = inputs.git-sync-rs.packages.${prev.stdenv.hostPlatform.system}.default;
})