Skip to content

Add window switcher with Alt+Tab and snappy-switcher integration - #892

Closed
shunkakinoki wants to merge 3 commits into
mainfrom
claude/implement-video-tutorial-1nksz
Closed

Add window switcher with Alt+Tab and snappy-switcher integration#892
shunkakinoki wants to merge 3 commits into
mainfrom
claude/implement-video-tutorial-1nksz

Conversation

@shunkakinoki

@shunkakinoki shunkakinoki commented Feb 22, 2026

Copy link
Copy Markdown
Owner

Summary

This PR adds two complementary window switching mechanisms to Hyprland: a custom Alt+Tab script that mimics Windows/macOS focus-history behavior, and integration with snappy-switcher for Super+Tab visual window switching.

Key Changes

  • Added alt-tab.sh script: A comprehensive bash script that implements focus-history-based window switching with the following features:

    • Alt+Tab cycles through windows in focus history order (most recent first)
    • Alt+Shift+Tab reverses the cycle direction
    • Supports --same flag to filter windows by class type
    • Supports --visible flag to limit switching to visible workspaces
    • Maintains session state with cooldown detection (0.8s) to distinguish between continuous Alt+Tab presses and new sessions
    • Provides visual feedback by temporarily dimming inactive windows
    • Validates window existence and handles edge cases (closed windows, empty lists)
  • Added snappy-switcher integration:

    • Configured snappy-switcher as a daemon for Super+Tab visual window switching
    • Added snappy-switcher-config.ini with Dracula theme and layout customization
    • Super+Tab and Super+Shift+Tab now trigger snappy-switcher for visual window selection
  • Updated Hyprland keybindings:

    • Replaced Super+Tab (hyprexpo) with snappy-switcher for window switching
    • Added Alt+Tab and Alt+Shift+Tab for focus-history-based switching
    • Added Super+Shift+Tab for reverse direction in snappy-switcher
  • Added dependencies:

    • Added bc package for floating-point arithmetic in the alt-tab script
    • Added snappy-switcher flake input and package

Implementation Details

The alt-tab script uses temporary state files to track:

  • Last key press time (for cooldown detection)
  • Current window index in the active list
  • The current window list (to detect when the list changes)

This allows it to distinguish between a user holding Alt and repeatedly pressing Tab (continuing session) versus pressing Alt+Tab after a delay (new session). The script also validates that windows still exist before focusing them, preventing errors when windows are closed during switching.

https://claude.ai/code/session_01NNjd5QrBK887SPA9wLsBZx


Summary by cubic

Replaces hyprexpo and the old Alt+Tab script with snappy-switcher for Super+Tab window switching. Adds a Dracula-themed config, starts a daemon at login, and moves the config to its own module.

  • New Features

    • Super+Tab and Super+Shift+Tab call snappy-switcher next/prev.
    • snappy-switcher runs as a daemon with a Dracula-styled grid.
  • Dependencies

    • Added snappy-switcher as a flake input and package.
    • Added bc.

Written for commit 02988f9. Summary will update on new commits.

Add a focus-history-based Alt+Tab window switcher for Hyprland, inspired
by the saneAspect tutorial. Uses hyprctl to cycle windows by recency
(like Windows/macOS), with session-aware state persistence for continuous
switching, reverse cycling (ALT+Shift+Tab), and temporary dim feedback.

https://claude.ai/code/session_01NNjd5QrBK887SPA9wLsBZx
Use snappy-switcher (OpalAayan/snappy-switcher) for Super+Tab instead of
hyprexpo. Adds daemon startup, Dracula-themed config, and Super+Shift+Tab
for reverse cycling.

https://claude.ai/code/session_01NNjd5QrBK887SPA9wLsBZx
Copilot AI review requested due to automatic review settings February 22, 2026 05:23
@mesa-dot-dev

mesa-dot-dev Bot commented Feb 22, 2026

Copy link
Copy Markdown

