@derxen I had a little play around in my config, and might have something that solves you’re problem?
Its pretty verbose, so i would suggest you split it out from your main config as follows:
- create
~/.config/hypr/utils.lua
- populate it as follows:
local M = {}
---Checks if a table contains a specific value.
---@param tbl table the table to check
---@param value any the value to look for
---@return boolean result true if the value is found in the table, false otherwise
function M.tbl_contains(tbl, value)
for _, v in pairs(tbl) do
if v == value then
return true
end
end
return false
end
--- Filters a table based on a predicate function
--- @param tbl table The table to filter
--- @param predicate fun(value: any): boolean The predicate function to use for filtering the table
--- @return table result The filtered table
function M.filter_table(tbl, predicate)
local filtered = {}
for _, v in ipairs(tbl) do
if predicate(v) then
table.insert(filtered, v)
end
end
return filtered
end
--- Queries a hyprland window / workspace object based on a selector
--- @param obj HL.Window|HL.Workspace The hyprland window or workspace object to query
--- @param selector HL.WindowSelector|HL.WorkspaceSelector The selector string or table to use for querying the object
--- @return boolean result True if the object matches the selector, false otherwise
function M.hl_match_query(obj, selector)
if type(selector) == 'number' then
return obj.pid == selector or obj.stable_id == selector or obj.id == selector
end
if type(selector) == 'string' then
if obj.name == selector then
return true
end
local key, value = selector:match('^(.-):(.+)$')
if key and value then
return obj[key] == value
end
end
if type(selector) == 'table' then
for k, v in pairs(selector) do
if obj[k] ~= v then
return false
end
end
return true
end
if type(selector) == 'userdata' then
return obj == selector
end
return false
end
--- @class DemoteOptions
--- @field launched_from? HL.WindowSelector The [window selector](https://wiki.hypr.land/Configuring/Basics/Dispatchers/#window) of the window that is the file manager launching the files we want to demote (if nil, then all windows capable of launching a file will be affected)
--- @field exclusions? HL.WindowSelector[] A list of [window selectors](https://wiki.hypr.land/Configuring/Basics/Dispatchers/#window) that should not be demoted from the special workspace
--- @field demote_from? HL.WorkspaceSelector A [workspace selector](https://wiki.hypr.land/Configuring/Basics/Dispatchers/#workspace-selectors) that should be monitored for windows to demote from the special workspace
--- Demotes windows from special workspaces to the active workspace if they are not excepted by the criteria in the options tables exlusions property
--- @param window HL.Window The [window](https://wiki.hypr.land/Configuring/Basics/Dispatchers/#window) to demote from the special workspace
--- @param opts? DemoteOptions The options to use for demoting the window from the special workspace
--- @return nil
function M.demote_from_special_workspace(window, opts)
if window == nil then
return
end
opts = opts or {}
opts.exclusions = opts.exclusions or {}
opts.demote_from = opts.demote_from and hl.get_workspace(opts.demote_from) or hl.get_active_special_workspace()
if not opts.demote_from then
return
end
local active_window = hl.get_active_window()
local launched_from_selector = opts.launched_from
local demote_from_windows = hl.get_workspace_windows(opts.demote_from)
demote_from_windows = M.filter_table(demote_from_windows, function(w)
return launched_from_selector == nil or M.hl_match_query(w, launched_from_selector)
end)
if #demote_from_windows == 0 then
return
end
if not M.tbl_contains(demote_from_windows, active_window) then
return
end
for _, exclusion in ipairs(opts.exclusions) do
if M.hl_match_query(window, exclusion) then
return
end
end
if window.workspace == opts.demote_from then
hl.dispatch(hl.dsp.window.move({ window = window, workspace = hl.get_active_workspace() }))
end
end
return M
- update your window rule to look like this:
local demote_opts = {}
-- some example ways of targeting windows that should be excluded from
-- being demoted to the workspace below the special one
demote_opts.exclusions = {
'class:thunar',
'class:nemo',
{ class = 'thunar', intial_title = 'Nextcloud - Thunar' },
{ initial_title = nil, class = nil },
hl.get_window('some_window_selector'),
}
-- if launched_from is ommitted from the opts table, then any app capable of
-- launching a file will demote the non-excluded windows to the workspace below
-- the special one
demote_opts.launched_from = 'class:thunar'
-- demote_from should be a selector that matches your special workspace
demote_opts.demote_from = 'name:special:files'
hl.on('window.open_early', function(w)
require('utils').demote_from_special_workspace(w, demote_opts)
end)
a more simplified option without all the commenting would be:
local demote_opts = {
exclusions = { 'class:thunar' },
launched_from = 'class:thunar',
demote_from = 'name:special:files',
}
hl.on('window.open_early', function(w)
require('utils').demote_from_special_workspace(w, demote_opts)
end)