satisfactory positioning for wayland switchover
This commit is contained in:
@@ -1,33 +0,0 @@
|
|||||||
#!/bin/zsh
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
USAGE="$0 (next|prev|playpause)"
|
|
||||||
|
|
||||||
function ERROR() {
|
|
||||||
local ERROR_CODE=$1
|
|
||||||
local MESSAGE="$2"
|
|
||||||
|
|
||||||
echo -e "$MESSAGE\n\nUSAGE : $USAGE\n" >&2
|
|
||||||
notify-send "YouTube media controller" "$MESSAGE"
|
|
||||||
|
|
||||||
exit $ERROR_CODE
|
|
||||||
}
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
COMMAND="$1"
|
|
||||||
case $COMMAND in
|
|
||||||
next ) COMMAND_KEY='j';;
|
|
||||||
prev ) COMMAND_KEY='k';;
|
|
||||||
play ) COMMAND_KEY='space';;
|
|
||||||
* ) ERROR 1 "unsupported command '$COMMAND'" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
xdotool search --name 'YouTube Music' windowactivate \
|
|
||||||
|| ERROR 2 "YouTube Music is not running"
|
|
||||||
|
|
||||||
|
|
||||||
# Chrome / YouTube only accepts keypresses when window is active
|
|
||||||
xdotool search --name 'YouTube Music' windowactivate
|
|
||||||
sleep 0.3
|
|
||||||
xdotool key --clearmodifiers "$COMMAND_KEY"
|
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
local dotwryn = ... -- ../config.lua
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
--
|
||||||
|
-- named monitor layouts (hyprland port of the per-host xrandr layout scripts)
|
||||||
|
--
|
||||||
|
-- ---------------------------------------------------------------------
|
||||||
|
-- hosts define their monitors and layouts using the following spec:
|
||||||
|
--
|
||||||
|
-- (inside config/local/<host>/user/hypr/host.lua)
|
||||||
|
--
|
||||||
|
-- dotwryn.monitors.<name> = {
|
||||||
|
-- output = "<connector>", -- e.g. "DP-1"; a "desc:" rule can't re-enable a disabled monitor
|
||||||
|
-- modes = { "<w>x<h>@<hz>", ... }, -- the modes the monitor advertises
|
||||||
|
-- excluded_modes = { "<w>x<h>@<hz>", ... }, -- (optional) advertised, but tested and not working
|
||||||
|
-- max_refresh = <hz>, -- (optional) never pick a faster mode
|
||||||
|
-- mirrored_max_refresh = <hz>, -- (optional) cap while another monitor mirrors this one
|
||||||
|
-- }
|
||||||
|
--
|
||||||
|
-- dotwryn.monitor_layouts["<layout>"] = {
|
||||||
|
-- primary = "<name>", -- the monitor an emergency monitor mirrors
|
||||||
|
-- monitors = { -- omit monitor = monitor disabled
|
||||||
|
-- <name> = { resolution = "4k", position = "0x0", scale = 1.5 },
|
||||||
|
-- <name> = { resolution = "4k", mirror = "<name>" },
|
||||||
|
-- },
|
||||||
|
-- wallpaper = "theme", -- (optional) set-background selection
|
||||||
|
-- sfx = "gamedock", -- (optional) play-sfx selection
|
||||||
|
-- }
|
||||||
|
--
|
||||||
|
-- dotwryn.emergency_monitor = { monitor = "<name>", key = "<keysym>" } -- (optional) the key toggles <name> as a mirror of the active layout's primary
|
||||||
|
--
|
||||||
|
-- a monitor runs the fastest of its modes at the requested resolution which isn't excluded
|
||||||
|
-- or over its refresh caps; 'max' takes the highest resolution, and a resolution the monitor
|
||||||
|
-- doesn't have falls back to 'max'. a resolution chosen in the picker replaces the resolution
|
||||||
|
-- of every monitor in the layout; the scale is kept unless it doesn't divide the mode evenly (then 1)
|
||||||
|
--
|
||||||
|
-- 'default' is applied on startup and reload (see ./99-autostart.lua)
|
||||||
|
--
|
||||||
|
|
||||||
|
dotwryn.monitors = {}
|
||||||
|
dotwryn.monitor_layouts = {}
|
||||||
|
|
||||||
|
local RESOLUTIONS = {
|
||||||
|
["1080"] = "1920x1080",
|
||||||
|
["1440"] = "2560x1440",
|
||||||
|
["4k"] = "3840x2160",
|
||||||
|
["max"] = "max",
|
||||||
|
}
|
||||||
|
|
||||||
|
local MAX_RESOLUTION_ORDER = { "4k", "1440", "1080" }
|
||||||
|
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
local get_sorted_keys = function(table_)
|
||||||
|
local keys = {}
|
||||||
|
for key, _ in pairs(table_) do table.insert(keys, key) end
|
||||||
|
table.sort(keys)
|
||||||
|
return keys
|
||||||
|
end
|
||||||
|
|
||||||
|
local get_layout = function(layout_name, resolution)
|
||||||
|
local layout = dotwryn.monitor_layouts[layout_name]
|
||||||
|
|
||||||
|
assert(layout ~= nil, "unknown monitor layout '" .. tostring(layout_name) .. "' (available : " .. table.concat(get_sorted_keys(dotwryn.monitor_layouts), ", ") .. ")")
|
||||||
|
assert(resolution == nil or RESOLUTIONS[resolution] ~= nil, "unknown resolution '" .. tostring(resolution) .. "' (available : " .. table.concat(get_sorted_keys(RESOLUTIONS), ", ") .. ")")
|
||||||
|
assert(layout.primary ~= nil and (layout.monitors or {})[layout.primary] ~= nil, "monitor layout '" .. layout_name .. "' needs a primary from its monitors")
|
||||||
|
|
||||||
|
for monitor_name, monitor in pairs(layout.monitors) do
|
||||||
|
assert(dotwryn.monitors[monitor_name] ~= nil, "monitor layout '" .. layout_name .. "' uses unknown monitor '" .. monitor_name .. "'")
|
||||||
|
assert(RESOLUTIONS[monitor.resolution or "max"] ~= nil, "monitor layout '" .. layout_name .. "' uses unknown resolution '" .. tostring(monitor.resolution) .. "' for '" .. monitor_name .. "'")
|
||||||
|
assert(monitor.mirror == nil or layout.monitors[monitor.mirror] ~= nil, "monitor layout '" .. layout_name .. "' mirrors '" .. tostring(monitor.mirror) .. "', which isn't in the layout")
|
||||||
|
end
|
||||||
|
|
||||||
|
return layout
|
||||||
|
end
|
||||||
|
|
||||||
|
local get_monitor_mode = function(monitor_name, resolution, is_mirrored)
|
||||||
|
local monitor = dotwryn.monitors[monitor_name]
|
||||||
|
|
||||||
|
local max_refresh = monitor.max_refresh or math.huge
|
||||||
|
if is_mirrored and monitor.mirrored_max_refresh ~= nil then max_refresh = math.min(max_refresh, monitor.mirrored_max_refresh) end
|
||||||
|
|
||||||
|
local excluded_modes = {}
|
||||||
|
for _, mode in ipairs(monitor.excluded_modes or {}) do excluded_modes[mode] = true end
|
||||||
|
|
||||||
|
local get_fastest_mode = function(size)
|
||||||
|
local fastest_mode, fastest_refresh
|
||||||
|
for _, mode in ipairs(monitor.modes or {}) do
|
||||||
|
local mode_size, refresh = mode:match("^(%d+x%d+)@([%d.]+)$")
|
||||||
|
refresh = tonumber(refresh)
|
||||||
|
|
||||||
|
if mode_size == size and refresh <= max_refresh and not excluded_modes[mode] and (fastest_refresh == nil or refresh > fastest_refresh) then
|
||||||
|
fastest_mode, fastest_refresh = mode, refresh
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return fastest_mode
|
||||||
|
end
|
||||||
|
|
||||||
|
local mode = resolution ~= "max" and get_fastest_mode(RESOLUTIONS[resolution]) or nil
|
||||||
|
for _, max_resolution in ipairs(MAX_RESOLUTION_ORDER) do
|
||||||
|
if mode ~= nil then break end
|
||||||
|
mode = get_fastest_mode(RESOLUTIONS[max_resolution])
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(mode ~= nil, "monitor '" .. monitor_name .. "' has no usable 1080/1440/4k mode")
|
||||||
|
return mode
|
||||||
|
end
|
||||||
|
|
||||||
|
local get_resolution_scale = function(mode, scale)
|
||||||
|
local width, height = mode:match("^(%d+)x(%d+)")
|
||||||
|
if width == nil or type(scale) ~= "number" then return scale end
|
||||||
|
|
||||||
|
if (width / scale) % 1 == 0 and (height / scale) % 1 == 0 then return scale end
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local get_monitor_has_workspaces = function(output)
|
||||||
|
for _, workspace in ipairs(hl.get_workspaces()) do
|
||||||
|
if workspace.monitor ~= nil and workspace.monitor.name == output then return true end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local emergency_monitor_enabled = false
|
||||||
|
|
||||||
|
dotwryn.set_monitor_layout = function(layout_name, resolution, options)
|
||||||
|
local layout = get_layout(layout_name, resolution)
|
||||||
|
|
||||||
|
local layout_monitors = {}
|
||||||
|
for monitor_name, monitor in pairs(layout.monitors) do layout_monitors[monitor_name] = monitor end
|
||||||
|
|
||||||
|
local emergency_monitor = dotwryn.emergency_monitor
|
||||||
|
if emergency_monitor ~= nil and emergency_monitor_enabled and layout_monitors[emergency_monitor.monitor] == nil then
|
||||||
|
layout_monitors[emergency_monitor.monitor] = { resolution = "max", mirror = layout.primary }
|
||||||
|
end
|
||||||
|
|
||||||
|
local mirrored_monitor_names = {}
|
||||||
|
for _, monitor in pairs(layout_monitors) do
|
||||||
|
if monitor.mirror ~= nil then mirrored_monitor_names[monitor.mirror] = true end
|
||||||
|
end
|
||||||
|
|
||||||
|
local get_monitor_spec = function(monitor_name)
|
||||||
|
local monitor = layout_monitors[monitor_name]
|
||||||
|
local output = dotwryn.monitors[monitor_name].output
|
||||||
|
local mode = get_monitor_mode(monitor_name, resolution or monitor.resolution or "max", mirrored_monitor_names[monitor_name])
|
||||||
|
|
||||||
|
local monitor_spec = { -- every field set; hl.monitor merges into the output's previous rule
|
||||||
|
output = output,
|
||||||
|
disabled = false,
|
||||||
|
mode = mode,
|
||||||
|
position = monitor.position or "auto",
|
||||||
|
scale = get_resolution_scale(mode, monitor.scale or 1),
|
||||||
|
mirror = "",
|
||||||
|
}
|
||||||
|
|
||||||
|
if monitor.mirror ~= nil then
|
||||||
|
monitor_spec.mirror = dotwryn.monitors[monitor.mirror].output
|
||||||
|
|
||||||
|
-- after a dpms cycle hyprland can lose a mirror it still reports (the "mirror"
|
||||||
|
-- owns workspaces); re-setting the same mirror is a no-op, so clear it first
|
||||||
|
if get_monitor_has_workspaces(output) then hl.monitor({ output = output, mirror = "" }) end
|
||||||
|
end
|
||||||
|
|
||||||
|
return monitor_spec
|
||||||
|
end
|
||||||
|
|
||||||
|
-- monitors the others mirror come up first and disabled monitors go down last,
|
||||||
|
-- so there's never a mirror of a disabled monitor or a moment with no monitors
|
||||||
|
for monitor_name, monitor in pairs(layout_monitors) do
|
||||||
|
if monitor.mirror == nil then hl.monitor(get_monitor_spec(monitor_name)) end
|
||||||
|
end
|
||||||
|
for monitor_name, monitor in pairs(layout_monitors) do
|
||||||
|
if monitor.mirror ~= nil then hl.monitor(get_monitor_spec(monitor_name)) end
|
||||||
|
end
|
||||||
|
for monitor_name, monitor in pairs(dotwryn.monitors) do
|
||||||
|
if layout_monitors[monitor_name] == nil then hl.monitor({ output = monitor.output, disabled = true }) end
|
||||||
|
end
|
||||||
|
|
||||||
|
dotwryn.active_monitor_layout = { name = layout_name, resolution = resolution }
|
||||||
|
|
||||||
|
if not (options or {}).switch then return end
|
||||||
|
|
||||||
|
if layout.wallpaper ~= nil then hl.dispatch(dotwryn.scwrypts.hyprland.set_background(layout.wallpaper)) end
|
||||||
|
if layout.sfx ~= nil then hl.dispatch(dotwryn.scwrypts.media.play_sfx(layout.sfx)) end
|
||||||
|
end
|
||||||
|
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
hl.bind("SUPER + d", hl.dsp.exec_cmd("qs ipc call -- displays show"))
|
||||||
|
|
||||||
|
dotwryn_set_monitor_layout = function(layout_name, resolution)
|
||||||
|
dotwryn.set_monitor_layout(layout_name, resolution, { switch = true })
|
||||||
|
end
|
||||||
|
|
||||||
|
dotwryn_toggle_emergency_monitor = function()
|
||||||
|
assert(dotwryn.emergency_monitor ~= nil, "no emergency monitor configured for this host")
|
||||||
|
|
||||||
|
emergency_monitor_enabled = not emergency_monitor_enabled
|
||||||
|
|
||||||
|
local layout = dotwryn.active_monitor_layout
|
||||||
|
dotwryn.set_monitor_layout(layout.name, layout.resolution)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- eval can't return values; the picker (config/user/quickshell/DisplayPicker.qml) reads this IPC event instead
|
||||||
|
dotwryn_publish_monitor_layouts = function()
|
||||||
|
local get_json_string = function(value)
|
||||||
|
return '"' .. value:gsub('[%c"\\]', function(character) return string.format("\\u%04x", character:byte()) end) .. '"'
|
||||||
|
end
|
||||||
|
|
||||||
|
local get_json_strings = function(values)
|
||||||
|
local json_strings = {}
|
||||||
|
for _, value in ipairs(values) do table.insert(json_strings, get_json_string(value)) end
|
||||||
|
return "[" .. table.concat(json_strings, ",") .. "]"
|
||||||
|
end
|
||||||
|
|
||||||
|
local layout_names = get_sorted_keys(dotwryn.monitor_layouts)
|
||||||
|
for index, layout_name in ipairs(layout_names) do
|
||||||
|
if layout_name == "default" then table.insert(layout_names, 1, table.remove(layout_names, index)) break end
|
||||||
|
end
|
||||||
|
|
||||||
|
hl.dispatch(hl.dsp.event("dotwryn-monitor-layouts " .. table.concat({
|
||||||
|
'{"layouts":', get_json_strings(layout_names),
|
||||||
|
',"resolutions":', get_json_strings(get_sorted_keys(RESOLUTIONS)), "}",
|
||||||
|
})))
|
||||||
|
end
|
||||||
@@ -16,7 +16,7 @@ local SCRATCHPAD_FILTERS = {
|
|||||||
games = { "^steam$", "^dolphin%-emu$", "^Visualboyadvance%-m$", "^steam_app_" },
|
games = { "^steam$", "^dolphin%-emu$", "^Visualboyadvance%-m$", "^steam_app_" },
|
||||||
}
|
}
|
||||||
|
|
||||||
local matches_filter = function(class, filter)
|
local function matches_filter(class, filter)
|
||||||
if filter == nil then
|
if filter == nil then
|
||||||
for name, _ in pairs(SCRATCHPAD_FILTERS) do
|
for name, _ in pairs(SCRATCHPAD_FILTERS) do
|
||||||
if matches_filter(class, name) then return false end
|
if matches_filter(class, name) then return false end
|
||||||
|
|||||||
@@ -22,13 +22,16 @@ local to_pixels = function(value)
|
|||||||
return math.floor(math.min(monitor.width, monitor.height) / scale * percent / 100)
|
return math.floor(math.min(monitor.width, monitor.height) / scale * percent / 100)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local labels = {} -- reattach labels from ./80-workspaces.lua
|
||||||
|
for _, workspace in ipairs(dotwryn.workspaces) do labels[workspace.name] = workspace.label end
|
||||||
|
|
||||||
local gaps_rules = {}
|
local gaps_rules = {}
|
||||||
local set_gaps = function(workspace, inner, outer)
|
local set_gaps = function(workspace, inner, outer)
|
||||||
if gaps_rules[workspace] ~= nil then gaps_rules[workspace].rule:set_enabled(false) end
|
if gaps_rules[workspace] ~= nil then gaps_rules[workspace].rule:set_enabled(false) end
|
||||||
gaps_rules[workspace] = {
|
gaps_rules[workspace] = {
|
||||||
inner = inner,
|
inner = inner,
|
||||||
outer = outer,
|
outer = outer,
|
||||||
rule = hl.workspace_rule({ workspace = workspace, gaps_in = inner, gaps_out = outer }),
|
rule = hl.workspace_rule({ workspace = workspace, gaps_in = inner, gaps_out = outer, default_name = labels[workspace] }),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -23,3 +23,9 @@ hl.window_rule({
|
|||||||
match = { class = "^steam_app_.*$" },
|
match = { class = "^steam_app_.*$" },
|
||||||
workspace = "100", -- 🎮
|
workspace = "100", -- 🎮
|
||||||
})
|
})
|
||||||
|
|
||||||
|
hl.window_rule({
|
||||||
|
name = "idle-inhibit-fullscreen",
|
||||||
|
match = { class = ".*" },
|
||||||
|
idle_inhibit = "fullscreen",
|
||||||
|
})
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ local dofile_if_exists = function(path)
|
|||||||
local file = io.open(path, "r")
|
local file = io.open(path, "r")
|
||||||
if file == nil then return end
|
if file == nil then return end
|
||||||
file:close()
|
file:close()
|
||||||
dofile(path)
|
assert(loadfile(path))(dotwryn)
|
||||||
end
|
end
|
||||||
|
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
|
|||||||
@@ -10,3 +10,11 @@ hl.on("hyprland.start", function()
|
|||||||
hl.exec_cmd("/usr/lib/xfce-polkit/xfce-polkit")
|
hl.exec_cmd("/usr/lib/xfce-polkit/xfce-polkit")
|
||||||
hl.exec_cmd("command -v apps && apps") -- host-local background apps ($DOTWRYN/config/local/<hostname>/bin/apps)
|
hl.exec_cmd("command -v apps && apps") -- host-local background apps ($DOTWRYN/config/local/<hostname>/bin/apps)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
if dotwryn.monitor_layouts["default"] ~= nil then
|
||||||
|
dotwryn.set_monitor_layout("default")
|
||||||
|
end
|
||||||
|
|
||||||
|
if dotwryn.emergency_monitor ~= nil then
|
||||||
|
hl.bind(dotwryn.emergency_monitor.key, dotwryn_toggle_emergency_monitor)
|
||||||
|
end
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ local hyprland = {
|
|||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local media = {
|
||||||
|
play_sfx = function(...)
|
||||||
|
return hl.dsp.exec_cmd(scwrypts_desktop({ 'play-sfx' }, { ... }))
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
local wayland = {
|
local wayland = {
|
||||||
sync_gtk_settings = function(...)
|
sync_gtk_settings = function(...)
|
||||||
return hl.dsp.exec_cmd(scwrypts({ 'sync-gtk-settings' }, { ... }))
|
return hl.dsp.exec_cmd(scwrypts({ 'sync-gtk-settings' }, { ... }))
|
||||||
@@ -73,6 +79,7 @@ return {
|
|||||||
scwrypts_desktop = scwrypts_desktop,
|
scwrypts_desktop = scwrypts_desktop,
|
||||||
|
|
||||||
hyprland = hyprland,
|
hyprland = hyprland,
|
||||||
|
media = media,
|
||||||
wayland = wayland,
|
wayland = wayland,
|
||||||
tmux = tmux,
|
tmux = tmux,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
local dotwryn = ... -- $DOTWRYN/config/desktop/hyprland/config.lua
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
|
||||||
|
dotwryn.monitors.desk = {
|
||||||
|
output = "DP-2",
|
||||||
|
modes = {
|
||||||
|
"3840x2160@239.99", "3840x2160@180.00", "3840x2160@120.00", "3840x2160@119.88", "3840x2160@60.00", "3840x2160@59.94",
|
||||||
|
"2560x1440@239.85", "2560x1440@120.00", "2560x1440@59.95",
|
||||||
|
"1920x1080@239.88", "1920x1080@119.88", "1920x1080@60.00", "1920x1080@59.94",
|
||||||
|
},
|
||||||
|
excluded_modes = { "1920x1080@239.88" },
|
||||||
|
mirrored_max_refresh = 120,
|
||||||
|
}
|
||||||
|
|
||||||
|
dotwryn.monitors.zapdos = {
|
||||||
|
output = "DP-1",
|
||||||
|
modes = {
|
||||||
|
"3840x2160@60.00", "3840x2160@59.94",
|
||||||
|
"2560x1440@143.97", "2560x1440@120.00",
|
||||||
|
"1920x1080@143.98", "1920x1080@119.88", "1920x1080@100.00", "1920x1080@60.00", "1920x1080@59.94",
|
||||||
|
},
|
||||||
|
excluded_modes = { "2560x1440@143.97", "1920x1080@119.88" },
|
||||||
|
}
|
||||||
|
|
||||||
|
dotwryn.monitors.splitter = {
|
||||||
|
output = "HDMI-A-1",
|
||||||
|
modes = {
|
||||||
|
"3840x2160@119.88", "3840x2160@100.00", "3840x2160@60.00", "3840x2160@59.94",
|
||||||
|
"2560x1440@144.01",
|
||||||
|
"1920x1080@119.98", "1920x1080@60.00", "1920x1080@59.94",
|
||||||
|
},
|
||||||
|
max_refresh = 60,
|
||||||
|
}
|
||||||
|
|
||||||
|
dotwryn.monitors.server_rack = {
|
||||||
|
output = "DP-3",
|
||||||
|
modes = {
|
||||||
|
"1920x1080@144.00", "1920x1080@120.00", "1920x1080@119.88", "1920x1080@100.00", "1920x1080@60.00", "1920x1080@59.94",
|
||||||
|
},
|
||||||
|
max_refresh = 60,
|
||||||
|
}
|
||||||
|
|
||||||
|
dotwryn.emergency_monitor = { monitor = "server_rack", key = "XF86Calculator" }
|
||||||
|
|
||||||
|
dotwryn.monitor_layouts["default"] = {
|
||||||
|
primary = "desk",
|
||||||
|
monitors = {
|
||||||
|
desk = { resolution = "4k", position = "0x0", scale = 1.5 },
|
||||||
|
zapdos = { resolution = "4k", mirror = "desk" },
|
||||||
|
splitter = { resolution = "4k", mirror = "desk" },
|
||||||
|
},
|
||||||
|
wallpaper = "theme",
|
||||||
|
sfx = "login",
|
||||||
|
}
|
||||||
|
|
||||||
|
dotwryn.monitor_layouts["broadcast"] = {
|
||||||
|
primary = "desk",
|
||||||
|
monitors = {
|
||||||
|
desk = { resolution = "4k", position = "2560x0", scale = 1.5 },
|
||||||
|
zapdos = { resolution = "4k", position = "0x0", scale = 1.5 },
|
||||||
|
splitter = { resolution = "4k", mirror = "zapdos" },
|
||||||
|
},
|
||||||
|
wallpaper = "theme",
|
||||||
|
sfx = "login",
|
||||||
|
}
|
||||||
|
|
||||||
|
dotwryn.monitor_layouts["beeg-game"] = {
|
||||||
|
primary = "zapdos",
|
||||||
|
monitors = {
|
||||||
|
zapdos = { resolution = "max", position = "0x0", scale = 1.5 },
|
||||||
|
},
|
||||||
|
wallpaper = "theme",
|
||||||
|
sfx = "gamedock",
|
||||||
|
}
|
||||||
|
|
||||||
|
dotwryn.monitor_layouts["smol-game"] = {
|
||||||
|
primary = "desk",
|
||||||
|
monitors = {
|
||||||
|
desk = { resolution = "max", position = "0x0", scale = 1.5 },
|
||||||
|
},
|
||||||
|
wallpaper = "theme",
|
||||||
|
sfx = "gamedock",
|
||||||
|
}
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
hl.on("hyprland.start", function()
|
||||||
|
hl.exec_cmd("hypridle") -- ./hypridle.conf : power the desk monitor off after an hour idle
|
||||||
|
|
||||||
|
-- office mouse
|
||||||
|
hl.exec_cmd("razer-cli --device 632531H25800834 --brightness 80 --effect breath_dual --color 006942 420069 --dpi 6400 --poll 1000")
|
||||||
|
|
||||||
|
-- zapdos keyboard
|
||||||
|
hl.exec_cmd("razer-cli --device IO2524F99900428 --brightness 5 --effect multicolor")
|
||||||
|
end)
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
// monitor layout picker for $DOTWRYN/config/desktop/hyprland/config.lua.d/02-monitor-layouts.lua;
|
||||||
|
// shown with 'qs ipc call -- displays show' (without '--', 'show' parses as 'qs ipc show')
|
||||||
|
Scope {
|
||||||
|
id: picker
|
||||||
|
|
||||||
|
property bool shown: false
|
||||||
|
property var screen: null
|
||||||
|
|
||||||
|
property var layouts: []
|
||||||
|
property var resolutions: []
|
||||||
|
|
||||||
|
property int selectedLayout: 0
|
||||||
|
property int selectedResolution: -1 // -1 : the modes configured in the layout
|
||||||
|
|
||||||
|
function show(): void {
|
||||||
|
selectedLayout = 0
|
||||||
|
selectedResolution = -1
|
||||||
|
screen = Quickshell.screens.find(screen => screen.name === Hyprland.focusedMonitor?.name) ?? null
|
||||||
|
Quickshell.execDetached(["hyprctl", "eval", "dotwryn_publish_monitor_layouts()"])
|
||||||
|
shown = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveLayout(offset: int): void {
|
||||||
|
if (layouts.length > 0) selectedLayout = (selectedLayout + offset + layouts.length) % layouts.length
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveResolution(offset: int): void {
|
||||||
|
const count = resolutions.length + 1
|
||||||
|
selectedResolution = ((selectedResolution + 1 + offset + count) % count) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// hyprland 0.56.2 segfaults if a focused layer surface is destroyed by a
|
||||||
|
// monitor change, so the picker must be gone before the layout is applied
|
||||||
|
function applyLayout(): void {
|
||||||
|
if (selectedLayout >= layouts.length) return
|
||||||
|
|
||||||
|
const resolution = selectedResolution < 0 ? "nil" : JSON.stringify(resolutions[selectedResolution])
|
||||||
|
evaluation.command = ["hyprctl", "eval", `dotwryn_set_monitor_layout(${JSON.stringify(layouts[selectedLayout])}, ${resolution})`]
|
||||||
|
|
||||||
|
shown = false
|
||||||
|
applyAfterHidden.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: applyAfterHidden
|
||||||
|
interval: 250
|
||||||
|
onTriggered: evaluation.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: evaluation
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
const result = text.trim()
|
||||||
|
if (result !== "ok") Quickshell.execDetached(["notify-send", "displays", result.replace(/^.*:\d+: /, "")])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IpcHandler {
|
||||||
|
target: "displays"
|
||||||
|
|
||||||
|
function show(): void { picker.show() }
|
||||||
|
function hide(): void { picker.shown = false }
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Hyprland
|
||||||
|
|
||||||
|
function onRawEvent(event) {
|
||||||
|
const prefix = "dotwryn-monitor-layouts "
|
||||||
|
if (event.name !== "custom" || !event.data.startsWith(prefix)) return
|
||||||
|
|
||||||
|
const published = JSON.parse(event.data.slice(prefix.length))
|
||||||
|
picker.layouts = published.layouts
|
||||||
|
picker.resolutions = published.resolutions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component PickerText: Text {
|
||||||
|
color: Colors.foreground
|
||||||
|
font.family: Colors.font
|
||||||
|
font.pointSize: Colors.fontSize
|
||||||
|
}
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
visible: picker.shown
|
||||||
|
screen: picker.screen
|
||||||
|
|
||||||
|
WlrLayershell.layer: WlrLayer.Overlay
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||||
|
exclusionMode: ExclusionMode.Ignore
|
||||||
|
|
||||||
|
implicitWidth: content.implicitWidth + 24
|
||||||
|
implicitHeight: content.implicitHeight + 24
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Colors.background
|
||||||
|
border.width: 1
|
||||||
|
border.color: Colors.primary
|
||||||
|
|
||||||
|
focus: true
|
||||||
|
Keys.onPressed: event => {
|
||||||
|
switch (event.key) {
|
||||||
|
case Qt.Key_J: case Qt.Key_Down: picker.moveLayout(1); break
|
||||||
|
case Qt.Key_K: case Qt.Key_Up: picker.moveLayout(-1); break
|
||||||
|
case Qt.Key_L: case Qt.Key_Right: picker.moveResolution(1); break
|
||||||
|
case Qt.Key_H: case Qt.Key_Left: picker.moveResolution(-1); break
|
||||||
|
|
||||||
|
case Qt.Key_Return: case Qt.Key_Enter: picker.applyLayout(); break
|
||||||
|
|
||||||
|
case Qt.Key_Escape: case Qt.Key_Q: picker.shown = false; break
|
||||||
|
default: return
|
||||||
|
}
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: content
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
PickerText {
|
||||||
|
text: "monitor layout"
|
||||||
|
font.bold: true
|
||||||
|
color: Colors.secondary
|
||||||
|
}
|
||||||
|
|
||||||
|
PickerText {
|
||||||
|
visible: picker.layouts.length === 0
|
||||||
|
text: "no monitor layouts for this host"
|
||||||
|
color: Colors.disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: picker.layouts
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: layoutRow
|
||||||
|
|
||||||
|
required property int index
|
||||||
|
required property string modelData
|
||||||
|
|
||||||
|
readonly property bool selected: picker.selectedLayout === index
|
||||||
|
|
||||||
|
implicitWidth: Math.max(layoutLabel.implicitWidth + 24, 280)
|
||||||
|
implicitHeight: layoutLabel.implicitHeight + 8
|
||||||
|
color: selected ? Colors.primary : "transparent"
|
||||||
|
|
||||||
|
PickerText {
|
||||||
|
id: layoutLabel
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 12
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: layoutRow.modelData
|
||||||
|
font.bold: layoutRow.selected
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onEntered: picker.selectedLayout = layoutRow.index
|
||||||
|
onClicked: picker.applyLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
visible: picker.layouts.length > 0
|
||||||
|
spacing: 8
|
||||||
|
topPadding: 8
|
||||||
|
|
||||||
|
PickerText {
|
||||||
|
text: "resolution"
|
||||||
|
color: Colors.secondary
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: ["configured"].concat(picker.resolutions)
|
||||||
|
|
||||||
|
PickerText {
|
||||||
|
required property int index
|
||||||
|
required property string modelData
|
||||||
|
|
||||||
|
readonly property bool selected: picker.selectedResolution === index - 1
|
||||||
|
|
||||||
|
text: selected ? `[${modelData}]` : ` ${modelData} `
|
||||||
|
color: selected ? Colors.foreground : Colors.disabled
|
||||||
|
font.bold: selected
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: picker.selectedResolution = parent.index - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,4 +19,6 @@ ShellRoot {
|
|||||||
VolumeOsd {}
|
VolumeOsd {}
|
||||||
|
|
||||||
LogoutPrompt {}
|
LogoutPrompt {}
|
||||||
|
|
||||||
|
DisplayPicker {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ set -sg repeat-time 0
|
|||||||
set-option -g default-shell /bin/zsh
|
set-option -g default-shell /bin/zsh
|
||||||
|
|
||||||
set-option -ga update-environment HYPRLAND_INSTANCE_SIGNATURE
|
set-option -ga update-environment HYPRLAND_INSTANCE_SIGNATURE
|
||||||
|
set-option -ga update-environment WAYLAND_DISPLAY # wl-clipboard through waypipe on reattach
|
||||||
|
|
||||||
# quick reload
|
# quick reload
|
||||||
bind-key r source-file ~/.config/tmux/tmux.conf\; display "TMUX CONFIG RELOADED"
|
bind-key r source-file ~/.config/tmux/tmux.conf\; display "TMUX CONFIG RELOADED"
|
||||||
@@ -100,6 +101,6 @@ bind-key -n C-M-r send-keys M-r
|
|||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
|
|
||||||
bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel 'xclip'
|
bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel 'if [ -n "$WAYLAND_DISPLAY" ]; then wl-copy; else xclip -selection clipboard; fi'
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
|
|||||||
+60
-8
@@ -112,15 +112,16 @@ MAIN() {
|
|||||||
".default.command" \
|
".default.command" \
|
||||||
)
|
)
|
||||||
|
|
||||||
|
local TMUX_SESSION_NAME=$(
|
||||||
|
remote.config.query-connection-with-fallback \
|
||||||
|
".sessions.$REMOTE_NAME.tmux.session" \
|
||||||
|
".default.tmux.session" \
|
||||||
|
"wryn" \
|
||||||
|
)
|
||||||
|
|
||||||
[ $REMOTE_COMMAND ] || {
|
[ $REMOTE_COMMAND ] || {
|
||||||
case $CONNECTION_TYPE in
|
case $CONNECTION_TYPE in
|
||||||
( tmux )
|
( tmux )
|
||||||
local TMUX_SESSION_NAME=$(
|
|
||||||
remote.config.query-connection-with-fallback \
|
|
||||||
".sessions.$REMOTE_NAME.tmux.session" \
|
|
||||||
".default.tmux.session" \
|
|
||||||
"wryn" \
|
|
||||||
)
|
|
||||||
local TMUX_ARGS=()
|
local TMUX_ARGS=()
|
||||||
|
|
||||||
locale charmap 2>/dev/null | grep -qi 'UTF-8' \
|
locale charmap 2>/dev/null | grep -qi 'UTF-8' \
|
||||||
@@ -189,6 +190,57 @@ MAIN() {
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
echo.debug "ssh ${SSH_ARGS[@]} $CONNECTION_STRING \"$REMOTE_COMMAND\""
|
##########################################
|
||||||
ssh ${SSH_ARGS[@]} $CONNECTION_STRING "$REMOTE_COMMAND"
|
|
||||||
|
local host_display_server=none
|
||||||
|
|
||||||
|
[[ "${DISPLAY}" ]] && host_display_server=x11
|
||||||
|
[[ "${WAYLAND_DISPLAY}" ]] && host_display_server=wayland
|
||||||
|
|
||||||
|
local ssh_command=(ssh)
|
||||||
|
|
||||||
|
case "${host_display_server}" in
|
||||||
|
( none | x11 ) ;;
|
||||||
|
( wayland )
|
||||||
|
local wayland_errors=0
|
||||||
|
local waypipe_display="wayland-waypipe-${TMUX_SESSION_NAME}"
|
||||||
|
|
||||||
|
command -v waypipe &>/dev/null || {
|
||||||
|
echo.warning "waypipe unavailable on host '$(hostnamectl --static)'"
|
||||||
|
((wayland_errors+=1))
|
||||||
|
}
|
||||||
|
|
||||||
|
# waypipe only unlinks its display socket on a clean exit; a dropped
|
||||||
|
# connection leaves it behind and every later bind fails EADDRINUSE
|
||||||
|
local remote_waypipe_check="
|
||||||
|
command -v waypipe || exit 1
|
||||||
|
socket=\"\${XDG_RUNTIME_DIR}/${waypipe_display}\"
|
||||||
|
[ -S \"\${socket}\" ] || exit 0
|
||||||
|
ss -xlH | grep -qF \" \${socket} \" || rm -- \"\${socket}\"
|
||||||
|
exit 0
|
||||||
|
"
|
||||||
|
|
||||||
|
ssh "${(@)SSH_ARGS:#-t}" "${CONNECTION_STRING}" "${remote_waypipe_check}" &>/dev/null || {
|
||||||
|
echo.warning "waypipe unavailable on host '${REMOTE_NAME}'"
|
||||||
|
((wayland_errors+=1))
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${wayland_errors}" in
|
||||||
|
( 0 ) ssh_command=(waypipe --display "${waypipe_display}" ssh) ;;
|
||||||
|
( * )
|
||||||
|
echo.reminder '
|
||||||
|
clipboard WILL NOT REACH the driver on this connection
|
||||||
|
|
||||||
|
fix by installing the following packages on each host:
|
||||||
|
- wl-clipboard
|
||||||
|
- waypipe
|
||||||
|
'
|
||||||
|
[[ -t 0 ]] && read -t 5 -k 1 -s
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo.debug "${ssh_command[@]} ${SSH_ARGS[@]} $CONNECTION_STRING \"$REMOTE_COMMAND\""
|
||||||
|
"${ssh_command[@]}" ${SSH_ARGS[@]} $CONNECTION_STRING "$REMOTE_COMMAND"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ dependency rg --yay ripgrep --dnf ripgrep --apt-get ripgre
|
|||||||
dependency sed --brew gsed
|
dependency sed --brew gsed
|
||||||
dependency tmux
|
dependency tmux
|
||||||
dependency vim --dnf vim-enhanced
|
dependency vim --dnf vim-enhanced
|
||||||
|
dependency waypipe
|
||||||
|
dependency wl-copy --yay wl-clipboard --dnf wl-clipboard --apt-get wl-clipboard --brew wl-clipboard
|
||||||
dependency xauth --yay xorg-xauth --dnf xorg-x11-xauth --apt-get xauth
|
dependency xauth --yay xorg-xauth --dnf xorg-x11-xauth --apt-get xauth
|
||||||
dependency yamllint
|
dependency yamllint
|
||||||
dependency yq --yay go-yq --not-on apt-get
|
dependency yq --yay go-yq --not-on apt-get
|
||||||
@@ -122,11 +124,11 @@ dependency_args=("${INSTALLED_BY__extended[@]}" --only-on yay)
|
|||||||
|
|
||||||
# hyprland stack (not available through default dnf/apt-get repositories at the moment)
|
# hyprland stack (not available through default dnf/apt-get repositories at the moment)
|
||||||
dependency grim
|
dependency grim
|
||||||
|
dependency hypridle
|
||||||
dependency hyprlock
|
dependency hyprlock
|
||||||
dependency hyprpaper
|
dependency hyprpaper
|
||||||
dependency qs --yay quickshell
|
dependency qs --yay quickshell
|
||||||
dependency start-hyprland --yay hyprland
|
dependency start-hyprland --yay hyprland
|
||||||
dependency wl-copy --yay wl-clipboard
|
|
||||||
dependency xdg-desktop-portal-hyprland --no-executable
|
dependency xdg-desktop-portal-hyprland --no-executable
|
||||||
|
|
||||||
##########################################
|
##########################################
|
||||||
@@ -135,6 +137,7 @@ dependency xdg-desktop-portal-hyprland --no-executable
|
|||||||
dependency_args=("${INSTALLED_BY__all[@]}" --only-on yay)
|
dependency_args=("${INSTALLED_BY__all[@]}" --only-on yay)
|
||||||
|
|
||||||
dependency arch-install-scripts --no-executable
|
dependency arch-install-scripts --no-executable
|
||||||
|
dependency dotool
|
||||||
dependency python-rtmidi --no-executable
|
dependency python-rtmidi --no-executable
|
||||||
|
|
||||||
dependency scwrypts
|
dependency scwrypts
|
||||||
|
|||||||
+7
-5
@@ -43,11 +43,13 @@ nnoremap <Leader>d :let @d = system("date '+%A, %B %-d, %Y'")<CR>o<C-r>d<BS> <e
|
|||||||
" \m to set buffer to modifiable
|
" \m to set buffer to modifiable
|
||||||
nnoremap <Leader>m :set modifiable<CR>
|
nnoremap <Leader>m :set modifiable<CR>
|
||||||
|
|
||||||
" (c)opy / (p)aste from xclip
|
" (c)opy / (p)aste from the system clipboard
|
||||||
" TODO: learn how to compile vim with x11 compatibility and delete
|
" wayland (local or through waypipe) uses wl-clipboard; otherwise falls back to xclip
|
||||||
vnoremap <Leader>c :w !xclip<CR><CR>
|
let g:clipboard_copy = empty($WAYLAND_DISPLAY) ? 'xclip -selection clipboard' : 'wl-copy'
|
||||||
nnoremap <Leader>v o<esc>!!xclip -o<CR>
|
let g:clipboard_paste = empty($WAYLAND_DISPLAY) ? 'xclip -selection clipboard -o' : 'wl-paste -n'
|
||||||
nnoremap <Leader>sc :'<,'>w !xclip<CR><CR>
|
execute 'vnoremap <Leader>c :w !' . g:clipboard_copy . '<CR><CR>'
|
||||||
|
execute 'nnoremap <Leader>v o<esc>!!' . g:clipboard_paste . '<CR>'
|
||||||
|
execute "nnoremap <Leader>sc :'<,'>w !" . g:clipboard_copy . '<CR><CR>'
|
||||||
|
|
||||||
" enable/disable true color
|
" enable/disable true color
|
||||||
nnoremap <f12> :set invtermguicolors<CR>
|
nnoremap <f12> :set invtermguicolors<CR>
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ alias pd='pushd'
|
|||||||
alias qd='popd'
|
alias qd='popd'
|
||||||
alias lsd='dirs'
|
alias lsd='dirs'
|
||||||
|
|
||||||
alias pdo='pwd | xclip' # [p]aste [d]irectory [o]n clipboard
|
alias pdo='pwd | { [[ "${WAYLAND_DISPLAY}" ]] && wl-copy || xclip -selection clipboard; }' # [p]aste [d]irectory [o]n clipboard
|
||||||
alias cdo='cd $(xclip -o)' # [c]hange to [d]irectory [o]n clipboard
|
alias cdo='cd "$([[ "${WAYLAND_DISPLAY}" ]] && wl-paste -n || xclip -selection clipboard -o)"' # [c]hange to [d]irectory [o]n clipboard
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
### default command override ########################################
|
### default command override ########################################
|
||||||
|
|||||||
Reference in New Issue
Block a user