You do not have enough credits to review this pull request. Please purchase more credits to continue.

@coderabbitai

coderabbitai Bot commented Feb 22, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@shunkakinoki has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 22 minutes and 55 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

This change integrates window switching functionality into Hyprland by adding snappy-switcher and a custom alt-tab script. Configuration files, keybindings, and package dependencies are updated across multiple files to support both switchers, with snappy-switcher pulled as a new flake input and included in desktop packages.

Changes

Cohort / File(s) Summary
Hyprland Configuration
config/hyprland/default.nix, config/hyprland/hyprland.conf
Registers new executable alt-tab.sh script and snappy-switcher-config.ini in xdg.configFile. Adds daemon startup via exec-once. Replaces mod+Tab/Shift+Tab bindings from hyprexpo to snappy-switcher and adds Alt+Tab/Shift+Alt+Tab bindings invoking alt-tab.sh.
Window Switcher Script
config/hyprland/scripts/alt-tab.sh
New 259-line Bash script implementing stateful Alt+Tab window cycling with cooldown detection, class filtering, visibility filtering, jq-based JSON parsing of hyprctl output, and temporary dimming decorations.
Snappy-Switcher Configuration
config/hyprland/snappy-switcher-config.ini
New INI file containing UI/theme settings for snappy-switcher including dracula theme, card dimensions, layout padding, and font configuration.
Flake and Package Management
flake.nix, home-manager/packages/default.nix
Adds snappy-switcher input from GitHub. Includes snappy-switcher package in new Linux+Desktop conditional group alongside desktop utilities; adds bc to base packages.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 A switcher swift, two paths we've made,
Alt-Tab dances, Snappy's played,
Windows cycling round and round,
State persists—no lag is found,
Hyprland hops with joy today! 🪟✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: adding Alt+Tab window switching and snappy-switcher integration to Hyprland.
Description check ✅ Passed The description comprehensively explains the changes, including the Alt+Tab script implementation, snappy-switcher integration, keybinding updates, and dependencies added.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/implement-video-tutorial-1nksz

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @shunkakinoki, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the window switching experience in Hyprland by introducing two complementary mechanisms. It provides a custom Alt+Tab script that offers a familiar, focus-history-based window cycling, complete with state management and visual feedback. Concurrently, it integrates snappy-switcher to enable a visual, graphical window selection interface via Super+Tab. These additions aim to offer users more flexible and intuitive ways to navigate between open applications, improving overall desktop usability.

Highlights

  • Alt+Tab Script Added: A new alt-tab.sh script was introduced, providing focus-history-based window switching similar to Windows/macOS, including reverse cycling, same-type filtering, and visible workspace limiting.
  • Snappy-Switcher Integration: The snappy-switcher application was integrated for visual window switching, configured to run as a daemon and styled with a Dracula theme.
  • Hyprland Keybindings Updated: Keybindings were reconfigured to utilize the new window switchers: Super+Tab for snappy-switcher and Alt+Tab / Alt+Shift+Tab for the custom alt-tab.sh script.
  • Dependencies Added: The bc package was added for floating-point arithmetic in the alt-tab.sh script, and snappy-switcher was included as a flake input and package.
Changelog
  • config/hyprland/default.nix
    • Defined the snappySwitcher package from inputs.
    • Configured snappy-switcher --daemon to execute on Hyprland startup.
    • Added alt-tab.sh script and snappy-switcher-config.ini to be managed as XDG config files.
  • config/hyprland/hyprland.conf
    • Replaced Super+Tab binding from hyprexpo to snappy-switcher next.
    • Added Super+Shift+Tab binding for snappy-switcher prev.
    • Introduced Alt+Tab and Alt+Shift+Tab bindings to execute the new alt-tab.sh script for focus-history switching.
  • config/hyprland/scripts/alt-tab.sh
    • Added a new bash script implementing a comprehensive Alt+Tab window switcher.
    • Included functions for getting window details, filtering by focus history, class, and visibility.
    • Implemented state management with cooldown detection to distinguish continuous presses from new sessions.
    • Added visual feedback by temporarily dimming inactive windows.
  • config/hyprland/snappy-switcher-config.ini
    • Added a new configuration file for snappy-switcher.
    • Configured mode = context, set the theme to dracula.ini, and customized layout and font properties.
  • flake.nix
    • Added snappy-switcher as a new input to the Nix flake, sourcing it from its GitHub repository.
  • home-manager/packages/default.nix
    • Added the bc package to the list of home-manager managed packages.
    • Included snappy-switcher as a desktop-specific package.
