From 890bdb0925dac75832bb3383abf1be0281d74ec8 Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Sat, 16 May 2026 00:24:51 -0700 Subject: [PATCH] hyprland: keep file chooser dialogs focused --- dotfiles/config/hypr/hyprland/events.lua | 4 + dotfiles/config/hypr/hyprland/scratchpads.lua | 1 + dotfiles/config/hypr/hyprland/settings.lua | 80 ++++++++++++++++++- 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/dotfiles/config/hypr/hyprland/events.lua b/dotfiles/config/hypr/hyprland/events.lua index 21481cdd..cea88680 100644 --- a/dotfiles/config/hypr/hyprland/events.lua +++ b/dotfiles/config/hypr/hyprland/events.lua @@ -87,6 +87,10 @@ function M.setup(ctx) hl.on("window.open", adopt_matching_scratchpad_window) hl.on("window.class", adopt_matching_scratchpad_window) hl.on("window.title", adopt_matching_scratchpad_window) + + hl.on("window.open", raise_file_chooser_window_later) + hl.on("window.class", raise_file_chooser_window_later) + hl.on("window.title", raise_file_chooser_window_later) end return M diff --git a/dotfiles/config/hypr/hyprland/scratchpads.lua b/dotfiles/config/hypr/hyprland/scratchpads.lua index f916e4c3..ac0ce767 100644 --- a/dotfiles/config/hypr/hyprland/scratchpads.lua +++ b/dotfiles/config/hypr/hyprland/scratchpads.lua @@ -77,6 +77,7 @@ function M.setup(ctx) local function scratchpad_window_matches(window, def) return window + and not (type(is_file_chooser_window) == "function" and is_file_chooser_window(window)) and lower_contains_any(window.class, def.classes or def.class) and lower_contains(window.title, def.title) end diff --git a/dotfiles/config/hypr/hyprland/settings.lua b/dotfiles/config/hypr/hyprland/settings.lua index dea327d8..3ca602fa 100644 --- a/dotfiles/config/hypr/hyprland/settings.lua +++ b/dotfiles/config/hypr/hyprland/settings.lua @@ -2,10 +2,77 @@ local M = {} function M.setup(ctx) local _ENV = ctx + local file_chooser_title_rule = "^(Open File|Open Files|Save File|Save Files|Save As|Select File|Select Files|Choose File|Choose Files|File Upload|Upload File|Upload Files|Select Folder|Choose Folder|Open Folder|Save Folder)$" + local function plugin_path(env_name, default) return os.getenv(env_name) or default end + local function lower_string(value) + return string.lower(tostring(value or "")) + end + + local function title_indicates_file_chooser(title) + title = lower_string(title) + if title == "" then + return false + end + + for _, exact in ipairs({ + "open file", + "open files", + "save file", + "save files", + "save as", + "select file", + "select files", + "choose file", + "choose files", + "file upload", + "upload file", + "upload files", + "select folder", + "choose folder", + "open folder", + "save folder", + }) do + if title == exact then + return true + end + end + + return title:find("file chooser", 1, true) ~= nil + or title:find("file picker", 1, true) ~= nil + end + + local function is_file_chooser_window(window) + return window + and (title_indicates_file_chooser(window.title) or title_indicates_file_chooser(window.initial_title)) + end + + local function raise_file_chooser_window(window) + if verify_config or not is_file_chooser_window(window) then + return + end + + local selector = window_selector(window) + if not selector then + return + end + + dispatch(hl.dsp.window.float({ action = "enable", window = selector })) + dispatch(hl.dsp.window.center({ window = selector })) + dispatch(hl.dsp.focus({ window = selector })) + dispatch(hl.dsp.window.bring_to_top({ window = selector })) + end + + local function raise_file_chooser_window_later(window, timeout) + hl.timer(function() + local refreshed = window and window.address and hl.get_window(window_selector(window)) or window + raise_file_chooser_window(refreshed) + end, { timeout = timeout or 50, type = "oneshot" }) + end + if enable_nstack and not verify_config then hl.plugin.load("/run/current-system/sw/lib/libhyprNStack.so") end @@ -255,8 +322,14 @@ function M.setup(ctx) hl.window_rule({ match = { class = "^()$", title = "^()$" }, float = true }) hl.window_rule({ match = { title = "^(Picture-in-Picture)$" }, float = true }) - hl.window_rule({ match = { title = "^(Open File)$" }, float = true }) - hl.window_rule({ match = { title = "^(Save File)$" }, float = true }) + hl.window_rule({ + name = "file-chooser-dialogs", + match = { title = file_chooser_title_rule }, + float = true, + center = true, + focus_on_activate = true, + stay_focused = true, + }) hl.window_rule({ match = { title = "^(Confirm)$" }, float = true }) for index, match in ipairs({ @@ -317,6 +390,9 @@ function M.setup(ctx) ctx.apply_hyprglass_config = apply_hyprglass_config ctx.apply_hyprwobbly_config = apply_hyprwobbly_config ctx.apply_visual_performance_mode = apply_visual_performance_mode + ctx.is_file_chooser_window = is_file_chooser_window + ctx.raise_file_chooser_window = raise_file_chooser_window + ctx.raise_file_chooser_window_later = raise_file_chooser_window_later ctx.toggle_visual_performance_mode = toggle_visual_performance_mode end