Activity
  • No human activity has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@mesa-dot-dev

mesa-dot-dev Bot commented Feb 22, 2026

Copy link
Copy Markdown

Mesa Description

TL;DR

Adds two window switchers to Hyprland: a focus-history Alt+Tab script and a visual switcher via snappy-switcher on Super+Tab. This makes switching fast and familiar to Windows/macOS users.

What changed?

  • config/hyprland/default.nix: Integrated snappy-switcher and alt-tab.sh into Hyprland configuration, adding a daemon launch and XDG config files for both.
  • config/hyprland/hyprland.conf: Updated keybindings to use snappy-switcher for Super+Tab and Super+Shift+Tab, and introduced Alt+Tab/Alt+Shift+Tab for the new focus-history script.
  • config/hyprland/scripts/alt-tab.sh: Added a new shell script for Alt+Tab-like window switching based on focus history, with filtering options and visual feedback.
  • config/hyprland/snappy-switcher-config.ini: Created a new configuration for snappy-switcher, specifying its mode, Dracula theme, and layout settings.
  • flake.nix: Added snappy-switcher as a new flake input from github:OpalAayan/snappy-switcher.
  • home-manager/packages/default.nix: Added bc to general packages and snappy-switcher to desktop-specific Linux packages.

Description generated by Mesa. Update settings

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 6 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="config/hyprland/scripts/alt-tab.sh">

<violation number="1" location="config/hyprland/scripts/alt-tab.sh:132">
P2: `local` is used outside a function, which causes a runtime error and stops the script. Declare the variable without `local` in this top-level block.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread config/hyprland/scripts/alt-tab.sh Outdated
- Move snappy-switcher config from config/hyprland/ to config/snappy-switcher/
  with its own default.nix module (matching repo convention)
- Delete config/hyprland/scripts/alt-tab.sh (replaced by snappy-switcher)
- Remove alt-tab.sh bindings and xdg.configFile entries from hyprland config

https://claude.ai/code/session_01NNjd5QrBK887SPA9wLsBZx

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces two new window switching mechanisms: a custom alt-tab.sh script for focus-history based switching, and integration with snappy-switcher for visual switching. While the overall integration is well-structured, a high-severity security vulnerability was identified in the alt-tab.sh script regarding insecure temporary file handling in /tmp. This could allow a local attacker to perform a symlink attack, leading to data destruction or a denial of service by killing arbitrary user processes. It is recommended to move the state directory to a user-specific, secure location like $XDG_RUNTIME_DIR. Additionally, the alt-tab.sh script could benefit from performance improvements by reducing redundant shell command executions, which can become noticeable with many open windows.

I am having trouble creating individual review comments. Click here to see my feedback.

config/hyprland/scripts/alt-tab.sh (3)

security-high high

The script uses a predictable directory /tmp/hypr-alt-tab to store state and PID files. Since /tmp is world-writable, an attacker can pre-create this directory and place symlinks inside it. When the script (run by a user) writes to these files (e.g., echo $! > "$STATE_DIR/dim_timer" on line 259), it will follow the symlink and overwrite the target file with the user's privileges. This can be used to destroy user data or cause a Denial of Service by killing arbitrary processes (via the kill command on line 252).

To remediate this, use a more secure, user-specific location for temporary files, such as a directory under $XDG_RUNTIME_DIR. Additionally, ensure the directory is created with restricted permissions (e.g., by using mkdir -p -m 0700 "$STATE_DIR" on line 13).

STATE_DIR="${XDG_RUNTIME_DIR:-/tmp}/hypr-alt-tab-${USER}"

config/hyprland/scripts/alt-tab.sh (102-114)

high

This function is inefficient because it calls window_exists for every window in a loop. The window_exists function in turn executes hyprctl clients -j, which is a relatively expensive operation. For a list of N windows, you are making N hyprctl calls just in this function.

This, and other parts of the script, can be significantly optimized by fetching the list of all clients once at the beginning of the script and using that cached data throughout.

Recommendation:

  1. At the start of the script, cache the client data:
    CLIENTS_JSON=$(hyprctl clients -j)
  2. Then, rewrite this function to use the cached data with a hash map for efficient lookups. This avoids calling hyprctl in a loop:
    validate_window_list() {
        local windows=("$@")
        local valid_windows=()
    
        # Create a hash map of existing windows for O(1) lookups
        declare -A existing_windows_map
        while read -r addr; do
            existing_windows_map["$addr"]=1
        done < <(echo "$CLIENTS_JSON" | jq -r '.[].address')
    
        for window in "${windows[@]}"; do
            if [[ -v existing_windows_map[$window] ]]; then
                valid_windows+=("$window")
            fi
        done
    
        printf '%s\n' "${valid_windows[@]}"
    }

Applying this pattern to all functions that call hyprctl clients -j will make the script much faster.

config/hyprland/scripts/alt-tab.sh (4)

medium

The variable STATE_FILE is defined but never used in the script. It can be safely removed to improve code clarity.

config/hyprland/scripts/alt-tab.sh (132-140)

medium

The nested loop to find the intersection of windows and same_type_windows has a time complexity of O(N*M), which can be slow if there are many windows. This can be optimized by using an associative array (hash map) for one of the lists to achieve more efficient O(N+M) complexity.

        declare -A same_type_map
        for w in "${same_type_windows[@]}"; do same_type_map["$w"]=1; done

        local filtered_windows=()
        for visible_window in "${windows[@]}"; do
            if [[ -v same_type_map[$visible_window] ]]; then
                filtered_windows+=("$visible_window")
            fi
        done

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
home-manager/packages/default.nix (1)

19-19: bc is only needed by alt-tab.sh, which is Linux-desktop only.

Consider moving bc into the lib.optionals (stdenv.isLinux && isDesktop) block alongside snappy-switcher, since it's only used for floating-point arithmetic in the Hyprland alt-tab script.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@home-manager/packages/default.nix` at line 19, Move the bc dependency out of
the global package list and include it only when building the Linux desktop
profile: add "bc" inside the existing lib.optionals (stdenv.isLinux &&
isDesktop) block alongside "snappy-switcher" so it is only added when
stdenv.isLinux and isDesktop are true (this keeps bc available for the Hyprland
alt-tab.sh script but avoids installing it on non-desktop or non-Linux
configurations).
config/hyprland/scripts/alt-tab.sh (2)

34-44: get_window_title and get_window_workspace are defined but never called.

These are dead code. Remove them to keep the script lean, or add a comment noting they're reserved for future use.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config/hyprland/scripts/alt-tab.sh` around lines 34 - 44, The functions
get_window_title and get_window_workspace are defined but never used; either
remove these dead functions to slim the script or keep them but add a short
comment above each (e.g., "reserved for future use" and a brief example of
intended usage) so they're no longer misleading; update the function blocks
named get_window_title and get_window_workspace accordingly and run a quick
lint/test of the script to ensure no accidental references remain.

248-259: Dim timer PID kill is racy — could kill an unrelated process.

If the previous background sleep (lines 255–258) has already exited by the time the next Alt+Tab invocation runs, the saved PID in dim_timer may have been reused by an unrelated process. The kill on line 252 would then signal that process.

A safer approach is to use a dedicated cleanup mechanism or check the process before killing:

Proposed fix
 # Clear any existing reset timer
-[[ -f "$STATE_DIR/dim_timer" ]] && kill "$(<"$STATE_DIR/dim_timer")" 2>/dev/null || true
+if [[ -f "$STATE_DIR/dim_timer" ]]; then
+    old_pid=$(<"$STATE_DIR/dim_timer")
+    # Only kill if PID is still our background sleep
+    if [[ -d "/proc/$old_pid" ]] && grep -q "sleep" "/proc/$old_pid/comm" 2>/dev/null; then
+        kill "$old_pid" 2>/dev/null || true
+    fi
+fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config/hyprland/scripts/alt-tab.sh` around lines 248 - 259, The current
dim-timer management is racy because the saved PID in "$STATE_DIR/dim_timer"
might have exited and been reused, so the kill "$( <"$STATE_DIR/dim_timer")" in
the cleanup step can signal an unrelated process; modify the cleanup to validate
the PID before killing by reading the PID from "$STATE_DIR/dim_timer", verify it
is a running process and that its command line or start time matches the
expected sleep/alt-tab helper (e.g., check /proc/<pid>/cmdline or
/proc/<pid>/comm contains "sleep" or the script name), and only then send the
kill; alternatively, create a more robust token (e.g., write the PID plus a UUID
or use a dedicated lockfile/flock) and compare that token when deciding to kill
so you never accidentally kill a different process when clearing dim_timer in
the functions that set/clear the dim timer (references: "$STATE_DIR/dim_timer",
the kill invocation, and the background sleep block).
config/hyprland/hyprland.conf (1)

442-451: Hyprexpo plugin is still loaded and configured but no longer bound to any key.

Lines 261–262 now use snappy-switcher instead of hyprexpo:expo, and the plugin is still loaded in default.nix (line 17). The gesture binding (3-finger swipe at line 448–449) still works through the plugin, so it may be intentionally kept. If hyprexpo is no longer needed, consider removing the plugin load and this config block to reduce overhead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config/hyprland/hyprland.conf` around lines 442 - 451, The hyprexpo plugin is
still loaded/configured via the plugin { hyprexpo { ... } } block but is no
longer bound to hyprexpo:expo (you now use snappy-switcher), so either remove
the plugin block and its load entry from default.nix to drop unused overhead, or
keep it if you rely on the 3-finger gesture—if keeping, explicitly document the
gesture dependency or rebind hyprexpo:expo to a key; locate the plugin block
named "hyprexpo" in hyprland.conf and the corresponding load line in default.nix
and apply one of these two changes.
flake.nix (1)

67-69: Add inputs.nixpkgs.follows to snappy-switcher input.

Most other inputs in this flake pin inputs.nixpkgs.follows = "nixpkgs" to share a single nixpkgs instance. Without it, snappy-switcher will pull its own copy of nixpkgs/nixos-unstable, bloating the flake lock and evaluation time.

Proposed fix
     snappy-switcher = {
       url = "github:OpalAayan/snappy-switcher";
+      inputs.nixpkgs.follows = "nixpkgs";
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@flake.nix` around lines 67 - 69, The snappy-switcher input is missing
inputs.nixpkgs.follows so it pulls its own nixpkgs; update the snappy-switcher
input block (the entry named "snappy-switcher") to include
inputs.nixpkgs.follows = "nixpkgs" so it follows the top-level nixpkgs and
shares a single nixpkgs instance with the other inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@config/hyprland/scripts/alt-tab.sh`:
- Around line 96-114: The script makes repeated hyprctl clients -j calls (e.g.,
inside window_exists and used by validate_window_list), causing many IPC
round-trips; fix by fetching the clients JSON once (e.g., into a variable
CLIENTS_JSON) at the start of the workflow and modify functions like
window_exists, validate_window_list, get_window_class, get_windows_by_focus,
get_visible_windows, and get_same_type_windows to accept or read that cached
JSON instead of invoking hyprctl each time, then update their jq/grep
invocations to pipe from the cached CLIENTS_JSON so a single hyprctl call
services the whole Alt+Tab operation.
- Line 132: The script uses "local filtered_windows=()" at top-level which is
invalid in bash; change the declaration of filtered_windows so it isn't using
local outside a function—either move the declaration into the function where
it's used (e.g., the alt-tab handling function) or replace "local" with a
top-level-safe declaration such as "declare -a filtered_windows=()" or
"filtered_windows=()" so the variable is properly initialized; update any
references to filtered_windows accordingly.
- Around line 80-89: The get_same_type_windows function returns windows in
hyprctl's default order; update it to sort by focus history so the
most-recent-first behavior is preserved: when building the jq pipeline in
get_same_type_windows, apply sort_by(-.focusHistoryID) before selecting by
.class and outputting .address (i.e., sort the clients by negative
.focusHistoryID then filter by $class).

---

Nitpick comments:
In `@config/hyprland/hyprland.conf`:
- Around line 442-451: The hyprexpo plugin is still loaded/configured via the
plugin { hyprexpo { ... } } block but is no longer bound to hyprexpo:expo (you
now use snappy-switcher), so either remove the plugin block and its load entry
from default.nix to drop unused overhead, or keep it if you rely on the 3-finger
gesture—if keeping, explicitly document the gesture dependency or rebind
hyprexpo:expo to a key; locate the plugin block named "hyprexpo" in
hyprland.conf and the corresponding load line in default.nix and apply one of
these two changes.

In `@config/hyprland/scripts/alt-tab.sh`:
- Around line 34-44: The functions get_window_title and get_window_workspace are
defined but never used; either remove these dead functions to slim the script or
keep them but add a short comment above each (e.g., "reserved for future use"
and a brief example of intended usage) so they're no longer misleading; update
the function blocks named get_window_title and get_window_workspace accordingly
and run a quick lint/test of the script to ensure no accidental references
remain.
- Around line 248-259: The current dim-timer management is racy because the
saved PID in "$STATE_DIR/dim_timer" might have exited and been reused, so the
kill "$( <"$STATE_DIR/dim_timer")" in the cleanup step can signal an unrelated
process; modify the cleanup to validate the PID before killing by reading the
PID from "$STATE_DIR/dim_timer", verify it is a running process and that its
command line or start time matches the expected sleep/alt-tab helper (e.g.,
check /proc/<pid>/cmdline or /proc/<pid>/comm contains "sleep" or the script
name), and only then send the kill; alternatively, create a more robust token
(e.g., write the PID plus a UUID or use a dedicated lockfile/flock) and compare
that token when deciding to kill so you never accidentally kill a different
process when clearing dim_timer in the functions that set/clear the dim timer
(references: "$STATE_DIR/dim_timer", the kill invocation, and the background
sleep block).

In `@flake.nix`:
- Around line 67-69: The snappy-switcher input is missing inputs.nixpkgs.follows
so it pulls its own nixpkgs; update the snappy-switcher input block (the entry
named "snappy-switcher") to include inputs.nixpkgs.follows = "nixpkgs" so it
follows the top-level nixpkgs and shares a single nixpkgs instance with the
other inputs.

In `@home-manager/packages/default.nix`:
- Line 19: Move the bc dependency out of the global package list and include it
only when building the Linux desktop profile: add "bc" inside the existing
lib.optionals (stdenv.isLinux && isDesktop) block alongside "snappy-switcher" so
it is only added when stdenv.isLinux and isDesktop are true (this keeps bc
available for the Hyprland alt-tab.sh script but avoids installing it on
non-desktop or non-Linux configurations).

Comment thread config/hyprland/scripts/alt-tab.sh Outdated
Comment on lines +80 to +89
# Function to get windows filtered by type (same class)
get_same_type_windows() {
local current_window="$1"
local current_class
current_class=$(get_window_class "$current_window")

# Get all windows with same class
hyprctl clients -j | jq -r --arg class "$current_class" \
'.[] | select(.class == $class) | .address'
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

get_same_type_windows doesn't sort by focus history.

When --same is used without --visible, the returned window list comes from get_same_type_windows which uses hyprctl's default order rather than sort_by(-.focusHistoryID). This breaks the "most-recent-first" behavior for same-class switching.

Proposed fix
 get_same_type_windows() {
     local current_window="$1"
     local current_class
     current_class=$(get_window_class "$current_window")
 
     # Get all windows with same class
     hyprctl clients -j | jq -r --arg class "$current_class" \
-        '.[] | select(.class == $class) | .address'
+        '[.[] | select(.class == $class)] | sort_by(-.focusHistoryID) | .[].address'
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Function to get windows filtered by type (same class)
get_same_type_windows() {
local current_window="$1"
local current_class
current_class=$(get_window_class "$current_window")
# Get all windows with same class
hyprctl clients -j | jq -r --arg class "$current_class" \
'.[] | select(.class == $class) | .address'
}
# Function to get windows filtered by type (same class)
get_same_type_windows() {
local current_window="$1"
local current_class
current_class=$(get_window_class "$current_window")
# Get all windows with same class
hyprctl clients -j | jq -r --arg class "$current_class" \
'[.[] | select(.class == $class)] | sort_by(-.focusHistoryID) | .[].address'
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config/hyprland/scripts/alt-tab.sh` around lines 80 - 89, The
get_same_type_windows function returns windows in hyprctl's default order;
update it to sort by focus history so the most-recent-first behavior is
preserved: when building the jq pipeline in get_same_type_windows, apply
sort_by(-.focusHistoryID) before selecting by .class and outputting .address
(i.e., sort the clients by negative .focusHistoryID then filter by $class).

Comment thread config/hyprland/scripts/alt-tab.sh Outdated
Comment on lines +96 to +114
# Function to check if window exists
window_exists() {
local window="$1"
[[ -n "$window" ]] && hyprctl clients -j | jq -r '.[].address' | grep -q "^${window}$"
}

# Function to validate and refresh window list
validate_window_list() {
local windows=("$@")
local valid_windows=()

for window in "${windows[@]}"; do
if window_exists "$window"; then
valid_windows+=("$window")
fi
done

printf '%s\n' "${valid_windows[@]}"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Repeated hyprctl clients -j calls — O(n) IPC round-trips in validate_window_list.

window_exists calls hyprctl clients -j per window, and validate_window_list invokes it for every window in the list. Combined with earlier calls in get_windows_by_focus, get_same_type_windows, etc., a single Alt+Tab press can issue many IPC calls. Consider fetching the client list once and passing it through.

Proposed approach — cache clients JSON once
+# Fetch clients once and reuse
+CLIENTS_JSON=$(hyprctl clients -j)
+
 # Function to check if window exists
 window_exists() {
     local window="$1"
-    [[ -n "$window" ]] && hyprctl clients -j | jq -r '.[].address' | grep -q "^${window}$"
+    [[ -n "$window" ]] && echo "$CLIENTS_JSON" | jq -r '.[].address' | grep -q "^${window}$"
 }

Apply the same pattern to get_window_class, get_windows_by_focus, get_visible_windows, get_same_type_windows, etc., piping from $CLIENTS_JSON instead of calling hyprctl clients -j each time.

Also applies to: 157-158

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config/hyprland/scripts/alt-tab.sh` around lines 96 - 114, The script makes
repeated hyprctl clients -j calls (e.g., inside window_exists and used by
validate_window_list), causing many IPC round-trips; fix by fetching the clients
JSON once (e.g., into a variable CLIENTS_JSON) at the start of the workflow and
modify functions like window_exists, validate_window_list, get_window_class,
get_windows_by_focus, get_visible_windows, and get_same_type_windows to accept
or read that cached JSON instead of invoking hyprctl each time, then update
their jq/grep invocations to pipe from the cached CLIENTS_JSON so a single
hyprctl call services the whole Alt+Tab operation.

Comment thread config/hyprland/scripts/alt-tab.sh Outdated
if [[ "$same_type" == "true" ]] && [[ -n "$current_focused" ]]; then
mapfile -t same_type_windows < <(get_same_type_windows "$current_focused")

local filtered_windows=()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

local used outside a function — bash will error here.

local is only valid inside a function body in bash. At script top-level (lines 120–155 are not inside any function), this will emit bash: local: can only be used in a function and filtered_windows won't be declared as intended.

Proposed fix
-        local filtered_windows=()
+        filtered_windows=()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
local filtered_windows=()
filtered_windows=()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config/hyprland/scripts/alt-tab.sh` at line 132, The script uses "local
filtered_windows=()" at top-level which is invalid in bash; change the
declaration of filtered_windows so it isn't using local outside a
function—either move the declaration into the function where it's used (e.g.,
the alt-tab handling function) or replace "local" with a top-level-safe
declaration such as "declare -a filtered_windows=()" or "filtered_windows=()" so
the variable is properly initialized; update any references to filtered_windows
accordingly.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds two window-switching workflows to the Hyprland setup: a focus-history-based Alt+Tab script and a visual Super+Tab switcher via snappy-switcher, wired into the Nix flake + Home Manager configuration.

Changes:

  • Add alt-tab.sh and Hyprland keybinds for Alt+Tab / Alt+Shift+Tab window cycling.
  • Add snappy-switcher flake input, package installation, daemon startup, and config file; bind Super+Tab / Super+Shift+Tab to it.
  • Add bc dependency for the Alt+Tab script’s timing logic.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
home-manager/packages/default.nix Adds bc and installs snappy-switcher on Linux desktop systems.
flake.nix Adds snappy-switcher as a flake input.
config/hyprland/snappy-switcher-config.ini Introduces snappy-switcher configuration (theme/layout/font).
config/hyprland/scripts/alt-tab.sh New focus-history-based Alt+Tab implementation with session state + dimming feedback.
config/hyprland/hyprland.conf Rebinds Super+Tab to snappy-switcher and adds Alt+Tab bindings.
config/hyprland/default.nix Starts snappy-switcher daemon and installs new config/scripts via xdg.configFile.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +261 to +262
bind = $mod, TAB, exec, snappy-switcher next
bind = $mod SHIFT, TAB, exec, snappy-switcher prev

Copilot AI Feb 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$mod+TAB is switched to snappy-switcher, but Hyprland is still loading/configuring the hyprexpo plugin (see plugin = ...libhyprexpo.so in config/hyprland/default.nix and the plugin { hyprexpo { ... } } block later in this config). If hyprexpo is no longer used anywhere, consider removing the plugin load/config to avoid unnecessary plugin overhead and keep the config consistent.

Copilot uses AI. Check for mistakes.
Comment on lines 16 to 20
extraConfig = ''
plugin = ${hyprexpoPlugin}/lib/libhyprexpo.so
exec-once = ${pkgs.hyprpanel}/bin/hyprpanel
exec-once = ${snappySwitcher}/bin/snappy-switcher --daemon
''

Copilot AI Feb 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hyprland still loads the hyprexpo plugin (plugin = ...libhyprexpo.so) even though the PR replaces the only visible hyprexpo:expo keybind with snappy-switcher. If hyprexpo is no longer invoked, removing this plugin load (and its config block in hyprland.conf) would reduce startup work and avoid carrying unused plugin configuration.

Copilot uses AI. Check for mistakes.
@shunkakinoki
shunkakinoki deleted the claude/implement-video-tutorial-1nksz branch February 26, 2026 04:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants