config/lua: init lua config manager, use lua if available (#13817)

Adds a new lua config manager, which will automatically be used if there is a lua config present.
This commit is contained in:
Vaxry
2026-04-26 15:16:36 +01:00
committed by GitHub
parent 329b2b356d
commit 5ba33f8461
258 changed files with 19187 additions and 7335 deletions
-1
View File
@@ -201,6 +201,5 @@ CheckOptions:
readability-identifier-naming.EnumConstantCase: UPPER_CASE
readability-identifier-naming.FunctionCase: camelBack
readability-identifier-naming.NamespaceCase: CamelCase
readability-identifier-naming.NamespacePrefix: N
readability-identifier-naming.StructPrefix: S
readability-identifier-naming.StructCase: CamelCase
+1
View File
@@ -45,6 +45,7 @@ runs:
libxkbcommon \
libxkbfile \
lld \
lua \
meson \
muparser \
ninja \
+29 -1
View File
@@ -34,6 +34,7 @@ if(NOT HYPR_SHADER_GEN_RESULT EQUAL 0)
endif()
find_package(PkgConfig REQUIRED)
find_package(Python3 COMPONENTS Interpreter QUIET)
# Try to find canihavesomecoffee's udis86 using pkgconfig vmd/udis86 does not
# provide a .pc file and won't be detected this way
@@ -268,7 +269,8 @@ pkg_check_modules(
gio-2.0
re2
muparser
lcms2)
lcms2
lua55)
find_package(hyprwayland-scanner 0.3.10 REQUIRED)
@@ -437,6 +439,28 @@ endif()
# used by `make installheaders`, to ensure the headers are generated
add_custom_target(generate-protocol-headers)
if(Python3_Interpreter_FOUND)
add_custom_target(
generate-lua-stubs ALL
COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/meta/generateLuaStubs.py --root ${CMAKE_SOURCE_DIR}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Generating Lua API stubs for LuaLS"
VERBATIM)
add_dependencies(hyprland_lib generate-lua-stubs)
add_dependencies(Hyprland generate-lua-stubs)
add_custom_target(
check-lua-stubs
COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/meta/generateLuaStubs.py --root ${CMAKE_SOURCE_DIR} --check
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Checking Lua API stubs are up to date"
VERBATIM)
else()
message(FATAL_ERROR "Python3 interpreter not found, which is required for the build")
endif()
set(PROTOCOL_SOURCES "")
function(protocolnew protoPath protoName external)
@@ -605,6 +629,10 @@ install(FILES ${INSTALLABLE_ASSETS}
install(FILES ${CMAKE_SOURCE_DIR}/example/hyprland.conf
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr)
# LuaLS stubs
install(FILES ${CMAKE_SOURCE_DIR}/meta/hl.meta.lua
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr/stubs)
# portal config
install(FILES ${CMAKE_SOURCE_DIR}/assets/hyprland-portals.conf
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/xdg-desktop-portal)
+1 -1
View File
@@ -108,4 +108,4 @@ format-fix:
test:
$(MAKE) debug
./build/hyprtester/hyprtester -c hyprtester/test.conf -b ./build/Hyprland -p hyprtester/plugin/hyprtestplugin.so
./build/hyprtester/hyprtester -c hyprtester/test.lua -b ./build/Hyprland -p hyprtester/plugin/hyprtestplugin.so
+6
View File
@@ -123,6 +123,12 @@ Lists all the layers.
.RS
.PP
Returns the current random splash.
\f[B]status\f[R]
.RS
.PP
Returns internal status information like config format or backend.
.RE
.PP
.RE
.SH OPTIONS
.PP
+4
View File
@@ -88,6 +88,10 @@ INFO COMMANDS
Returns the current random splash.
**status**
Returns internal status information like config format or backend.
OPTIONS
=======
-1
View File
@@ -179,7 +179,6 @@ animations {
# See https://wiki.hypr.land/Configuring/Dwindle-Layout/ for more
dwindle {
pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
preserve_split = true # You probably want this
}
+350
View File
@@ -0,0 +1,350 @@
-- This is an example Hyprland Lua config file.
-- Refer to the wiki for more information.
-- https://wiki.hypr.land/Configuring/
-- Please note not all available settings / options are set here.
-- For a full list, see the wiki
-- You can split this configuration into multiple files
-- Create your files separately and then require them like this:
-- require("myColors")
------------------
---- MONITORS ----
------------------
-- See https://wiki.hypr.land/Configuring/Monitors/
hl.monitor({
output = "",
mode = "preferred",
position = "auto",
scale = "auto",
})
---------------------
---- MY PROGRAMS ----
---------------------
-- See https://wiki.hypr.land/Configuring/Keywords/
-- Set programs that you use
local terminal = "kitty"
local fileManager = "dolphin"
local menu = "hyprlauncher"
-------------------
---- AUTOSTART ----
-------------------
-- Autostart necessary processes (like notifications daemons, status bars, etc.)
-- Or execute your favorite apps at launch like this:
--
-- hl.on("hyprland.start", function ()
-- hl.exec_cmd(terminal)
-- hl.exec_cmd("nm-applet")
-- hl.exec_cmd("waybar & hyprpaper & firefox")
-- end)
-- Timers return an object handle.
-- local demoTimer = hl.timer(function()
-- print("hello from timer")
-- end, { timeout = 1000, type = "repeat" })
-- demoTimer:set_enabled(false)
-------------------------------
---- ENVIRONMENT VARIABLES ----
-------------------------------
-- See https://wiki.hypr.land/Configuring/Environment-variables/
hl.env("XCURSOR_SIZE", "24")
hl.env("HYPRCURSOR_SIZE", "24")
-----------------------
---- LOOK AND FEEL ----
-----------------------
-- Refer to https://wiki.hypr.land/Configuring/Variables/
-- https://wiki.hypr.land/Configuring/Variables/#general
hl.config({
general = {
gaps_in = 5,
gaps_out = 20,
border_size = 2,
-- https://wiki.hypr.land/Configuring/Variables/#variable-types for info about colors
col = {
active_border = { colors = {"rgba(33ccffee)", "rgba(00ff99ee)"}, angle = 45 },
inactive_border = "rgba(595959aa)",
},
-- Set to true to enable resizing windows by clicking and dragging on borders and gaps
resize_on_border = false,
-- Please see https://wiki.hypr.land/Configuring/Tearing/ before you turn this on
allow_tearing = false,
layout = "dwindle",
},
})
-- https://wiki.hypr.land/Configuring/Variables/#decoration
hl.config({
decoration = {
rounding = 10,
rounding_power = 2,
-- Change transparency of focused and unfocused windows
active_opacity = 1.0,
inactive_opacity = 1.0,
shadow = {
enabled = true,
range = 4,
render_power = 3,
color = 0xee1a1a1a,
},
-- https://wiki.hypr.land/Configuring/Variables/#blur
blur = {
enabled = true,
size = 3,
passes = 1,
vibrancy = 0.1696,
},
},
})
-- https://wiki.hypr.land/Configuring/Variables/#animations
hl.config({
animations = {
enabled = true,
},
})
-- Default curves, see https://wiki.hypr.land/Configuring/Animations/#curves
hl.curve("easeOutQuint", { type = "bezier", points = { {0.23, 1}, {0.32, 1} } })
hl.curve("easeInOutCubic", { type = "bezier", points = { {0.65, 0.05}, {0.36, 1} } })
hl.curve("linear", { type = "bezier", points = { {0, 0}, {1, 1} } })
hl.curve("almostLinear", { type = "bezier", points = { {0.5, 0.5}, {0.75, 1} } })
hl.curve("quick", { type = "bezier", points = { {0.15, 0}, {0.1, 1} } })
-- Default animations, see https://wiki.hypr.land/Configuring/Animations/
hl.animation({ leaf = "global", enabled = true, speed = 10, bezier = "default" })
hl.animation({ leaf = "border", enabled = true, speed = 5.39, bezier = "easeOutQuint" })
hl.animation({ leaf = "windows", enabled = true, speed = 4.79, bezier = "easeOutQuint" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 4.1, bezier = "easeOutQuint", style = "popin 87%" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 1.49, bezier = "linear", style = "popin 87%" })
hl.animation({ leaf = "fadeIn", enabled = true, speed = 1.73, bezier = "almostLinear" })
hl.animation({ leaf = "fadeOut", enabled = true, speed = 1.46, bezier = "almostLinear" })
hl.animation({ leaf = "fade", enabled = true, speed = 3.03, bezier = "quick" })
hl.animation({ leaf = "layers", enabled = true, speed = 3.81, bezier = "easeOutQuint" })
hl.animation({ leaf = "layersIn", enabled = true, speed = 4, bezier = "easeOutQuint", style = "fade" })
hl.animation({ leaf = "layersOut", enabled = true, speed = 1.5, bezier = "linear", style = "fade" })
hl.animation({ leaf = "fadeLayersIn", enabled = true, speed = 1.79, bezier = "almostLinear" })
hl.animation({ leaf = "fadeLayersOut", enabled = true, speed = 1.39, bezier = "almostLinear" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 1.94, bezier = "almostLinear", style = "fade" })
hl.animation({ leaf = "workspacesIn", enabled = true, speed = 1.21, bezier = "almostLinear", style = "fade" })
hl.animation({ leaf = "workspacesOut", enabled = true, speed = 1.94, bezier = "almostLinear", style = "fade" })
hl.animation({ leaf = "zoomFactor", enabled = true, speed = 7, bezier = "quick" })
-- Ref https://wiki.hypr.land/Configuring/Workspace-Rules/
-- "Smart gaps" / "No gaps when only"
-- uncomment all if you wish to use that.
-- hl.workspace_rule({ workspace = "w[tv1]", gaps_out = 0, gaps_in = 0 })
-- hl.workspace_rule({ workspace = "f[1]", gaps_out = 0, gaps_in = 0 })
-- hl.window_rule({
-- name = "no-gaps-wtv1",
-- match = { float = false, workspace = "w[tv1]" },
-- border_size = 0,
-- rounding = 0,
-- })
-- hl.window_rule({
-- name = "no-gaps-f1",
-- match = { float = false, workspace = "f[1]" },
-- border_size = 0,
-- rounding = 0,
-- })
-- See https://wiki.hypr.land/Configuring/Dwindle-Layout/ for more
hl.config({
dwindle = {
preserve_split = true, -- You probably want this
},
})
-- See https://wiki.hypr.land/Configuring/Master-Layout/ for more
hl.config({
master = {
new_status = "master",
},
})
-- See https://wiki.hypr.land/Configuring/Scrolling-Layout/ for more
hl.config({
scrolling = {
fullscreen_on_one_column = true,
},
})
-- https://wiki.hypr.land/Configuring/Variables/#misc
hl.config({
misc = {
force_default_wallpaper = -1, -- Set to 0 or 1 to disable the anime mascot wallpapers
disable_hyprland_logo = false, -- If true disables the random hyprland logo / anime girl background. :(
},
})
---------------
---- INPUT ----
---------------
-- https://wiki.hypr.land/Configuring/Variables/#input
hl.config({
input = {
kb_layout = "us",
kb_variant = "",
kb_model = "",
kb_options = "",
kb_rules = "",
follow_mouse = 1,
sensitivity = 0, -- -1.0 - 1.0, 0 means no modification.
touchpad = {
natural_scroll = false,
},
},
})
-- See https://wiki.hypr.land/Configuring/Gestures/
hl.gesture({
fingers = 3,
direction = "horizontal",
action = "workspace"
})
-- Example per-device config
-- See https://wiki.hypr.land/Configuring/Keywords/#per-device-input-configs for more
hl.device({
name = "epic-mouse-v1",
sensitivity = -0.5,
})
---------------------
---- KEYBINDINGS ----
---------------------
-- See https://wiki.hypr.land/Configuring/Keywords/
local mainMod = "SUPER" -- Sets "Windows" key as main modifier
-- Example binds, see https://wiki.hypr.land/Configuring/Binds/ for more
hl.bind(mainMod .. " + Q", hl.dsp.exec_cmd(terminal))
local closeWindowBind = hl.bind(mainMod .. " + C", hl.dsp.window.close())
-- closeWindowBind:set_enabled(false)
hl.bind(mainMod .. " + M", hl.dsp.exec_cmd("command -v hyprshutdown >/dev/null 2>&1 && hyprshutdown || hyprctl dispatch 'hl.dsp.exit()'"))
hl.bind(mainMod .. " + E", hl.dsp.exec_cmd(fileManager))
hl.bind(mainMod .. " + V", hl.dsp.window.float({ action = "toggle" }))
hl.bind(mainMod .. " + R", hl.dsp.exec_cmd(menu))
hl.bind(mainMod .. " + P", hl.dsp.window.pseudo())
hl.bind(mainMod .. " + J", hl.dsp.layout("togglesplit")) -- dwindle only
-- Move focus with mainMod + arrow keys
hl.bind(mainMod .. " + left", hl.dsp.focus({ direction = "left" }))
hl.bind(mainMod .. " + right", hl.dsp.focus({ direction = "right" }))
hl.bind(mainMod .. " + up", hl.dsp.focus({ direction = "up" }))
hl.bind(mainMod .. " + down", hl.dsp.focus({ direction = "down" }))
-- Switch workspaces with mainMod + [0-9]
-- Move active window to a workspace with mainMod + SHIFT + [0-9]
for i = 1, 10 do
local key = i % 10 -- 10 maps to key 0
hl.bind(mainMod .. " + " .. key, hl.dsp.focus({ workspace = i}))
hl.bind(mainMod .. " + SHIFT + " .. key, hl.dsp.window.move({ workspace = i }))
end
-- Example special workspace (scratchpad)
hl.bind(mainMod .. " + S", hl.dsp.workspace.toggle_special("magic"))
hl.bind(mainMod .. " + SHIFT + S", hl.dsp.window.move({ workspace = "special:magic" }))
-- Scroll through existing workspaces with mainMod + scroll
hl.bind(mainMod .. " + mouse_down", hl.dsp.focus({ workspace = "e+1" }))
hl.bind(mainMod .. " + mouse_up", hl.dsp.focus({ workspace = "e-1" }))
-- Move/resize windows with mainMod + LMB/RMB and dragging
hl.bind(mainMod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
hl.bind(mainMod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true })
-- Laptop multimedia keys for volume and LCD brightness
hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+"), { locked = true, repeating = true })
hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"), { locked = true, repeating = true })
hl.bind("XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"), { locked = true, repeating = true })
hl.bind("XF86AudioMicMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"), { locked = true, repeating = true })
hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd("brightnessctl -e4 -n2 set 5%+"), { locked = true, repeating = true })
hl.bind("XF86MonBrightnessDown",hl.dsp.exec_cmd("brightnessctl -e4 -n2 set 5%-"), { locked = true, repeating = true })
-- Requires playerctl
hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"), { locked = true })
hl.bind("XF86AudioPause", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true })
hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true })
hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true })
--------------------------------
---- WINDOWS AND WORKSPACES ----
--------------------------------
-- See https://wiki.hypr.land/Configuring/Window-Rules/ for more
-- See https://wiki.hypr.land/Configuring/Workspace-Rules/ for workspace rules
-- Example window rules that are useful
local suppressMaximizeRule = hl.window_rule({
-- Ignore maximize requests from all apps. You'll probably like this.
name = "suppress-maximize-events",
match = { class = ".*" },
suppress_event = "maximize",
})
-- suppressMaximizeRule:set_enabled(false)
hl.window_rule({
-- Fix some dragging issues with XWayland
name = "fix-xwayland-drags",
match = {
class = "^$",
title = "^$",
xwayland = true,
float = true,
fullscreen = false,
pin = false,
},
no_focus = true,
})
-- Layer rules also return a handle.
-- local overlayLayerRule = hl.layer_rule({
-- name = "no-anim-overlay",
-- match = { namespace = "^my-overlay$" },
-- no_anim = true,
-- })
-- overlayLayerRule:set_enabled(false)
-- Hyprland-run windowrule
hl.window_rule({
name = "move-hyprland-run",
match = { class = "hyprland-run" },
move = "20 monitor_h-120",
float = true,
})
+1 -1
View File
@@ -23,7 +23,7 @@ _hyprctl () {
local words cword
_get_comp_words_by_ref -n "$COMP_WORDBREAKS" words cword
declare -a literals=(resizeactive 2 changegroupactive -r moveintogroup forceallowsinput 4 ::= systeminfo all layouts setprop animationstyle switchxkblayout create denywindowfromgroup headless activebordercolor exec setcursor wayland focusurgentorlast workspacerules movecurrentworkspacetomonitor movetoworkspacesilent hyprpaper alpha inactivebordercolor movegroupwindow movecursortocorner movewindowpixel prev movewindow globalshortcuts clients dimaround setignoregrouplock splash execr monitors 0 forcenoborder -q animations 1 nomaxsize splitratio moveactive pass swapnext devices layers rounding lockactivegroup 5 moveworkspacetomonitor -f -i --quiet forcenodim pin 0 1 forceopaque forcenoshadow setfloating minsize alphaoverride sendshortcut workspaces cyclenext alterzorder togglegroup lockgroups bordersize dpms focuscurrentorlast -1 --batch notify remove instances 1 3 moveoutofgroup killactive 2 movetoworkspace movecursor configerrors closewindow swapwindow tagwindow forcerendererreload centerwindow auto focuswindow seterror nofocus alphafullscreen binds version -h togglespecialworkspace fullscreen windowdancecompat 0 keyword toggleopaque 3 --instance togglefloating renameworkspace alphafullscreenoverride activeworkspace x11 kill forceopaqueoverriden output global dispatch reload forcenoblur -j event --help disable -1 activewindow keepaspectratio dismissnotify focusmonitor movefocus plugin exit workspace fullscreenstate getoption alphainactiveoverride alphainactive decorations settiled config-only descriptions resizewindowpixel fakefullscreen rollinglog swapactiveworkspaces submap next movewindoworgroup cursorpos forcenoanims focusworkspaceoncurrentmonitor maxsize sendkeystate)
declare -a literals=(resizeactive 2 changegroupactive -r moveintogroup forceallowsinput 4 ::= systeminfo all layouts setprop animationstyle switchxkblayout create denywindowfromgroup headless activebordercolor exec setcursor wayland focusurgentorlast workspacerules movecurrentworkspacetomonitor movetoworkspacesilent hyprpaper alpha inactivebordercolor movegroupwindow movecursortocorner movewindowpixel prev movewindow globalshortcuts clients dimaround splash execr monitors 0 forcenoborder -q animations 1 nomaxsize splitratio moveactive pass swapnext devices layers rounding lockactivegroup 5 moveworkspacetomonitor -f -i --quiet forcenodim pin 0 1 forceopaque forcenoshadow setfloating minsize alphaoverride sendshortcut workspaces cyclenext alterzorder togglegroup lockgroups bordersize dpms focuscurrentorlast -1 --batch notify remove instances 1 3 moveoutofgroup killactive 2 movetoworkspace movecursor configerrors closewindow swapwindow tagwindow forcerendererreload centerwindow auto focuswindow seterror nofocus alphafullscreen binds version -h togglespecialworkspace fullscreen windowdancecompat 0 keyword toggleopaque 3 --instance togglefloating renameworkspace alphafullscreenoverride activeworkspace x11 kill forceopaqueoverriden output global dispatch reload forcenoblur -j event --help disable -1 activewindow keepaspectratio dismissnotify focusmonitor movefocus plugin exit workspace fullscreenstate getoption alphainactiveoverride alphainactive decorations settiled config-only descriptions resizewindowpixel fakefullscreen rollinglog swapactiveworkspaces submap next movewindoworgroup cursorpos forcenoanims focusworkspaceoncurrentmonitor maxsize sendkeystate)
declare -A literal_transitions
literal_transitions[0]="([120]=14 [43]=2 [125]=21 [81]=2 [3]=21 [51]=2 [50]=2 [128]=2 [89]=2 [58]=21 [8]=2 [10]=2 [11]=3 [130]=4 [13]=5 [97]=6 [101]=2 [102]=21 [133]=7 [100]=2 [137]=2 [22]=2 [19]=2 [140]=8 [25]=2 [143]=2 [107]=9 [146]=10 [69]=2 [33]=2 [34]=2 [78]=21 [114]=2 [37]=2 [151]=2 [116]=2 [121]=13 [123]=21 [39]=11 [42]=21 [79]=15 [118]=12)"
literal_transitions[1]="([81]=2 [51]=2 [50]=2 [128]=2 [8]=2 [89]=2 [10]=2 [11]=3 [130]=4 [13]=5 [97]=6 [101]=2 [133]=7 [100]=2 [22]=2 [19]=2 [137]=2 [140]=8 [25]=2 [143]=2 [107]=9 [146]=10 [69]=2 [33]=2 [34]=2 [114]=2 [37]=2 [151]=2 [116]=2 [39]=11 [118]=12 [121]=13 [120]=14 [79]=15 [43]=2)"
+1 -1
View File
@@ -29,7 +29,7 @@ function _hyprctl
set COMP_CWORD (count $COMP_WORDS)
end
set literals "resizeactive" "2" "changegroupactive" "-r" "moveintogroup" "forceallowsinput" "4" "::=" "systeminfo" "all" "layouts" "setprop" "animationstyle" "switchxkblayout" "create" "denywindowfromgroup" "headless" "activebordercolor" "exec" "setcursor" "wayland" "focusurgentorlast" "workspacerules" "movecurrentworkspacetomonitor" "movetoworkspacesilent" "hyprpaper" "alpha" "inactivebordercolor" "movegroupwindow" "movecursortocorner" "movewindowpixel" "prev" "movewindow" "globalshortcuts" "clients" "dimaround" "setignoregrouplock" "splash" "execr" "monitors" "0" "forcenoborder" "-q" "animations" "1" "nomaxsize" "splitratio" "moveactive" "pass" "swapnext" "devices" "layers" "rounding" "lockactivegroup" "5" "moveworkspacetomonitor" "-f" "-i" "--quiet" "forcenodim" "pin" "0" "1" "forceopaque" "forcenoshadow" "setfloating" "minsize" "alphaoverride" "sendshortcut" "workspaces" "cyclenext" "alterzorder" "togglegroup" "lockgroups" "bordersize" "dpms" "focuscurrentorlast" "-1" "--batch" "notify" "remove" "instances" "1" "3" "moveoutofgroup" "killactive" "2" "movetoworkspace" "movecursor" "configerrors" "closewindow" "swapwindow" "tagwindow" "forcerendererreload" "centerwindow" "auto" "focuswindow" "seterror" "nofocus" "alphafullscreen" "binds" "version" "-h" "togglespecialworkspace" "fullscreen" "windowdancecompat" "0" "keyword" "toggleopaque" "3" "--instance" "togglefloating" "renameworkspace" "alphafullscreenoverride" "activeworkspace" "x11" "kill" "forceopaqueoverriden" "output" "global" "dispatch" "reload" "forcenoblur" "-j" "event" "--help" "disable" "-1" "activewindow" "keepaspectratio" "dismissnotify" "focusmonitor" "movefocus" "plugin" "exit" "workspace" "fullscreenstate" "getoption" "alphainactiveoverride" "alphainactive" "decorations" "settiled" "config-only" "descriptions" "resizewindowpixel" "fakefullscreen" "rollinglog" "swapactiveworkspaces" "submap" "next" "movewindoworgroup" "cursorpos" "forcenoanims" "focusworkspaceoncurrentmonitor" "maxsize" "sendkeystate"
set literals "resizeactive" "2" "changegroupactive" "-r" "moveintogroup" "forceallowsinput" "4" "::=" "systeminfo" "all" "layouts" "setprop" "animationstyle" "switchxkblayout" "create" "denywindowfromgroup" "headless" "activebordercolor" "exec" "setcursor" "wayland" "focusurgentorlast" "workspacerules" "movecurrentworkspacetomonitor" "movetoworkspacesilent" "hyprpaper" "alpha" "inactivebordercolor" "movegroupwindow" "movecursortocorner" "movewindowpixel" "prev" "movewindow" "globalshortcuts" "clients" "dimaround" "splash" "execr" "monitors" "0" "forcenoborder" "-q" "animations" "1" "nomaxsize" "splitratio" "moveactive" "pass" "swapnext" "devices" "layers" "rounding" "lockactivegroup" "5" "moveworkspacetomonitor" "-f" "-i" "--quiet" "forcenodim" "pin" "0" "1" "forceopaque" "forcenoshadow" "setfloating" "minsize" "alphaoverride" "sendshortcut" "workspaces" "cyclenext" "alterzorder" "togglegroup" "lockgroups" "bordersize" "dpms" "focuscurrentorlast" "-1" "--batch" "notify" "remove" "instances" "1" "3" "moveoutofgroup" "killactive" "2" "movetoworkspace" "movecursor" "configerrors" "closewindow" "swapwindow" "tagwindow" "forcerendererreload" "centerwindow" "auto" "focuswindow" "seterror" "nofocus" "alphafullscreen" "binds" "version" "-h" "togglespecialworkspace" "fullscreen" "windowdancecompat" "0" "keyword" "toggleopaque" "3" "--instance" "togglefloating" "renameworkspace" "alphafullscreenoverride" "activeworkspace" "x11" "kill" "forceopaqueoverriden" "output" "global" "dispatch" "reload" "forcenoblur" "-j" "event" "--help" "disable" "-1" "activewindow" "keepaspectratio" "dismissnotify" "focusmonitor" "movefocus" "plugin" "exit" "workspace" "fullscreenstate" "getoption" "alphainactiveoverride" "alphainactive" "decorations" "settiled" "config-only" "descriptions" "resizewindowpixel" "fakefullscreen" "rollinglog" "swapactiveworkspaces" "submap" "next" "movewindoworgroup" "cursorpos" "forcenoanims" "focusworkspaceoncurrentmonitor" "maxsize" "sendkeystate"
set descriptions
set descriptions[1] "Resize the active window"
+1 -1
View File
@@ -93,6 +93,7 @@ hyprctl [<OPTIONS>]... <ARGUMENTS>
| (version) "Print the Hyprland version: flags, commit and branch of build"
| (workspacerules) "Get the list of defined workspace rules"
| (workspaces) "List all workspaces with their properties"
| (status) "Get internal status information like config format or backend"
;
<WINDOW_STATE> ::= (-1) "Current"
@@ -157,7 +158,6 @@ hyprctl [<OPTIONS>]... <ARGUMENTS>
| (movewindoworgroup) "Behave as moveintogroup"
| (movegroupwindow) "Swap the active window with the next or previous in a group"
| (denywindowfromgroup) "Prohibit the active window from becoming or being inserted into group"
| (setignoregrouplock) "Temporarily enable or disable binds:ignore_group_lock"
| (global) "Execute a Global Shortcut using the GlobalShortcuts portal"
| (submap) "Change the current mapping group"
| (event) "Emits a custom event to socket2"
+1 -1
View File
@@ -17,7 +17,7 @@ _hyprctl_cmd_0 () {
}
_hyprctl () {
local -a literals=("resizeactive" "2" "changegroupactive" "-r" "moveintogroup" "forceallowsinput" "4" "::=" "systeminfo" "all" "layouts" "setprop" "animationstyle" "switchxkblayout" "create" "denywindowfromgroup" "headless" "activebordercolor" "exec" "setcursor" "wayland" "focusurgentorlast" "workspacerules" "movecurrentworkspacetomonitor" "movetoworkspacesilent" "hyprpaper" "alpha" "inactivebordercolor" "movegroupwindow" "movecursortocorner" "movewindowpixel" "prev" "movewindow" "globalshortcuts" "clients" "dimaround" "setignoregrouplock" "splash" "execr" "monitors" "0" "forcenoborder" "-q" "animations" "1" "nomaxsize" "splitratio" "moveactive" "pass" "swapnext" "devices" "layers" "rounding" "lockactivegroup" "5" "moveworkspacetomonitor" "-f" "-i" "--quiet" "forcenodim" "pin" "0" "1" "forceopaque" "forcenoshadow" "setfloating" "minsize" "alphaoverride" "sendshortcut" "workspaces" "cyclenext" "alterzorder" "togglegroup" "lockgroups" "bordersize" "dpms" "focuscurrentorlast" "-1" "--batch" "notify" "remove" "instances" "1" "3" "moveoutofgroup" "killactive" "2" "movetoworkspace" "movecursor" "configerrors" "closewindow" "swapwindow" "tagwindow" "forcerendererreload" "centerwindow" "auto" "focuswindow" "seterror" "nofocus" "alphafullscreen" "binds" "version" "-h" "togglespecialworkspace" "fullscreen" "windowdancecompat" "0" "keyword" "toggleopaque" "3" "--instance" "togglefloating" "renameworkspace" "alphafullscreenoverride" "activeworkspace" "x11" "kill" "forceopaqueoverriden" "output" "global" "dispatch" "reload" "forcenoblur" "-j" "event" "--help" "disable" "-1" "activewindow" "keepaspectratio" "dismissnotify" "focusmonitor" "movefocus" "plugin" "exit" "workspace" "fullscreenstate" "getoption" "alphainactiveoverride" "alphainactive" "decorations" "settiled" "config-only" "descriptions" "resizewindowpixel" "fakefullscreen" "rollinglog" "swapactiveworkspaces" "submap" "next" "movewindoworgroup" "cursorpos" "forcenoanims" "focusworkspaceoncurrentmonitor" "maxsize" "sendkeystate")
local -a literals=("resizeactive" "2" "changegroupactive" "-r" "moveintogroup" "forceallowsinput" "4" "::=" "systeminfo" "all" "layouts" "setprop" "animationstyle" "switchxkblayout" "create" "denywindowfromgroup" "headless" "activebordercolor" "exec" "setcursor" "wayland" "focusurgentorlast" "workspacerules" "movecurrentworkspacetomonitor" "movetoworkspacesilent" "hyprpaper" "alpha" "inactivebordercolor" "movegroupwindow" "movecursortocorner" "movewindowpixel" "prev" "movewindow" "globalshortcuts" "clients" "dimaround" "splash" "execr" "monitors" "0" "forcenoborder" "-q" "animations" "1" "nomaxsize" "splitratio" "moveactive" "pass" "swapnext" "devices" "layers" "rounding" "lockactivegroup" "5" "moveworkspacetomonitor" "-f" "-i" "--quiet" "forcenodim" "pin" "0" "1" "forceopaque" "forcenoshadow" "setfloating" "minsize" "alphaoverride" "sendshortcut" "workspaces" "cyclenext" "alterzorder" "togglegroup" "lockgroups" "bordersize" "dpms" "focuscurrentorlast" "-1" "--batch" "notify" "remove" "instances" "1" "3" "moveoutofgroup" "killactive" "2" "movetoworkspace" "movecursor" "configerrors" "closewindow" "swapwindow" "tagwindow" "forcerendererreload" "centerwindow" "auto" "focuswindow" "seterror" "nofocus" "alphafullscreen" "binds" "version" "-h" "togglespecialworkspace" "fullscreen" "windowdancecompat" "0" "keyword" "toggleopaque" "3" "--instance" "togglefloating" "renameworkspace" "alphafullscreenoverride" "activeworkspace" "x11" "kill" "forceopaqueoverriden" "output" "global" "dispatch" "reload" "forcenoblur" "-j" "event" "--help" "disable" "-1" "activewindow" "keepaspectratio" "dismissnotify" "focusmonitor" "movefocus" "plugin" "exit" "workspace" "fullscreenstate" "getoption" "alphainactiveoverride" "alphainactive" "decorations" "settiled" "config-only" "descriptions" "resizewindowpixel" "fakefullscreen" "rollinglog" "swapactiveworkspaces" "submap" "next" "movewindoworgroup" "cursorpos" "forcenoanims" "focusworkspaceoncurrentmonitor" "maxsize" "sendkeystate")
local -A descriptions
descriptions[1]="Resize the active window"
+1
View File
@@ -51,6 +51,7 @@ commands:
setprop ... Sets a window property
getprop ... Gets a window property
splash Get the current splash
status Get internal status information
switchxkblayout ... Sets the xkb layout index for a keyboard
systeminfo Get system info
version Prints the hyprland version, meaning flags, commit
+2
View File
@@ -526,6 +526,8 @@ int main(int argc, char** argv) {
exitStatus = request(fullRequest, 2);
else if (fullRequest.contains("/decorations"))
exitStatus = request(fullRequest, 1);
else if (fullRequest.contains("/eval"))
exitStatus = request(fullRequest, 1);
else if (fullRequest.contains("/--help"))
std::println("{}", USAGE);
else if (fullRequest.contains("/rollinglog") && needRoll)
+1 -1
View File
@@ -24,7 +24,7 @@ target_link_libraries(hyprtester PUBLIC PkgConfig::hyprtester_deps)
install(TARGETS hyprtester)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/test.conf
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/test.lua
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/plugin/hyprtestplugin.so
+91 -27
View File
@@ -4,8 +4,6 @@
#include <any>
#define private public
#include <src/config/legacy/ConfigManager.hpp>
#include <src/config/supplementary/ConfigDescriptions.hpp>
#include <src/managers/input/InputManager.hpp>
#include <src/managers/PointerManager.hpp>
#include <src/managers/input/trackpad/TrackpadGestures.hpp>
@@ -23,6 +21,11 @@
using namespace Hyprutils::Utils;
using namespace Hyprutils::String;
extern "C" {
#include <lua.h>
#include <lauxlib.h>
}
#include "globals.hpp"
// Do NOT change this function.
@@ -31,18 +34,7 @@ APICALL EXPORT std::string PLUGIN_API_VERSION() {
}
static SDispatchResult test(std::string in) {
bool success = true;
std::string errors = "";
if (Config::Legacy::mgr()->m_configValueNumber != Config::Supplementary::CONFIG_OPTIONS.size() + 1 /* autogenerated is special */) {
errors += "config value number mismatches descriptions size\n";
success = false;
}
return SDispatchResult{
.success = success,
.error = errors,
};
return {.success = true};
}
// Trigger a snap move event for the active window
@@ -353,22 +345,94 @@ static SDispatchResult floatingFocusOnFullscreen(std::string in) {
return {};
}
static int luaResult(lua_State* L, const SDispatchResult& result) {
if (result.success)
return 0;
lua_pushstring(L, result.error.empty() ? "plugin function failed" : result.error.c_str());
return lua_error(L);
}
static int luaTest(lua_State* L) {
return luaResult(L, ::test(""));
}
static int luaSnapMove(lua_State* L) {
return luaResult(L, ::snapMove(""));
}
static int luaVkb(lua_State* L) {
return luaResult(L, ::vkb(""));
}
static int luaAlt(lua_State* L) {
return luaResult(L, ::pressAlt(std::to_string((int)luaL_checkinteger(L, 1))));
}
static int luaGesture(lua_State* L) {
const auto direction = std::string{luaL_checkstring(L, 1)};
const auto fingers = (int)luaL_optinteger(L, 2, 3);
return luaResult(L, ::simulateGesture(std::format("{},{}", direction, fingers)));
}
static int luaScroll(lua_State* L) {
return luaResult(L, ::scroll(std::to_string((double)luaL_checknumber(L, 1))));
}
static int luaClick(lua_State* L) {
const auto button = (int)luaL_checkinteger(L, 1);
const auto pressed = (int)luaL_checkinteger(L, 2);
return luaResult(L, ::click(std::format("{},{}", button, pressed)));
}
static int luaKeybind(lua_State* L) {
const auto press = (int)luaL_checkinteger(L, 1);
const auto modifier = (int)luaL_checkinteger(L, 2);
const auto key = (int)luaL_checkinteger(L, 3);
return luaResult(L, ::keybind(std::format("{},{},{}", press, modifier, key)));
}
static int luaAddWindowRule(lua_State* L) {
return luaResult(L, ::addWindowRule(""));
}
static int luaCheckWindowRule(lua_State* L) {
return luaResult(L, ::checkWindowRule(""));
}
static int luaAddLayerRule(lua_State* L) {
return luaResult(L, ::addLayerRule(""));
}
static int luaCheckLayerRule(lua_State* L) {
return luaResult(L, ::checkLayerRule(""));
}
static int luaFloatingFocusOnFullscreen(lua_State* L) {
return luaResult(L, ::floatingFocusOnFullscreen(""));
}
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
PHANDLE = handle;
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:test", ::test);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:snapmove", ::snapMove);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:vkb", ::vkb);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:alt", ::pressAlt);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:gesture", ::simulateGesture);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:scroll", ::scroll);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:click", ::click);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:keybind", ::keybind);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_window_rule", ::addWindowRule);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_window_rule", ::checkWindowRule);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_layer_rule", ::addLayerRule);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_layer_rule", ::checkLayerRule);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:floating_focus_on_fullscreen", ::floatingFocusOnFullscreen);
auto addLuaFn = [](const std::string& name, PLUGIN_LUA_FN fn) {
if (!HyprlandAPI::addLuaFunction(PHANDLE, "test", name, fn))
Log::logger->log(Log::ERR, "hyprtester plugin: failed to register hl.plugin.test.{}", name);
};
addLuaFn("test", ::luaTest);
addLuaFn("snapmove", ::luaSnapMove);
addLuaFn("vkb", ::luaVkb);
addLuaFn("alt", ::luaAlt);
addLuaFn("gesture", ::luaGesture);
addLuaFn("scroll", ::luaScroll);
addLuaFn("click", ::luaClick);
addLuaFn("keybind", ::luaKeybind);
addLuaFn("add_window_rule", ::luaAddWindowRule);
addLuaFn("check_window_rule", ::luaCheckWindowRule);
addLuaFn("add_layer_rule", ::luaAddLayerRule);
addLuaFn("check_layer_rule", ::luaCheckLayerRule);
addLuaFn("floating_focus_on_fullscreen", ::luaFloatingFocusOnFullscreen);
// init mouse
g_mouse = CTestMouse::create(false);
+4 -4
View File
@@ -59,12 +59,12 @@ static bool launchHyprland(std::string configPath, std::string binaryPath) {
if (configPath == "") {
std::error_code ec;
if (!std::filesystem::exists(cwd + "/test.conf", ec) || ec) {
if (!std::filesystem::exists(cwd + "/test.lua", ec) || ec) {
NLog::log("{}No test config", Colors::RED);
return false;
}
configPath = cwd + "/test.conf";
configPath = cwd + "/test.lua";
}
NLog::log("{}Launching Hyprland", Colors::YELLOW);
@@ -222,7 +222,7 @@ int main(int argc, char** argv, char** envp) {
NLog::log("{}trying to load plugin", Colors::YELLOW);
if (const auto R = getFromSocket(std::format("/plugin load {}", pluginPath)); R != "ok") {
NLog::log("{}Failed to load the test plugin: {}", Colors::RED, R);
getFromSocket("/dispatch exit 1");
getFromSocket("/dispatch hl.dsp.exit()");
return 1;
}
@@ -248,7 +248,7 @@ int main(int argc, char** argv, char** envp) {
// kill hyprland
NLog::log("{}dispatching exit", Colors::YELLOW);
getFromSocket("/dispatch exit");
getFromSocket("/dispatch hl.dsp.exit()");
NLog::log("\n{}Summary:\n\tPASSED: {}{}{}/{}\n\tFAILED: {}{}{}/{}\n{}", Colors::RESET, Colors::GREEN, TESTS_PASSED, Colors::RESET, TESTS_PASSED + TESTS_FAILED, Colors::RED,
TESTS_FAILED, Colors::RESET, TESTS_PASSED + TESTS_FAILED, (TESTS_FAILED > 0 ? std::string{Colors::RED} + "\nSome tests failed.\n" : ""));
@@ -92,7 +92,7 @@ static bool createChild(SClient& client) {
if (!waitForWindow(client.proc, Tests::windowCount()))
NLog::log("{}Child window took too long to open", Colors::RED);
if (getFromSocket("/dispatch focuswindow class:child-test-child") != "ok") {
if (getFromSocket("/dispatch hl.dsp.focus({ window = 'class:child-test-child' })") != "ok") {
NLog::log("{}Failed to focus child window", Colors::RED);
return false;
}
@@ -105,8 +105,8 @@ static bool test() {
if (!startClient(client))
return false;
OK(getFromSocket("/dispatch setfloating class:child-test-parent"));
OK(getFromSocket("/dispatch pin class:child-test-parent"));
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set', window = 'class:child-test-parent' })"));
OK(getFromSocket("/dispatch hl.dsp.window.pin({ action = 'set', window = 'class:child-test-parent' })"));
createChild(client);
EXPECT(Tests::windowCount(), 2)
@@ -127,8 +127,8 @@ static bool test() {
}
// create group and enable auto-grouping
OK(getFromSocket("/dispatch togglegroup"));
OK(getFromSocket("/keyword group:auto_group true"));
OK(getFromSocket("/dispatch hl.dsp.group.toggle()"));
OK(getFromSocket("/eval hl.config({ group = { auto_group = true } })"));
SClient client2;
if (!startClient(client2))
@@ -75,12 +75,12 @@ static bool startClient(SClient& client) {
}
}
if (getFromSocket(std::format("/dispatch setprop pid:{} no_anim 1", client.proc->pid())) != "ok") {
if (getFromSocket(std::format("/dispatch hl.dsp.window.set_prop({{ window = 'pid:{}', prop = 'no_anim', value = '1' }})", client.proc->pid())) != "ok") {
NLog::log("{}Failed to disable animations for client window", Colors::RED, ret);
return false;
}
if (getFromSocket(std::format("/dispatch focuswindow pid:{}", client.proc->pid())) != "ok") {
if (getFromSocket(std::format("/dispatch hl.dsp.focus({{ window = 'pid:{}' }})", client.proc->pid())) != "ok") {
NLog::log("{}Failed to focus pointer-scroll client", Colors::RED, ret);
return false;
}
@@ -119,7 +119,7 @@ static int getLastDelta(SClient& client) {
}
static bool sendScroll(int delta) {
return getFromSocket(std::format("/dispatch plugin:test:scroll {}", delta)) == "ok";
return getFromSocket(std::format("/eval hl.plugin.test.scroll({})", delta)) == "ok";
}
static bool test() {
@@ -128,20 +128,20 @@ static bool test() {
if (!startClient(client))
return false;
EXPECT(getFromSocket("/keyword input:emulate_discrete_scroll 0"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { emulate_discrete_scroll = 0 } })"), "ok");
EXPECT(sendScroll(10), true);
EXPECT(getLastDelta(client), 10);
EXPECT(getFromSocket("/keyword input:scroll_factor 2"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { scroll_factor = 2 } })"), "ok");
EXPECT(sendScroll(10), true);
EXPECT(getLastDelta(client), 20);
EXPECT(getFromSocket("r/keyword device[test-mouse-1]:scroll_factor 3"), "ok");
EXPECT(getFromSocket("r/eval hl.device({ name = 'test-mouse-1', scroll_factor = 3 })"), "ok");
EXPECT(sendScroll(10), true);
EXPECT(getLastDelta(client), 30);
EXPECT(getFromSocket("r/dispatch setprop active scroll_mouse 4"), "ok");
EXPECT(getFromSocket("r/dispatch hl.dsp.window.set_prop({ window = 'active', prop = 'scroll_mouse', value = '4' })"), "ok");
EXPECT(sendScroll(10), true);
EXPECT(getLastDelta(client), 40);
@@ -75,12 +75,12 @@ static bool startClient(SClient& client) {
}
}
if (getFromSocket(std::format("/dispatch setprop pid:{} no_anim 1", client.proc->pid())) != "ok") {
if (getFromSocket(std::format("/dispatch hl.dsp.window.set_prop({{ window = 'pid:{}', prop = 'no_anim', value = '1' }})", client.proc->pid())) != "ok") {
NLog::log("{}Failed to disable animations for client window", Colors::RED, ret);
return false;
}
if (getFromSocket(std::format("/dispatch focuswindow pid:{}", client.proc->pid())) != "ok") {
if (getFromSocket(std::format("/dispatch hl.dsp.focus({{ window = 'pid:{}' }})", client.proc->pid())) != "ok") {
NLog::log("{}Failed to focus pointer-warp client", Colors::RED, ret);
return false;
}
@@ -85,7 +85,7 @@ static bool startClient(SClient& client) {
return false;
}
if (getFromSocket(std::format("/dispatch focuswindow pid:{}", client.proc->pid())) != "ok") {
if (getFromSocket(std::format("/dispatch hl.dsp.focus({{ window = 'pid:{}' }})", client.proc->pid())) != "ok") {
NLog::log("{}Failed to focus shortcut-inhibitor client", Colors::RED, ret);
return false;
}
@@ -146,27 +146,27 @@ static bool test() {
NLog::log("{}Testing keybinds", Colors::GREEN);
//basic keybind test
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword bind SUPER,Y,exec,touch " + flagFile), "ok");
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'))"), "ok");
OK(getFromSocket("/eval hl.plugin.test.keybind(1, 7, 29)"));
EXPECT(attemptCheckFlag(20, 50), false);
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
OK(getFromSocket("/eval hl.plugin.test.keybind(0, 0, 29)"));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
//keybind bypass flag test
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword bindp SUPER,Y,exec,touch " + flagFile), "ok");
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { dont_inhibit = true })"), "ok");
OK(getFromSocket("/eval hl.plugin.test.keybind(1, 7, 29)"));
EXPECT(attemptCheckFlag(20, 50), true);
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
OK(getFromSocket("/eval hl.plugin.test.keybind(0, 0, 29)"));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
NLog::log("{}Testing gestures", Colors::GREEN);
//basic gesture test
OK(getFromSocket("/dispatch plugin:test:gesture right,3"));
OK(getFromSocket("/eval hl.plugin.test.gesture('right', 3)"));
EXPECT_NOT_CONTAINS(getFromSocket("/activewindow"), "floating: 1");
//gesture bypass flag test
OK(getFromSocket("/dispatch plugin:test:gesture right,2"));
OK(getFromSocket("/eval hl.plugin.test.gesture('right', 2)"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "floating: 1");
stopClient(client);
+2 -2
View File
@@ -14,7 +14,7 @@ static bool test() {
std::string monitorsSpec = getFromSocket("j/monitors");
EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset")");
EXPECT_CONTAINS(getFromSocket("/keyword monitor HEADLESS-2,1920x1080x60.00000,0x0,1.0,bitdepth,10,cm,wide"), "ok")
EXPECT_CONTAINS(getFromSocket("/eval hl.monitor({ output = 'HEADLESS-2', bitdepth = 10, cm = 'wide' })"), "ok")
// monitor settings are applied after a frame is pushed.
std::this_thread::sleep_for(std::chrono::milliseconds(500));
@@ -22,7 +22,7 @@ static bool test() {
monitorsSpec = getFromSocket("j/monitors");
EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset": "wide")");
EXPECT_CONTAINS(getFromSocket("/keyword monitor HEADLESS-2,1920x1080x60.00000,0x0,1.0,bitdepth,10,cm,srgb,sdrbrightness,1.2,sdrsaturation,0.98"), "ok")
EXPECT_CONTAINS(getFromSocket("/eval hl.monitor({ output = 'HEADLESS-2', bitdepth = 10, cm = 'srgb', sdrbrightness = 1.2, sdrsaturation = 0.98 })"), "ok")
monitorsSpec = getFromSocket("j/monitors");
std::this_thread::sleep_for(std::chrono::milliseconds(500));
+41 -41
View File
@@ -15,13 +15,13 @@ static void testFloatClamp() {
}
}
OK(getFromSocket("/keyword dwindle:force_split 2"));
OK(getFromSocket("/keyword monitor HEADLESS-2, addreserved, 0, 20, 0, 20"));
OK(getFromSocket("/dispatch focuswindow class:c"));
OK(getFromSocket("/dispatch setfloating class:c"));
OK(getFromSocket("/dispatch resizewindowpixel exact 1200 900,class:c"));
OK(getFromSocket("/dispatch settiled class:c"));
OK(getFromSocket("/dispatch setfloating class:c"));
OK(getFromSocket("/eval hl.config({ dwindle = { force_split = 2 } })"));
OK(getFromSocket("/eval hl.monitor({ output = 'HEADLESS-2', reserved = { top = 0, right = 20, bottom = 0, left = 20 } })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:c' })"));
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set', window = 'class:c' })"));
OK(getFromSocket("/dispatch hl.dsp.window.resize({ x = 1200, y = 900, window = 'class:c' })"));
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'unset', window = 'class:c' })"));
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set', window = 'class:c' })"));
{
auto str = getFromSocket("/clients");
@@ -29,7 +29,7 @@ static void testFloatClamp() {
EXPECT_CONTAINS(str, "size: 1200,900");
}
OK(getFromSocket("/keyword dwindle:force_split 0"));
OK(getFromSocket("/eval hl.config({ dwindle = { force_split = 0 } })"));
// clean up
NLog::log("{}Killing all windows", Colors::YELLOW);
@@ -52,7 +52,7 @@ static void test13349() {
}
}
OK(getFromSocket("/dispatch focuswindow class:c"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:c' })"));
{
auto str = getFromSocket("/activewindow");
@@ -60,7 +60,7 @@ static void test13349() {
EXPECT_CONTAINS(str, "size: 931,511");
}
OK(getFromSocket("/dispatch movewindow l"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ direction = 'left' })"));
{
auto str = getFromSocket("/activewindow");
@@ -68,7 +68,7 @@ static void test13349() {
EXPECT_CONTAINS(str, "size: 931,511");
}
OK(getFromSocket("/dispatch movewindow r"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ direction = 'right' })"));
{
auto str = getFromSocket("/activewindow");
@@ -87,13 +87,13 @@ static void testSplit() {
Tests::spawnKitty("a");
// these must not crash
EXPECT_NOT(getFromSocket("/dispatch layoutmsg swapsplit"), "ok");
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio 1 exact"), "ok");
EXPECT_NOT(getFromSocket("/dispatch hl.dsp.layout('swapsplit')"), "ok");
EXPECT_NOT(getFromSocket("/dispatch hl.dsp.layout('splitratio 1 exact')"), "ok");
Tests::spawnKitty("b");
OK(getFromSocket("/dispatch focuswindow class:a"));
OK(getFromSocket("/dispatch layoutmsg splitratio -0.2"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:a' })"));
OK(getFromSocket("/dispatch hl.dsp.layout('splitratio -0.2')"));
{
auto str = getFromSocket("/activewindow");
@@ -101,7 +101,7 @@ static void testSplit() {
EXPECT_CONTAINS(str, "size: 743,1036");
}
OK(getFromSocket("/dispatch layoutmsg splitratio 1.6 exact"));
OK(getFromSocket("/dispatch hl.dsp.layout('splitratio 1.6 exact')"));
{
auto str = getFromSocket("/activewindow");
@@ -109,13 +109,13 @@ static void testSplit() {
EXPECT_CONTAINS(str, "size: 1495,1036");
}
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio fhne exact"), "ok");
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio exact"), "ok");
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio -....9"), "ok");
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio ..9"), "ok");
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio"), "ok");
EXPECT_NOT(getFromSocket("/dispatch hl.dsp.layout('splitratio fhne exact')"), "ok");
EXPECT_NOT(getFromSocket("/dispatch hl.dsp.layout('splitratio exact')"), "ok");
EXPECT_NOT(getFromSocket("/dispatch hl.dsp.layout('splitratio -....9')"), "ok");
EXPECT_NOT(getFromSocket("/dispatch hl.dsp.layout('splitratio ..9')"), "ok");
EXPECT_NOT(getFromSocket("/dispatch hl.dsp.layout('splitratio')"), "ok");
OK(getFromSocket("/dispatch layoutmsg togglesplit"));
OK(getFromSocket("/dispatch hl.dsp.layout('togglesplit')"));
{
auto str = getFromSocket("/activewindow");
@@ -123,7 +123,7 @@ static void testSplit() {
EXPECT_CONTAINS(str, "size: 1876,823");
}
OK(getFromSocket("/dispatch layoutmsg swapsplit"));
OK(getFromSocket("/dispatch hl.dsp.layout('swapsplit')"));
{
auto str = getFromSocket("/activewindow");
@@ -136,9 +136,9 @@ static void testSplit() {
}
static void testRotatesplit() {
OK(getFromSocket("r/keyword general:gaps_in 0"));
OK(getFromSocket("r/keyword general:gaps_out 0"));
OK(getFromSocket("r/keyword general:border_size 0"));
OK(getFromSocket("r/eval hl.config({ general = { gaps_in = 0 } })"));
OK(getFromSocket("r/eval hl.config({ general = { gaps_out = 0 } })"));
OK(getFromSocket("r/eval hl.config({ general = { border_size = 0 } })"));
for (auto const& win : {"a", "b"}) {
if (!Tests::spawnKitty(win)) {
@@ -156,28 +156,28 @@ static void testRotatesplit() {
}
// test 4 repeated rotations by 90 degrees
OK(getFromSocket("/dispatch layoutmsg rotatesplit"));
OK(getFromSocket("/dispatch hl.dsp.layout('rotatesplit')"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 1920,540");
}
OK(getFromSocket("/dispatch layoutmsg rotatesplit"));
OK(getFromSocket("/dispatch hl.dsp.layout('rotatesplit')"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 960,0");
EXPECT_CONTAINS(str, "size: 960,1080");
}
OK(getFromSocket("/dispatch layoutmsg rotatesplit"));
OK(getFromSocket("/dispatch hl.dsp.layout('rotatesplit')"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,540");
EXPECT_CONTAINS(str, "size: 1920,540");
}
OK(getFromSocket("/dispatch layoutmsg rotatesplit"));
OK(getFromSocket("/dispatch hl.dsp.layout('rotatesplit')"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,0");
@@ -185,21 +185,21 @@ static void testRotatesplit() {
}
// test different angles
OK(getFromSocket("/dispatch layoutmsg rotatesplit 180"));
OK(getFromSocket("/dispatch hl.dsp.layout('rotatesplit 180')"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 960,0");
EXPECT_CONTAINS(str, "size: 960,1080");
}
OK(getFromSocket("/dispatch layoutmsg rotatesplit 270"));
OK(getFromSocket("/dispatch hl.dsp.layout('rotatesplit 270')"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,540");
EXPECT_CONTAINS(str, "size: 1920,540");
}
OK(getFromSocket("/dispatch layoutmsg rotatesplit 360"));
OK(getFromSocket("/dispatch hl.dsp.layout('rotatesplit 360')"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,0");
@@ -207,14 +207,14 @@ static void testRotatesplit() {
}
// test negative angles
OK(getFromSocket("/dispatch layoutmsg rotatesplit -90"));
OK(getFromSocket("/dispatch hl.dsp.layout('rotatesplit -90')"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 960,1080");
}
OK(getFromSocket("/dispatch layoutmsg rotatesplit -180"));
OK(getFromSocket("/dispatch hl.dsp.layout('rotatesplit -180')"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 960,0");
@@ -228,16 +228,16 @@ static void testRotatesplit() {
}
static void testForceSplitOnMoveToWorkspace() {
OK(getFromSocket("/dispatch workspace 2"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '2' })"));
EXPECT(!!Tests::spawnKitty("kitty"), true);
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
EXPECT(!!Tests::spawnKitty("kitty"), true);
std::string posBefore = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:");
OK(getFromSocket("/keyword dwindle:force_split 2"));
OK(getFromSocket("/dispatch movecursortocorner 3")); // top left
OK(getFromSocket("/dispatch movetoworkspace 2"));
OK(getFromSocket("/eval hl.config({ dwindle = { force_split = 2 } })"));
OK(getFromSocket("/dispatch hl.dsp.cursor.move_to_corner({ corner = 3 })")); // top left
OK(getFromSocket("/dispatch hl.dsp.window.move({ workspace = '2' })"));
// Should be moved to the right, so the position should change
std::string activeWindow = getFromSocket("/activewindow");
@@ -270,7 +270,7 @@ static bool test() {
// clean up
NLog::log("Cleaning up", Colors::YELLOW);
getFromSocket("/dispatch workspace 1");
getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })");
OK(getFromSocket("/reload"));
return !ret;
+1 -1
View File
@@ -24,7 +24,7 @@ static bool test() {
for (const auto duration : SLEEP_DURATIONS) {
// Note: POSIX sleep does not support fractional seconds, so
// can't sleep for less than 1 second.
OK(getFromSocket(std::format("/dispatch exec sleep {}", duration)));
OK(getFromSocket(std::format("/dispatch hl.dsp.exec_cmd('sleep {}')", duration)));
// Ensure that sleep is our child
const std::string sleepPidS = Tests::execAndGet("pgrep sleep");
@@ -46,78 +46,78 @@ static bool waitForActiveWindow(const std::string& class_, char fullscreen = '0'
static bool test() {
NLog::log("{}Testing follow_mouse_shrink", Colors::GREEN);
getFromSocket("/dispatch workspace name:follow_mouse_shrink");
getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:follow_mouse_shrink' })");
// follow_mouse 2 so cursor position determines focus (mode 1's delta threshold
// is unreliable with movecursor/simulateMouseMovement). float_switch_override_focus 2
// enables focus switching between floating windows.
OK(getFromSocket("/keyword input:follow_mouse 2"));
OK(getFromSocket("/keyword input:float_switch_override_focus 2"));
OK(getFromSocket("/eval hl.config({ input = { follow_mouse = 2 } })"));
OK(getFromSocket("/eval hl.config({ input = { float_switch_override_focus = 2 } })"));
// Spawn two floating windows with a 20px gap
// fms_a: position (100,100), size 400x400 -> hitbox [100,499] x [100,499]
if (!spawnKitty("fms_a"))
return false;
OK(getFromSocket("/dispatch setfloating"));
OK(getFromSocket("/dispatch resizewindowpixel exact 400 400,activewindow"));
OK(getFromSocket("/dispatch movewindowpixel exact 100 100,activewindow"));
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set' })"));
OK(getFromSocket("/dispatch hl.dsp.window.resize({ x = 400, y = 400 })"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ x = 100, y = 100 })"));
// fms_b: position (520,100), size 400x400 -> hitbox [520,919] x [100,499]
if (!spawnKitty("fms_b"))
return false;
OK(getFromSocket("/dispatch setfloating"));
OK(getFromSocket("/dispatch resizewindowpixel exact 400 400,activewindow"));
OK(getFromSocket("/dispatch movewindowpixel exact 520 100,activewindow"));
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set' })"));
OK(getFromSocket("/dispatch hl.dsp.window.resize({ x = 400, y = 400 })"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ x = 520, y = 100 })"));
// --- Test 1: Baseline shrink=0, edge focus works ---
NLog::log("{}Test 1: shrink=0, cursor at B's left edge focuses B", Colors::GREEN);
OK(getFromSocket("/keyword input:follow_mouse_shrink 0"));
OK(getFromSocket("/eval hl.config({ input = { follow_mouse_shrink = 0 } })"));
// Focus A explicitly, then move cursor inside A so follow_mouse tracks it
OK(getFromSocket("/dispatch focuswindow class:fms_a"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:fms_a' })"));
EXPECT(waitForActiveWindow("fms_a"), true);
OK(getFromSocket("/dispatch movecursor 300 300"));
OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 300, y = 300 })"));
std::this_thread::sleep_for(std::chrono::milliseconds(200));
// Move to just inside B's left edge
OK(getFromSocket("/dispatch movecursor 521 300"));
OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 521, y = 300 })"));
EXPECT(waitForActiveWindow("fms_b"), true);
// --- Test 2: Shrink=20, cursor in dead zone does NOT change focus ---
NLog::log("{}Test 2: shrink=20, cursor in B's dead zone stays on A", Colors::GREEN);
OK(getFromSocket("/keyword input:follow_mouse_shrink 20"));
OK(getFromSocket("/eval hl.config({ input = { follow_mouse_shrink = 20 } })"));
// Focus A explicitly
OK(getFromSocket("/dispatch focuswindow class:fms_a"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:fms_a' })"));
EXPECT(waitForActiveWindow("fms_a"), true);
OK(getFromSocket("/dispatch movecursor 300 300"));
OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 300, y = 300 })"));
std::this_thread::sleep_for(std::chrono::milliseconds(200));
// Move to 530,300 -- 10px inside B, within 20px shrink zone (B's shrunk hitbox starts at 540)
OK(getFromSocket("/dispatch movecursor 530 300"));
OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 530, y = 300 })"));
std::this_thread::sleep_for(std::chrono::milliseconds(500));
EXPECT(isActiveWindow("fms_a"), true);
// --- Test 3: Shrink=20, cursor well inside inactive window DOES focus it ---
NLog::log("{}Test 3: shrink=20, cursor at B's center focuses B", Colors::GREEN);
// Still focused on A from test 2
OK(getFromSocket("/dispatch movecursor 720 300"));
OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 720, y = 300 })"));
EXPECT(waitForActiveWindow("fms_b"), true);
// --- Test 4: Focused window's hitbox is NOT shrunk ---
NLog::log("{}Test 4a: focused window hitbox is not shrunk", Colors::GREEN);
// Focus A explicitly, move cursor inside A, then move near A's right edge
OK(getFromSocket("/dispatch focuswindow class:fms_a"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:fms_a' })"));
EXPECT(waitForActiveWindow("fms_a"), true);
OK(getFromSocket("/dispatch movecursor 300 300"));
OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 300, y = 300 })"));
std::this_thread::sleep_for(std::chrono::milliseconds(200));
OK(getFromSocket("/dispatch movecursor 490 300"));
OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 490, y = 300 })"));
std::this_thread::sleep_for(std::chrono::milliseconds(500));
EXPECT(isActiveWindow("fms_a"), true);
NLog::log("{}Test 4b: inactive window hitbox IS shrunk at same position", Colors::GREEN);
// Focus B explicitly, then move to (490,300). Now A is inactive with shrunk box ending at 479, so 490 is outside A.
OK(getFromSocket("/dispatch focuswindow class:fms_b"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:fms_b' })"));
EXPECT(waitForActiveWindow("fms_b"), true);
OK(getFromSocket("/dispatch movecursor 720 300"));
OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 720, y = 300 })"));
std::this_thread::sleep_for(std::chrono::milliseconds(200));
OK(getFromSocket("/dispatch movecursor 490 300"));
OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 490, y = 300 })"));
std::this_thread::sleep_for(std::chrono::milliseconds(500));
EXPECT(isActiveWindow("fms_b"), true);
+36 -36
View File
@@ -39,7 +39,7 @@ static bool test() {
// test on workspace "window"
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
getFromSocket("/dispatch workspace 1"); // no OK: we might be on 1 already
getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"); // no OK: we might be on 1 already
Tests::spawnKitty();
EXPECT(Tests::windowCount(), 1);
@@ -47,53 +47,53 @@ static bool test() {
// Give the shell a moment to initialize
std::this_thread::sleep_for(std::chrono::milliseconds(500));
OK(getFromSocket("/dispatch plugin:test:gesture up,5"));
OK(getFromSocket("/dispatch plugin:test:gesture down,5"));
OK(getFromSocket("/dispatch plugin:test:gesture left,5"));
OK(getFromSocket("/dispatch plugin:test:gesture right,5"));
OK(getFromSocket("/dispatch plugin:test:gesture right,4"));
OK(getFromSocket("/eval hl.plugin.test.gesture('up', 5)"));
OK(getFromSocket("/eval hl.plugin.test.gesture('down', 5)"));
OK(getFromSocket("/eval hl.plugin.test.gesture('left', 5)"));
OK(getFromSocket("/eval hl.plugin.test.gesture('right', 5)"));
OK(getFromSocket("/eval hl.plugin.test.gesture('right', 4)"));
EXPECT(waitForWindowCount(0, "Gesture sent paste exit + enter to kitty"), true);
EXPECT(Tests::windowCount(), 0);
OK(getFromSocket("/dispatch plugin:test:gesture left,3"));
OK(getFromSocket("/eval hl.plugin.test.gesture('left', 3)"));
EXPECT(waitForWindowCount(1, "Gesture spawned kitty"), true);
EXPECT(Tests::windowCount(), 1);
OK(getFromSocket("/dispatch plugin:test:gesture right,3"));
OK(getFromSocket("/eval hl.plugin.test.gesture('right', 3)"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "floating: 1");
}
OK(getFromSocket("/dispatch plugin:test:gesture down,3"));
OK(getFromSocket("/eval hl.plugin.test.gesture('down', 3)"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "fullscreen: 2");
}
OK(getFromSocket("/dispatch plugin:test:gesture down,3"));
OK(getFromSocket("/eval hl.plugin.test.gesture('down', 3)"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "fullscreen: 0");
}
OK(getFromSocket("/dispatch plugin:test:alt 1"));
OK(getFromSocket("/eval hl.plugin.test.alt(1)"));
OK(getFromSocket("/dispatch plugin:test:gesture left,3"));
OK(getFromSocket("/eval hl.plugin.test.gesture('left', 3)"));
{
auto str = getFromSocket("/workspaces");
EXPECT_CONTAINS(str, "ID 2 (2)");
}
OK(getFromSocket("/dispatch plugin:test:gesture right,3"));
OK(getFromSocket("/eval hl.plugin.test.gesture('right', 3)"));
{
auto str = getFromSocket("/workspaces");
@@ -101,33 +101,33 @@ static bool test() {
}
// check for crashes
OK(getFromSocket("/dispatch plugin:test:gesture right,3"));
OK(getFromSocket("/eval hl.plugin.test.gesture('right', 3)"));
{
auto str = getFromSocket("/workspaces");
EXPECT_NOT_CONTAINS(str, "ID 2 (2)");
}
OK(getFromSocket("/keyword gestures:workspace_swipe_invert 0"));
OK(getFromSocket("/eval hl.config({ gestures = { workspace_swipe_invert = 0 } })"));
OK(getFromSocket("/dispatch plugin:test:gesture right,3"));
OK(getFromSocket("/eval hl.plugin.test.gesture('right', 3)"));
{
auto str = getFromSocket("/workspaces");
EXPECT_CONTAINS(str, "ID 2 (2)");
}
OK(getFromSocket("/dispatch plugin:test:gesture left,3"));
OK(getFromSocket("/eval hl.plugin.test.gesture('left', 3)"));
{
auto str = getFromSocket("/workspaces");
EXPECT_NOT_CONTAINS(str, "ID 2 (2)");
}
OK(getFromSocket("/keyword gestures:workspace_swipe_invert 1"));
OK(getFromSocket("/keyword gestures:workspace_swipe_create_new 0"));
OK(getFromSocket("/eval hl.config({ gestures = { workspace_swipe_invert = 1 } })"));
OK(getFromSocket("/eval hl.config({ gestures = { workspace_swipe_create_new = 0 } })"));
OK(getFromSocket("/dispatch plugin:test:gesture left,3"));
OK(getFromSocket("/eval hl.plugin.test.gesture('left', 3)"));
{
auto str = getFromSocket("/workspaces");
@@ -135,16 +135,16 @@ static bool test() {
EXPECT_CONTAINS(str, "ID 1 (1)");
}
OK(getFromSocket("/dispatch plugin:test:gesture down,3"));
OK(getFromSocket("/eval hl.plugin.test.gesture('down', 3)"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "floating: 0");
}
OK(getFromSocket("/dispatch plugin:test:alt 0"));
OK(getFromSocket("/eval hl.plugin.test.alt(0)"));
OK(getFromSocket("/dispatch plugin:test:gesture up,3"));
OK(getFromSocket("/eval hl.plugin.test.gesture('up', 3)"));
EXPECT(waitForWindowCount(0, "Gesture closed kitty"), true);
@@ -153,38 +153,38 @@ static bool test() {
// This test ensures that `movecursortocorner`, which expects
// a single-character direction argument, is parsed correctly.
Tests::spawnKitty();
OK(getFromSocket("/dispatch movecursortocorner 0"));
OK(getFromSocket("/dispatch hl.dsp.cursor.move_to_corner({ corner = 0, window = 'activewindow' })"));
const std::string cursorPos1 = getFromSocket("/cursorpos");
OK(getFromSocket("/dispatch plugin:test:gesture left,4"));
OK(getFromSocket("/eval hl.plugin.test.gesture('left', 4)"));
const std::string cursorPos2 = getFromSocket("/cursorpos");
// The cursor should have moved because of the gesture
EXPECT(cursorPos1 != cursorPos2, true);
// Test that `workspace previous` works correctly after a workspace gesture.
{
OK(getFromSocket("/keyword gestures:workspace_swipe_invert 0"));
OK(getFromSocket("/keyword gestures:workspace_swipe_create_new 1"));
OK(getFromSocket("/dispatch workspace 3"));
OK(getFromSocket("/eval hl.config({ gestures = { workspace_swipe_invert = 0 } })"));
OK(getFromSocket("/eval hl.config({ gestures = { workspace_swipe_create_new = 1 } })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '3' })"));
// Come to workspace 5 from workspace 3: 5 will remember that.
OK(getFromSocket("/dispatch workspace 5"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '5' })"));
Tests::spawnKitty(); // Keep workspace 5 open
// Swipe from 1 to 5: 5 shall remember that.
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch plugin:test:alt 1"));
OK(getFromSocket("/dispatch plugin:test:gesture right,3"));
OK(getFromSocket("/dispatch plugin:test:alt 0"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
OK(getFromSocket("/eval hl.plugin.test.alt(1)"));
OK(getFromSocket("/eval hl.plugin.test.gesture('right', 3)"));
OK(getFromSocket("/eval hl.plugin.test.alt(0)"));
EXPECT_CONTAINS(getFromSocket("/activeworkspace"), "ID 5 (5)");
// Must return to 1 rather than 3
OK(getFromSocket("/dispatch workspace previous"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'previous' })"));
EXPECT_CONTAINS(getFromSocket("/activeworkspace"), "ID 1 (1)");
OK(getFromSocket("/dispatch workspace previous"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'previous' })"));
EXPECT_CONTAINS(getFromSocket("/activeworkspace"), "ID 5 (5)");
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
}
// kill all
+36 -36
View File
@@ -23,7 +23,7 @@ static bool test() {
// test on workspace "window"
NLog::log("{}Dispatching workspace `groups`", Colors::YELLOW);
getFromSocket("/dispatch workspace name:groups");
getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:groups' })");
NLog::log("{}Testing movewindoworgroup from group to group", Colors::YELLOW);
auto kittyA = Tests::spawnKitty("kittyA");
@@ -47,10 +47,10 @@ static bool test() {
return false;
}
OK(getFromSocket("/dispatch focuswindow class:kittyB"));
OK(getFromSocket("/dispatch togglegroup"));
OK(getFromSocket("/dispatch focuswindow class:kittyA"));
OK(getFromSocket("/dispatch togglegroup"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kittyB' })"));
OK(getFromSocket("/dispatch hl.dsp.group.toggle()"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kittyA' })"));
OK(getFromSocket("/dispatch hl.dsp.group.toggle()"));
NLog::log("{}Check kittyB dimensions", Colors::YELLOW);
{
@@ -72,7 +72,7 @@ static bool test() {
EXPECT_COUNT_STRING(str, "fullscreen: 0", 1);
}
OK(getFromSocket("/dispatch movewindoworgroup r"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ direction = 'right', group_aware = true })"));
NLog::log("{}Check that dimensions remain the same after move", Colors::YELLOW);
{
auto str = getFromSocket("/activewindow");
@@ -105,8 +105,8 @@ static bool test() {
// group the kitty
NLog::log("{}Enable group and groupbar", Colors::YELLOW);
OK(getFromSocket("/dispatch togglegroup"));
OK(getFromSocket("/keyword group:groupbar:enabled 1"));
OK(getFromSocket("/dispatch hl.dsp.group.toggle()"));
OK(getFromSocket("/eval hl.config({ group = { groupbar = { enabled = 1 } } })"));
// check the height of the window now
NLog::log("{}Recheck kitty dimensions", Colors::YELLOW);
@@ -118,7 +118,7 @@ static bool test() {
// disable the groupbar for ease of testing for now
NLog::log("{}Disable groupbar", Colors::YELLOW);
OK(getFromSocket("r/keyword group:groupbar:enabled 0"));
OK(getFromSocket("r/eval hl.config({ group = { groupbar = { enabled = 0 } } })"));
// kill all
NLog::log("{}Kill windows", Colors::YELLOW);
@@ -132,7 +132,7 @@ static bool test() {
}
NLog::log("{}Group kitty", Colors::YELLOW);
OK(getFromSocket("/dispatch togglegroup"));
OK(getFromSocket("/dispatch hl.dsp.group.toggle()"));
// check the height of the window now
NLog::log("{}Check kitty dimensions 2", Colors::YELLOW);
@@ -166,7 +166,7 @@ static bool test() {
// test cycling through
NLog::log("{}Test cycling through grouped windows", Colors::YELLOW);
OK(getFromSocket("/dispatch changegroupactive f"));
OK(getFromSocket("/dispatch hl.dsp.group.next()"));
try {
auto str = getFromSocket("/activewindow");
@@ -176,7 +176,7 @@ static bool test() {
ret = 1;
}
getFromSocket("/dispatch changegroupactive f");
getFromSocket("/dispatch hl.dsp.group.next()");
try {
auto str = getFromSocket("/activewindow");
@@ -191,7 +191,7 @@ static bool test() {
try {
auto str = getFromSocket("/activewindow");
auto activeBeforeMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
OK(getFromSocket("/dispatch movegroupwindow f"));
OK(getFromSocket("/dispatch hl.dsp.group.move_window({ forward = true })"));
str = getFromSocket("/activewindow");
auto activeAfterMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
EXPECT(activeAfterMove, activeBeforeMove);
@@ -205,7 +205,7 @@ static bool test() {
try {
auto str = getFromSocket("/activewindow");
auto activeBeforeMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
OK(getFromSocket("/dispatch movegroupwindow b"));
OK(getFromSocket("/dispatch hl.dsp.group.move_window({ forward = false })"));
str = getFromSocket("/activewindow");
auto activeAfterMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
EXPECT(activeAfterMove, activeBeforeMove);
@@ -215,7 +215,7 @@ static bool test() {
}
NLog::log("{}Disable autogrouping", Colors::YELLOW);
OK(getFromSocket("/keyword group:auto_group false"));
OK(getFromSocket("/eval hl.config({ group = { auto_group = false } })"));
NLog::log("{}Spawn kittyProcC", Colors::YELLOW);
auto kittyProcC = Tests::spawnKitty();
@@ -231,10 +231,10 @@ static bool test() {
EXPECT_COUNT_STRING(str, "at: 22,22", 2);
}
OK(getFromSocket("/dispatch movefocus l"));
OK(getFromSocket("/dispatch changegroupactive 1"));
OK(getFromSocket("/keyword group:auto_group true"));
OK(getFromSocket("/keyword group:insert_after_current false"));
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'left' })"));
OK(getFromSocket("/dispatch hl.dsp.group.active({ index = 1 })"));
OK(getFromSocket("/eval hl.config({ group = { auto_group = true } })"));
OK(getFromSocket("/eval hl.config({ group = { insert_after_current = false } })"));
NLog::log("{}Spawn kittyProcD", Colors::YELLOW);
auto kittyProcD = Tests::spawnKitty();
@@ -246,7 +246,7 @@ static bool test() {
NLog::log("{}Expecting 4 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 4);
OK(getFromSocket("/dispatch changegroupactive 3"));
OK(getFromSocket("/dispatch hl.dsp.group.active({ index = 3 })"));
{
auto str = getFromSocket("/activewindow");
@@ -262,7 +262,7 @@ static bool test() {
// test movewindoworgroup: direction should be respected when extracting from group
NLog::log("{}Test movewindoworgroup respects direction out of group", Colors::YELLOW);
OK(getFromSocket("/keyword group:groupbar:enabled 0"));
OK(getFromSocket("/eval hl.config({ group = { groupbar = { enabled = 0 } } })"));
{
auto kittyE = Tests::spawnKitty();
if (!kittyE) {
@@ -271,7 +271,7 @@ static bool test() {
}
// group kitty, and new windows should be auto-grouped
OK(getFromSocket("/dispatch togglegroup"));
OK(getFromSocket("/dispatch hl.dsp.group.toggle()"));
auto kittyF = Tests::spawnKitty();
if (!kittyF) {
@@ -288,7 +288,7 @@ static bool test() {
// move active window out of group to the right
NLog::log("{}Test movewindoworgroup r", Colors::YELLOW);
OK(getFromSocket("/dispatch movewindoworgroup r"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ direction = 'right', group_aware = true })"));
// the group should stay at x=22, the extracted window should be to the right
{
@@ -297,11 +297,11 @@ static bool test() {
}
// move it back into the group
OK(getFromSocket("/dispatch moveintogroup l"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ into_group = 'left' })"));
// move active window out of group downward
NLog::log("{}Test movewindoworgroup d", Colors::YELLOW);
OK(getFromSocket("/dispatch movewindoworgroup d"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ direction = 'down', group_aware = true })"));
// the group should stay at y=22, the extracted window should be below
{
@@ -316,17 +316,17 @@ static bool test() {
// test that we deny a floated window getting auto-grouped into a tiled group.
NLog::log("{}Test that we deny a floated window getting auto-grouped into a tiled group.", Colors::GREEN);
OK(getFromSocket("/keyword windowrule[kitty-tiled]:match:class kitty_tiled"));
OK(getFromSocket("/keyword windowrule[kitty-tiled]:tile yes"));
OK(getFromSocket("/eval hl.window_rule({ name = 'kitty-tiled', match = { class = 'kitty_tiled' } })"));
OK(getFromSocket("/eval hl.window_rule({ name = 'kitty-tiled', tile = true })"));
auto kittyProcE = Tests::spawnKitty("kitty_tiled");
if (!kittyProcE) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
OK(getFromSocket("/dispatch togglegroup"));
OK(getFromSocket("/dispatch hl.dsp.group.toggle()"));
OK(getFromSocket("/keyword windowrule[kitty-floated]:match:class kitty_floated"));
OK(getFromSocket("/keyword windowrule[kitty-floated]:float yes"));
OK(getFromSocket("/eval hl.window_rule({ name = 'kitty-floated', match = { class = 'kitty_floated' } })"));
OK(getFromSocket("/eval hl.window_rule({ name = 'kitty-floated', float = true })"));
auto kittyProcF = Tests::spawnKitty("kitty_floated");
if (!kittyProcF) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
@@ -366,7 +366,7 @@ static bool test() {
NLog::log("{}Error: unlocked kitty did not spawn", Colors::RED);
return false;
}
OK(getFromSocket("/dispatch togglegroup"));
OK(getFromSocket("/dispatch hl.dsp.group.toggle()"));
auto winB = Tests::spawnKitty("top");
if (!winB) {
@@ -391,9 +391,9 @@ static bool test() {
NLog::log("{}Error: locked kitty did not spawn", Colors::RED);
return false;
}
OK(getFromSocket("/dispatch togglegroup"));
OK(getFromSocket(std::format("/dispatch focuswindow pid:{}", lockedWin->pid())));
OK(getFromSocket("/dispatch lockactivegroup lock"));
OK(getFromSocket("/dispatch hl.dsp.group.toggle()"));
OK(getFromSocket(std::format("/dispatch hl.dsp.focus({{ window = 'pid:{}' }})", lockedWin->pid())));
OK(getFromSocket("/dispatch hl.dsp.group.lock_active({ action = 'set' })"));
auto winB = Tests::spawnKitty("top");
if (!winB) {
@@ -413,8 +413,8 @@ static bool test() {
// Test locked groups WITH invade rule
{
OK(getFromSocket("/keyword windowrule[locked-im]:match:class ^locked|invade$"));
OK(getFromSocket("/keyword windowrule[locked-im]:group set always lock invade"));
OK(getFromSocket("/eval hl.window_rule({ name = 'locked-im', match = { class = '^locked|invade$' } })"));
OK(getFromSocket("/eval hl.window_rule({ name = 'locked-im', group = 'set always lock invade' })"));
auto lockedWin = Tests::spawnKitty("locked");
if (!lockedWin) {
+22 -18
View File
@@ -31,11 +31,15 @@ static std::string getCommandStdOut(std::string command) {
return stdOut.substr(0, stdOut.length() - 1);
}
static void setWindowProp(const std::string& selector, const std::string& prop, const std::string& value) {
getFromSocket("/dispatch hl.dsp.window.set_prop({ window = '" + selector + "', prop = '" + prop + "', value = '" + value + "' })");
}
static bool testDevicesActiveLayoutIndex() {
NLog::log("{}Testing hyprctl devices active_layout_index", Colors::GREEN);
// configure layouts
getFromSocket("/keyword input:kb_layout us,pl,ua");
OK(getFromSocket("r/eval hl.config({ input = { kb_layout = \"us,pl,ua\" } })"));
for (uint8_t i = 0; i < 3; i++) {
// set layout
@@ -59,101 +63,101 @@ static bool testGetprop() {
// animation
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation"), "(unset)");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation -j"), R"({"animation": ""})");
getFromSocket("/dispatch setprop class:kitty animation teststyle");
setWindowProp("class:kitty", "animation", "teststyle");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation"), "teststyle");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation -j"), R"({"animation": "teststyle"})");
// max_size
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "inf inf");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [null,null]})");
getFromSocket("/dispatch setprop class:kitty max_size 200 150");
setWindowProp("class:kitty", "max_size", "200 150");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "200 150");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [200,150]})");
// min_size
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "20 20");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [20,20]})");
getFromSocket("/dispatch setprop class:kitty min_size 100 50");
setWindowProp("class:kitty", "min_size", "100 50");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "100 50");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [100,50]})");
// expr-based min/max _size
getFromSocket("/dispatch setfloating class:kitty"); // need to set floating for tests below
getFromSocket("/dispatch setprop class:kitty max_size 90+10 25*2"); // set max to the same as min above, forcing window to 100*50
getFromSocket("/dispatch hl.dsp.window.float({ action = 'set', window = 'class:kitty' })"); // need to set floating for tests below
setWindowProp("class:kitty", "max_size", "90+10 25*2"); // set max to the same as min above, forcing window to 100*50
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "100 50");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [100,50]})");
getFromSocket("/dispatch setprop class:kitty min_size window_w*0.5 window_h-10");
setWindowProp("class:kitty", "min_size", "window_w*0.5 window_h-10");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "50 40");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [50,40]})");
getFromSocket("/dispatch settiled class:kitty"); // go back to tiled for consistency
getFromSocket("/dispatch hl.dsp.window.float({ action = 'unset', window = 'class:kitty' })"); // go back to tiled for consistency
// opacity
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "1");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 1})");
getFromSocket("/dispatch setprop class:kitty opacity 0.3");
setWindowProp("class:kitty", "opacity", "0.3");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "0.3");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 0.3})");
// opacity_inactive
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive"), "1");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive -j"), R"({"opacity_inactive": 1})");
getFromSocket("/dispatch setprop class:kitty opacity_inactive 0.5");
setWindowProp("class:kitty", "opacity_inactive", "0.5");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive"), "0.5");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive -j"), R"({"opacity_inactive": 0.5})");
// opacity_fullscreen
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen"), "1");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen -j"), R"({"opacity_fullscreen": 1})");
getFromSocket("/dispatch setprop class:kitty opacity_fullscreen 0.75");
setWindowProp("class:kitty", "opacity_fullscreen", "0.75");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen"), "0.75");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen -j"), R"({"opacity_fullscreen": 0.75})");
// opacity_override
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override -j"), R"({"opacity_override": false})");
getFromSocket("/dispatch setprop class:kitty opacity_override true");
setWindowProp("class:kitty", "opacity_override", "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override -j"), R"({"opacity_override": true})");
// opacity_inactive_override
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override -j"), R"({"opacity_inactive_override": false})");
getFromSocket("/dispatch setprop class:kitty opacity_inactive_override true");
setWindowProp("class:kitty", "opacity_inactive_override", "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override -j"), R"({"opacity_inactive_override": true})");
// opacity_fullscreen_override
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override -j"), R"({"opacity_fullscreen_override": false})");
getFromSocket("/dispatch setprop class:kitty opacity_fullscreen_override true");
setWindowProp("class:kitty", "opacity_fullscreen_override", "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override -j"), R"({"opacity_fullscreen_override": true})");
// active_border_color
EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color"), "ee33ccff ee00ff99 45deg");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color -j"), R"({"active_border_color": "ee33ccff ee00ff99 45deg"})");
getFromSocket("/dispatch setprop class:kitty active_border_color rgb(abcdef)");
setWindowProp("class:kitty", "active_border_color", "rgb(abcdef)");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color"), "ffabcdef 0deg");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color -j"), R"({"active_border_color": "ffabcdef 0deg"})");
// bool window properties
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input -j"), R"({"allows_input": false})");
getFromSocket("/dispatch setprop class:kitty allows_input true");
setWindowProp("class:kitty", "allows_input", "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input -j"), R"({"allows_input": true})");
// int window properties
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding"), "10");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding -j"), R"({"rounding": 10})");
getFromSocket("/dispatch setprop class:kitty rounding 4");
setWindowProp("class:kitty", "rounding", "4");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding"), "4");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding -j"), R"({"rounding": 4})");
// float window properties
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power"), "2");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power -j"), R"({"rounding_power": 2})");
getFromSocket("/dispatch setprop class:kitty rounding_power 1.25");
setWindowProp("class:kitty", "rounding_power", "1.25");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power"), "1.25");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power -j"), R"({"rounding_power": 1.25})");
+137 -127
View File
@@ -12,6 +12,14 @@ using namespace Hyprutils::Memory;
static int ret = 0;
static std::string flagFile = "/tmp/hyprtester-keybinds.txt";
static std::string pluginKeybindCmd(bool pressed, uint32_t modifier, uint32_t key) {
return "/eval hl.plugin.test.keybind(" + std::to_string(pressed ? 1 : 0) + ", " + std::to_string(modifier) + ", " + std::to_string(key) + ")";
}
static std::string pluginScrollCmd(int delta) {
return "/eval hl.plugin.test.scroll(" + std::to_string(delta) + ")";
}
// Because i don't feel like changing someone elses code.
enum eKeyboardModifierIndex : uint8_t {
MOD_SHIFT = 1,
@@ -82,34 +90,34 @@ static CUniquePointer<CProcess> spawnRemoteControlKitty() {
static void testBind() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword bind SUPER,Y,exec,touch " + flagFile), "ok");
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'))"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// await flag
EXPECT(attemptCheckFlag(20, 50), true);
// release keybind
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
}
static void testBindKey() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword bind ,Y,exec,touch " + flagFile), "ok");
EXPECT(getFromSocket("/eval hl.bind('Y', hl.dsp.exec_cmd('touch " + flagFile + "'))"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,0,29"));
OK(getFromSocket(pluginKeybindCmd(true, 0, 29)));
// await flag
EXPECT(attemptCheckFlag(20, 50), true);
// release keybind
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
EXPECT(getFromSocket("/keyword unbind ,Y"), "ok");
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('', 'Y')"), "ok");
}
static void testLongPress() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword bindo SUPER,Y,exec,touch " + flagFile), "ok");
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { long_press = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// check no flag on short press
std::this_thread::sleep_for(std::chrono::milliseconds(50));
EXPECT(checkFlag(), false);
@@ -117,16 +125,16 @@ static void testLongPress() {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), true);
// release keybind
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
}
static void testKeyLongPress() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword bindo ,Y,exec,touch " + flagFile), "ok");
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
EXPECT(getFromSocket("/eval hl.bind('Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { long_press = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,0,29"));
OK(getFromSocket(pluginKeybindCmd(true, 0, 29)));
// check no flag on short press
std::this_thread::sleep_for(std::chrono::milliseconds(50));
EXPECT(checkFlag(), false);
@@ -134,51 +142,51 @@ static void testKeyLongPress() {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), true);
// release keybind
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
EXPECT(getFromSocket("/keyword unbind ,Y"), "ok");
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('', 'Y')"), "ok");
}
static void testLongPressRelease() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword bindo SUPER,Y,exec,touch " + flagFile), "ok");
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { long_press = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// check no flag on short press
std::this_thread::sleep_for(std::chrono::milliseconds(50));
EXPECT(checkFlag(), false);
// release keybind
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
}
static void testLongPressOnlyKeyRelease() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword bindo SUPER,Y,exec,touch " + flagFile), "ok");
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { long_press = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// check no flag on short press
std::this_thread::sleep_for(std::chrono::milliseconds(50));
EXPECT(checkFlag(), false);
// release key, keep modifier
OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29"));
OK(getFromSocket(pluginKeybindCmd(false, 7, 29)));
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), false);
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
}
static void testRepeat() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword binde SUPER,Y,exec,touch " + flagFile), "ok");
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { repeating = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// await flag
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), true);
@@ -189,16 +197,16 @@ static void testRepeat() {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), true);
// release keybind
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
}
static void testKeyRepeat() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword binde ,Y,exec,touch " + flagFile), "ok");
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
EXPECT(getFromSocket("/eval hl.bind('Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { repeating = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,0,29"));
OK(getFromSocket(pluginKeybindCmd(true, 0, 29)));
// await flag
std::this_thread::sleep_for(std::chrono::milliseconds(50));
EXPECT(checkFlag(), true);
@@ -209,8 +217,8 @@ static void testKeyRepeat() {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), true);
// release keybind
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
EXPECT(getFromSocket("/keyword unbind ,Y"), "ok");
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('', 'Y')"), "ok");
}
static void testRepeatRelease() {
@@ -225,15 +233,15 @@ static void testRepeatRelease() {
}
EXPECT(ok, true);
EXPECT(getFromSocket("/keyword binde SUPER,Y,exec,touch " + flagFile), "ok");
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { repeating = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// await flag
std::this_thread::sleep_for(std::chrono::milliseconds(50));
EXPECT(checkFlag(), true);
// release keybind
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
clearFlag();
@@ -242,20 +250,20 @@ static void testRepeatRelease() {
// check that it is not repeating
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
}
static void testRepeatOnlyKeyRelease() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword binde SUPER,Y,exec,touch " + flagFile), "ok");
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { repeating = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// await flag
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), true);
// release key, keep modifier
OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29"));
OK(getFromSocket(pluginKeybindCmd(false, 7, 29)));
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
clearFlag();
@@ -264,8 +272,8 @@ static void testRepeatOnlyKeyRelease() {
// check that it is not repeating
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), false);
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
}
static void testShortcutBind() {
@@ -275,18 +283,18 @@ static void testShortcutBind() {
ret = 1;
return;
}
EXPECT(getFromSocket("/dispatch focuswindow class:keybinds_test"), "ok");
EXPECT(getFromSocket("/keyword bind SUPER,Y,sendshortcut,,q,"), "ok");
EXPECT(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:keybinds_test' })"), "ok");
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.send_shortcut({ mods = '', key = 'q', window = 'activewindow' }))"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// release keybind
std::this_thread::sleep_for(std::chrono::milliseconds(50));
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
std::this_thread::sleep_for(std::chrono::milliseconds(50));
const std::string output = readKittyOutput();
EXPECT_COUNT_STRING(output, "y", 0);
EXPECT_COUNT_STRING(output, "q", 1);
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
Tests::killAllWindows();
}
@@ -297,19 +305,19 @@ static void testShortcutBindKey() {
ret = 1;
return;
}
EXPECT(getFromSocket("/dispatch focuswindow class:keybinds_test"), "ok");
EXPECT(getFromSocket("/keyword bind ,Y,sendshortcut,,q,"), "ok");
EXPECT(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:keybinds_test' })"), "ok");
EXPECT(getFromSocket("/eval hl.bind('Y', hl.dsp.send_shortcut({ mods = '', key = 'q', window = 'activewindow' }))"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,0,29"));
OK(getFromSocket(pluginKeybindCmd(true, 0, 29)));
// release keybind
std::this_thread::sleep_for(std::chrono::milliseconds(50));
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
std::this_thread::sleep_for(std::chrono::milliseconds(50));
const std::string output = readKittyOutput();
EXPECT_COUNT_STRING(output, "y", 0);
// disabled: doesn't work in CI
// EXPECT_COUNT_STRING(output, "q", 1);
EXPECT(getFromSocket("/keyword unbind ,Y"), "ok");
EXPECT(getFromSocket("/eval hl.unbind('', 'Y')"), "ok");
Tests::killAllWindows();
}
@@ -320,15 +328,15 @@ static void testShortcutLongPress() {
ret = 1;
return;
}
EXPECT(getFromSocket("/dispatch focuswindow class:keybinds_test"), "ok");
EXPECT(getFromSocket("/keyword bindo SUPER,Y,sendshortcut,,q,"), "ok");
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
EXPECT(getFromSocket("/keyword input:repeat_rate 10"), "ok");
EXPECT(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:keybinds_test' })"), "ok");
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.send_shortcut({ mods = '', key = 'q', window = 'activewindow' }), { long_press = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_rate = 10 } })"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
std::this_thread::sleep_for(std::chrono::milliseconds(200));
const std::string output = readKittyOutput();
int yCount = Tests::countOccurrences(output, "y");
@@ -338,7 +346,7 @@ static void testShortcutLongPress() {
// final release stop repeats, and shouldn't send any more
EXPECT(true, yCount == 1 || yCount == 2);
EXPECT_COUNT_STRING(output, "q", 1);
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
Tests::killAllWindows();
}
@@ -349,23 +357,23 @@ static void testShortcutLongPressKeyRelease() {
ret = 1;
return;
}
EXPECT(getFromSocket("/dispatch focuswindow class:keybinds_test"), "ok");
EXPECT(getFromSocket("/keyword bindo SUPER,Y,sendshortcut,,q,"), "ok");
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
EXPECT(getFromSocket("/keyword input:repeat_rate 10"), "ok");
EXPECT(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:keybinds_test' })"), "ok");
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.send_shortcut({ mods = '', key = 'q', window = 'activewindow' }), { long_press = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_rate = 10 } })"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
std::this_thread::sleep_for(std::chrono::milliseconds(10));
// release key, keep modifier
OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29"));
OK(getFromSocket(pluginKeybindCmd(false, 7, 29)));
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
const std::string output = readKittyOutput();
// disabled: doesn't work on CI
// EXPECT_COUNT_STRING(output, "y", 1);
EXPECT_COUNT_STRING(output, "q", 0);
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
Tests::killAllWindows();
}
@@ -376,16 +384,16 @@ static void testShortcutRepeat() {
ret = 1;
return;
}
EXPECT(getFromSocket("/dispatch focuswindow class:keybinds_test"), "ok");
EXPECT(getFromSocket("/keyword binde SUPER,Y,sendshortcut,,q,"), "ok");
EXPECT(getFromSocket("/keyword input:repeat_rate 5"), "ok");
EXPECT(getFromSocket("/keyword input:repeat_delay 200"), "ok");
EXPECT(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:keybinds_test' })"), "ok");
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.send_shortcut({ mods = '', key = 'q', window = 'activewindow' }), { repeating = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_rate = 5 } })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 200 } })"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// await repeat
std::this_thread::sleep_for(std::chrono::milliseconds(210));
// release keybind
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
std::this_thread::sleep_for(std::chrono::milliseconds(450));
const std::string output = readKittyOutput();
EXPECT_COUNT_STRING(output, "y", 0);
@@ -395,7 +403,7 @@ static void testShortcutRepeat() {
// then repeat triggers, sending 1 q
// final release stop repeats, and shouldn't send any more
EXPECT(true, qCount == 2 || qCount == 3);
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
Tests::killAllWindows();
}
@@ -406,15 +414,15 @@ static void testShortcutRepeatKeyRelease() {
ret = 1;
return;
}
EXPECT(getFromSocket("/dispatch focuswindow class:keybinds_test"), "ok");
EXPECT(getFromSocket("/keyword binde SUPER,Y,sendshortcut,,q,"), "ok");
EXPECT(getFromSocket("/keyword input:repeat_rate 5"), "ok");
EXPECT(getFromSocket("/keyword input:repeat_delay 200"), "ok");
EXPECT(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:keybinds_test' })"), "ok");
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.send_shortcut({ mods = '', key = 'q', window = 'activewindow' }), { repeating = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_rate = 5 } })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 200 } })"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
std::this_thread::sleep_for(std::chrono::milliseconds(210));
// release key, keep modifier
OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29"));
OK(getFromSocket(pluginKeybindCmd(false, 7, 29)));
// if repeat was still active, we'd get 2 more q's here
std::this_thread::sleep_for(std::chrono::milliseconds(450));
// release modifier
@@ -426,16 +434,16 @@ static void testShortcutRepeatKeyRelease() {
// then repeat triggers, sending 1 q
// final release stop repeats, and shouldn't send any more
EXPECT(true, qCount == 2 || qCount == 3);
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
Tests::killAllWindows();
}
static void testSubmap() {
const auto press = [](const uint32_t key, const uint32_t mod = 0) {
// +8 because udev -> XKB keycode.
getFromSocket("/dispatch plugin:test:keybind 1," + std::to_string(mod) + "," + std::to_string(key + 8));
getFromSocket("/dispatch plugin:test:keybind 0," + std::to_string(mod) + "," + std::to_string(key + 8));
getFromSocket(pluginKeybindCmd(true, mod, key + 8));
getFromSocket(pluginKeybindCmd(false, mod, key + 8));
};
NLog::log("{}Testing submaps", Colors::GREEN);
@@ -470,57 +478,57 @@ static void testBindsAfterScroll() {
NLog::log("{}Testing binds after scroll", Colors::GREEN);
clearFlag();
OK(getFromSocket("/keyword binds Alt_R,w,exec,touch " + flagFile));
OK(getFromSocket("/eval hl.bind('ALT + w', hl.dsp.exec_cmd('touch " + flagFile + "'))"));
// press keybind before scroll
OK(getFromSocket("/dispatch plugin:test:keybind 1,0,108")); // Alt_R press
OK(getFromSocket("/dispatch plugin:test:keybind 1,4,25")); // w press
OK(getFromSocket(pluginKeybindCmd(true, 0, 108))); // Alt_R press
OK(getFromSocket(pluginKeybindCmd(true, 4, 25))); // w press
EXPECT(attemptCheckFlag(20, 50), true);
OK(getFromSocket("/dispatch plugin:test:keybind 0,4,25")); // w release
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,108")); // Alt_R release
OK(getFromSocket(pluginKeybindCmd(false, 4, 25))); // w release
OK(getFromSocket(pluginKeybindCmd(false, 0, 108))); // Alt_R release
// scroll
OK(getFromSocket("/dispatch plugin:test:scroll 120"));
OK(getFromSocket("/dispatch plugin:test:scroll -120"));
OK(getFromSocket("/dispatch plugin:test:scroll 120"));
OK(getFromSocket(pluginScrollCmd(120)));
OK(getFromSocket(pluginScrollCmd(-120)));
OK(getFromSocket(pluginScrollCmd(120)));
// press keybind after scroll
OK(getFromSocket("/dispatch plugin:test:keybind 1,0,108")); // Alt_R press
OK(getFromSocket("/dispatch plugin:test:keybind 1,4,25")); // w press
OK(getFromSocket(pluginKeybindCmd(true, 0, 108))); // Alt_R press
OK(getFromSocket(pluginKeybindCmd(true, 4, 25))); // w press
EXPECT(attemptCheckFlag(20, 50), true);
OK(getFromSocket("/dispatch plugin:test:keybind 0,4,25")); // w release
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,108")); // Alt_R release
OK(getFromSocket(pluginKeybindCmd(false, 4, 25))); // w release
OK(getFromSocket(pluginKeybindCmd(false, 0, 108))); // Alt_R release
clearFlag();
OK(getFromSocket("/keyword unbind Alt_R,w"));
OK(getFromSocket("/eval hl.unbind('ALT', 'w')"));
}
static void testSubmapUniversal() {
NLog::log("{}Testing submap universal", Colors::GREEN);
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword bindu SUPER,Y,exec,touch " + flagFile), "ok");
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { submap_universal = true })"), "ok");
EXPECT_CONTAINS(getFromSocket("/submap"), "default");
// keybind works on default submap
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29"));
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
OK(getFromSocket(pluginKeybindCmd(false, 7, 29)));
EXPECT(attemptCheckFlag(30, 5), true);
// keybind works on submap1
getFromSocket("/dispatch plugin:test:keybind 1,7,30");
getFromSocket("/dispatch plugin:test:keybind 0,7,30");
getFromSocket(pluginKeybindCmd(true, 7, 30));
getFromSocket(pluginKeybindCmd(false, 7, 30));
EXPECT_CONTAINS(getFromSocket("/submap"), "submap1");
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29"));
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
OK(getFromSocket(pluginKeybindCmd(false, 7, 29)));
EXPECT(attemptCheckFlag(30, 5), true);
// reset to default submap
getFromSocket("/dispatch plugin:test:keybind 1,0,33");
getFromSocket("/dispatch plugin:test:keybind 0,0,33");
getFromSocket(pluginKeybindCmd(true, 0, 33));
getFromSocket(pluginKeybindCmd(false, 0, 33));
EXPECT_CONTAINS(getFromSocket("/submap"), "default");
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
}
static void testPerDeviceKeybind() {
@@ -528,27 +536,29 @@ static void testPerDeviceKeybind() {
// Inclusive
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword bindk SUPER,Y,test-keyboard-1,exec,touch " + flagFile), "ok");
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { device = { inclusive = true, list = { 'test-keyboard-1' } } })"), "ok");
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
EXPECT(attemptCheckFlag(20, 50), true);
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
// Exclusive
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword bindk SUPER,Y,!test-keyboard-1,exec,touch " + flagFile), "ok");
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { device = { inclusive = false, list = { 'test-keyboard-1' } } })"), "ok");
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
EXPECT(attemptCheckFlag(20, 50), false);
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
// With description
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword binddk SUPER,Y,test-keyboard-1,test description,exec,touch " + flagFile), "ok");
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile +
"'), { description = 'test description', device = { inclusive = true, list = { 'test-keyboard-1' } } })"),
"ok");
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
EXPECT(attemptCheckFlag(20, 50), true);
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
}
static bool test() {
+3 -3
View File
@@ -26,10 +26,10 @@ static bool test() {
if (!spawnLayer("rule-layer"))
return false;
OK(getFromSocket("/dispatch plugin:test:add_layer_rule"));
OK(getFromSocket("/eval hl.plugin.test.add_layer_rule()"));
OK(getFromSocket("/reload"));
OK(getFromSocket("/keyword layerrule match:namespace rule-layer, plugin_rule effect"));
OK(getFromSocket("/eval hl.layer_rule({ match = { namespace = 'rule-layer' }, plugin_rule = 'effect' })"));
if (!spawnLayer("rule-layer"))
return false;
@@ -37,7 +37,7 @@ static bool test() {
if (!spawnLayer("norule-layer"))
return false;
OK(getFromSocket("/dispatch plugin:test:check_layer_rule"));
OK(getFromSocket("/eval hl.plugin.test.check_layer_rule()"));
OK(getFromSocket("/reload"));
+25 -25
View File
@@ -6,7 +6,7 @@
static int ret = 0;
static void swar() {
OK(getFromSocket("/keyword layout:single_window_aspect_ratio 1 1"));
OK(getFromSocket("/eval hl.config({ layout = { single_window_aspect_ratio = '1 1' } })"));
Tests::spawnKitty();
@@ -18,7 +18,7 @@ static void swar() {
Tests::spawnKitty();
OK(getFromSocket("/dispatch killwindow activewindow"));
OK(getFromSocket("/dispatch hl.dsp.window.kill({ window = 'activewindow' })"));
Tests::waitUntilWindowsN(1);
@@ -29,7 +29,7 @@ static void swar() {
}
// don't use swar on maximized
OK(getFromSocket("/dispatch fullscreen 1"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'maximized' })"));
{
auto str = getFromSocket("/activewindow");
@@ -49,10 +49,10 @@ static void testCrashOnGeomUpdate() {
Tests::spawnKitty();
// move the layout
OK(getFromSocket("/keyword monitor HEADLESS-2,1920x1080@60,1000x0,1"));
OK(getFromSocket("/eval hl.monitor({ output = 'HEADLESS-2', mode = '1920x1080@60', position = '1000x0', scale = '1' })"));
// shouldnt crash
OK(getFromSocket("/dispatch movefocus r"));
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'right' })"));
OK(getFromSocket("/reload"));
@@ -64,9 +64,9 @@ static void testCrashOnGeomUpdate() {
static void testPosPreserve() {
Tests::spawnKitty();
OK(getFromSocket("/dispatch setfloating class:kitty"));
OK(getFromSocket("/dispatch resizewindowpixel exact 1337 69, class:kitty"));
OK(getFromSocket("/dispatch movewindowpixel exact 420 420, class:kitty"));
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set', window = 'class:kitty' })"));
OK(getFromSocket("/dispatch hl.dsp.window.resize({ x = 1337, y = 69, window = 'class:kitty' })"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ x = 420, y = 420, window = 'class:kitty' })"));
{
auto str = getFromSocket("/activewindow");
@@ -74,15 +74,15 @@ static void testPosPreserve() {
EXPECT_CONTAINS(str, "size: 1337,69");
}
OK(getFromSocket("/dispatch fullscreen"));
OK(getFromSocket("/dispatch fullscreen"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen()"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen()"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "size: 1337,69");
}
OK(getFromSocket("/dispatch movewindow r"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ direction = 'right' })"));
{
auto str = getFromSocket("/activewindow");
@@ -90,8 +90,8 @@ static void testPosPreserve() {
EXPECT_CONTAINS(str, "size: 1337,69");
}
OK(getFromSocket("/dispatch fullscreen"));
OK(getFromSocket("/dispatch fullscreen"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen()"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen()"));
{
auto str = getFromSocket("/activewindow");
@@ -107,18 +107,18 @@ static bool testFocusMRUAfterClose() {
NLog::log("{}Testing focus after close (MRU order)", Colors::GREEN);
OK(getFromSocket("/reload"));
OK(getFromSocket("/keyword dwindle:default_split_ratio 1.25"));
OK(getFromSocket("/keyword input:focus_on_close 2"));
OK(getFromSocket("/eval hl.config({ dwindle = { default_split_ratio = 1.25 } })"));
OK(getFromSocket("/eval hl.config({ input = { focus_on_close = 2 } })"));
EXPECT(!!Tests::spawnKitty("kitty_A"), true);
EXPECT(!!Tests::spawnKitty("kitty_B"), true);
EXPECT(!!Tests::spawnKitty("kitty_C"), true);
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
OK(getFromSocket("/dispatch focuswindow class:kitty_C"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_B' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_C' })"));
OK(getFromSocket("/dispatch killactive"));
OK(getFromSocket("/dispatch hl.dsp.window.kill()"));
Tests::waitUntilWindowsN(2);
{
@@ -126,7 +126,7 @@ static bool testFocusMRUAfterClose() {
EXPECT(str.contains("class: kitty_B"), true);
}
OK(getFromSocket("/dispatch killactive"));
OK(getFromSocket("/dispatch hl.dsp.window.kill()"));
Tests::waitUntilWindowsN(1);
{
@@ -143,16 +143,16 @@ static bool testFocusMRUAfterClose() {
static bool testFocusPreservedLayoutChange() {
NLog::log("{}Testing focus is preserved on layout change", Colors::GREEN);
OK(getFromSocket("/keyword general:layout master"));
OK(getFromSocket("r/eval hl.config({ general = { layout = 'master' } })"));
EXPECT(!!Tests::spawnKitty("kitty_A"), true);
EXPECT(!!Tests::spawnKitty("kitty_B"), true);
EXPECT(!!Tests::spawnKitty("kitty_C"), true);
EXPECT(!!Tests::spawnKitty("kitty_D"), true);
OK(getFromSocket("/dispatch focuswindow class:kitty_C"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_C' })"));
OK(getFromSocket("/keyword general:layout monocle"));
OK(getFromSocket("r/eval hl.config({ general = { layout = 'monocle' } })"));
{
auto str = getFromSocket("/activewindow");
@@ -169,7 +169,7 @@ static bool test() {
NLog::log("{}Testing layout generic", Colors::GREEN);
// setup
OK(getFromSocket("/dispatch workspace 10"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '10' })"));
// test
NLog::log("{}Testing `single_window_aspect_ratio`", Colors::GREEN);
@@ -182,7 +182,7 @@ static bool test() {
// clean up
NLog::log("Cleaning up", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
OK(getFromSocket("/reload"));
return !ret;
+20 -20
View File
@@ -7,7 +7,7 @@ static int ret = 0;
// reqs 1 master 3 slaves
static void testOrientations() {
OK(getFromSocket("/keyword master:orientation top"));
OK(getFromSocket("r/eval hl.config({ master = { orientation = 'top' } })"));
// top
{
@@ -19,7 +19,7 @@ static void testOrientations() {
// cycle = top, right, bottom, center, left
// right
OK(getFromSocket("/dispatch layoutmsg orientationnext"));
OK(getFromSocket("/dispatch hl.dsp.layout('orientationnext')"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 873,22");
@@ -27,7 +27,7 @@ static void testOrientations() {
}
// bottom
OK(getFromSocket("/dispatch layoutmsg orientationnext"));
OK(getFromSocket("/dispatch hl.dsp.layout('orientationnext')"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 22,495");
@@ -35,7 +35,7 @@ static void testOrientations() {
}
// center
OK(getFromSocket("/dispatch layoutmsg orientationnext"));
OK(getFromSocket("/dispatch hl.dsp.layout('orientationnext')"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 450,22");
@@ -43,7 +43,7 @@ static void testOrientations() {
}
// left
OK(getFromSocket("/dispatch layoutmsg orientationnext"));
OK(getFromSocket("/dispatch hl.dsp.layout('orientationnext')"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 22,22");
@@ -54,7 +54,7 @@ static void testOrientations() {
static void focusMasterPrevious() {
// setup
NLog::log("{}Spawning 1 master and 3 slave windows", Colors::YELLOW);
// order of windows set according to new_status = master (set in test.conf)
// order of windows set according to new_status = master (set in test.lua)
for (auto const& win : {"slave1", "slave2", "slave3", "master"}) {
if (!Tests::spawnKitty(win)) {
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
@@ -64,30 +64,30 @@ static void focusMasterPrevious() {
}
}
NLog::log("{}Ensuring focus is on master before testing", Colors::YELLOW);
OK(getFromSocket("/dispatch layoutmsg focusmaster master"));
OK(getFromSocket("/dispatch hl.dsp.layout('focusmaster master')"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master");
// test
NLog::log("{}Testing fallback to focusmaster auto", Colors::YELLOW);
OK(getFromSocket("/dispatch layoutmsg focusmaster previous"));
OK(getFromSocket("/dispatch hl.dsp.layout('focusmaster previous')"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: slave1");
NLog::log("{}Testing focusing from slave to master", Colors::YELLOW);
OK(getFromSocket("/dispatch layoutmsg cyclenext noloop"));
OK(getFromSocket("/dispatch hl.dsp.layout('cyclenext noloop')"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: slave2");
OK(getFromSocket("/dispatch layoutmsg focusmaster previous"));
OK(getFromSocket("/dispatch hl.dsp.layout('focusmaster previous')"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master");
NLog::log("{}Testing focusing on previous window", Colors::YELLOW);
OK(getFromSocket("/dispatch layoutmsg focusmaster previous"));
OK(getFromSocket("/dispatch hl.dsp.layout('focusmaster previous')"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: slave2");
NLog::log("{}Testing focusing back to master", Colors::YELLOW);
OK(getFromSocket("/dispatch layoutmsg focusmaster previous"));
OK(getFromSocket("/dispatch hl.dsp.layout('focusmaster previous')"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master");
testOrientations();
@@ -110,8 +110,8 @@ static void testFsBehavior() {
}
}
OK(getFromSocket("/dispatch focuswindow class:master"));
OK(getFromSocket("/dispatch fullscreen 1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:master' })"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'maximized' })"));
{
auto str = getFromSocket("/activewindow");
@@ -120,7 +120,7 @@ static void testFsBehavior() {
EXPECT_CONTAINS(str, "class: master");
}
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 1"));
OK(getFromSocket("/eval hl.config({ misc = { on_focus_under_fullscreen = 1 } })"));
Tests::spawnKitty("new_master");
@@ -132,7 +132,7 @@ static void testFsBehavior() {
EXPECT_CONTAINS(str, "fullscreen: 1");
}
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0"));
OK(getFromSocket("/eval hl.config({ misc = { on_focus_under_fullscreen = 0 } })"));
Tests::spawnKitty("ignored");
@@ -144,7 +144,7 @@ static void testFsBehavior() {
EXPECT_CONTAINS(str, "fullscreen: 1");
}
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2"));
OK(getFromSocket("/eval hl.config({ misc = { on_focus_under_fullscreen = 2 } })"));
Tests::spawnKitty("vaxwashere");
@@ -162,8 +162,8 @@ static bool test() {
NLog::log("{}Testing Master layout", Colors::GREEN);
// setup
OK(getFromSocket("/dispatch workspace name:master"));
OK(getFromSocket("/keyword general:layout master"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:master' })"));
OK(getFromSocket("r/eval hl.config({ general = { layout = 'master' } })"));
// test
NLog::log("{}Testing `focusmaster previous` layoutmsg", Colors::GREEN);
@@ -174,7 +174,7 @@ static bool test() {
// clean up
NLog::log("Cleaning up", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
OK(getFromSocket("/reload"));
return !ret;
+38 -38
View File
@@ -22,12 +22,12 @@ using namespace Hyprutils::Memory;
// static void testAnrDialogs() {
// NLog::log("{}Testing ANR dialogs", Colors::YELLOW);
//
// OK(getFromSocket("/keyword misc:enable_anr_dialog true"));
// OK(getFromSocket("/keyword misc:anr_missed_pings 1"));
// OK(getFromSocket("/eval hl.config({ misc = { enable_anr_dialog = true } })"));
// OK(getFromSocket("/eval hl.config({ misc = { anr_missed_pings = 1 } })"));
//
// NLog::log("{}ANR dialog: regular workspaces", Colors::YELLOW);
// {
// OK(getFromSocket("/dispatch workspace 2"));
// OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '2' })"));
//
// auto kitty = Tests::spawnKitty("bad_kitty");
//
@@ -41,7 +41,7 @@ using namespace Hyprutils::Memory;
// EXPECT_CONTAINS(str, "workspace: 2");
// }
//
// OK(getFromSocket("/dispatch workspace 1"));
// OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
//
// ::kill(kitty->pid(), SIGSTOP);
// Tests::waitUntilWindowsN(2);
@@ -52,7 +52,7 @@ using namespace Hyprutils::Memory;
// }
//
// {
// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog"))
// OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:hyprland-dialog' })"))
// auto str = getFromSocket("/activewindow");
// EXPECT_CONTAINS(str, "workspace: 2");
// }
@@ -62,7 +62,7 @@ using namespace Hyprutils::Memory;
//
// NLog::log("{}ANR dialog: named workspaces", Colors::YELLOW);
// {
// OK(getFromSocket("/dispatch workspace name:yummy"));
// OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:yummy' })"));
//
// auto kitty = Tests::spawnKitty("bad_kitty");
//
@@ -76,7 +76,7 @@ using namespace Hyprutils::Memory;
// EXPECT_CONTAINS(str, "yummy");
// }
//
// OK(getFromSocket("/dispatch workspace 1"));
// OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
//
// ::kill(kitty->pid(), SIGSTOP);
// Tests::waitUntilWindowsN(2);
@@ -87,7 +87,7 @@ using namespace Hyprutils::Memory;
// }
//
// {
// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog"))
// OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:hyprland-dialog' })"))
// auto str = getFromSocket("/activewindow");
// EXPECT_CONTAINS(str, "yummy");
// }
@@ -97,7 +97,7 @@ using namespace Hyprutils::Memory;
//
// NLog::log("{}ANR dialog: special workspaces", Colors::YELLOW);
// {
// OK(getFromSocket("/dispatch workspace special:apple"));
// OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'special:apple' })"));
//
// auto kitty = Tests::spawnKitty("bad_kitty");
//
@@ -111,8 +111,8 @@ using namespace Hyprutils::Memory;
// EXPECT_CONTAINS(str, "special:apple");
// }
//
// OK(getFromSocket("/dispatch togglespecialworkspace apple"));
// OK(getFromSocket("/dispatch workspace 1"));
// OK(getFromSocket("/dispatch hl.dsp.workspace.toggle_special('apple')"));
// OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
//
// ::kill(kitty->pid(), SIGSTOP);
// Tests::waitUntilWindowsN(2);
@@ -123,7 +123,7 @@ using namespace Hyprutils::Memory;
// }
//
// {
// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog"))
// OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:hyprland-dialog' })"))
// auto str = getFromSocket("/activewindow");
// EXPECT_CONTAINS(str, "special:apple");
// }
@@ -138,8 +138,8 @@ static bool test() {
NLog::log("{}Testing close_special_on_empty", Colors::YELLOW);
OK(getFromSocket("/keyword misc:close_special_on_empty false"));
OK(getFromSocket("/dispatch workspace special:test"));
OK(getFromSocket("/eval hl.config({ misc = { close_special_on_empty = false } })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'special:test' })"));
Tests::spawnKitty();
@@ -157,7 +157,7 @@ static bool test() {
Tests::spawnKitty();
OK(getFromSocket("/keyword misc:close_special_on_empty true"));
OK(getFromSocket("/eval hl.config({ misc = { close_special_on_empty = true } })"));
Tests::killAllWindows();
@@ -168,11 +168,11 @@ static bool test() {
NLog::log("{}Testing new_window_takes_over_fullscreen", Colors::YELLOW);
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0"));
OK(getFromSocket("/eval hl.config({ misc = { on_focus_under_fullscreen = 0 } })"));
Tests::spawnKitty("kitty_A");
OK(getFromSocket("/dispatch fullscreen 0"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen' })"));
{
auto str = getFromSocket("/activewindow");
@@ -190,7 +190,7 @@ static bool test() {
EXPECT_CONTAINS(str, "kitty_A");
}
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_B' })"));
{
// should be ignored as per focus_under_fullscreen 0
@@ -200,7 +200,7 @@ static bool test() {
EXPECT_CONTAINS(str, "kitty_A");
}
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 1"));
OK(getFromSocket("/eval hl.config({ misc = { on_focus_under_fullscreen = 1 } })"));
Tests::spawnKitty("kitty_C");
@@ -211,7 +211,7 @@ static bool test() {
EXPECT_CONTAINS(str, "kitty_C");
}
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2"));
OK(getFromSocket("/eval hl.config({ misc = { on_focus_under_fullscreen = 2 } })"));
Tests::spawnKitty("kitty_D");
@@ -222,18 +222,18 @@ static bool test() {
EXPECT_CONTAINS(str, "kitty_D");
}
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0"));
OK(getFromSocket("/eval hl.config({ misc = { on_focus_under_fullscreen = 0 } })"));
Tests::killAllWindows();
NLog::log("{}Testing exit_window_retains_fullscreen", Colors::YELLOW);
OK(getFromSocket("/keyword misc:exit_window_retains_fullscreen false"));
OK(getFromSocket("/eval hl.config({ misc = { exit_window_retains_fullscreen = false } })"));
Tests::spawnKitty("kitty_A");
Tests::spawnKitty("kitty_B");
OK(getFromSocket("/dispatch fullscreen 0"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen' })"));
{
auto str = getFromSocket("/activewindow");
@@ -241,7 +241,7 @@ static bool test() {
EXPECT_CONTAINS(str, "fullscreenClient: 2");
}
OK(getFromSocket("/dispatch killwindow activewindow"));
OK(getFromSocket("/dispatch hl.dsp.window.kill({ window = 'activewindow' })"));
Tests::waitUntilWindowsN(1);
{
@@ -251,10 +251,10 @@ static bool test() {
}
Tests::spawnKitty("kitty_B");
OK(getFromSocket("/dispatch fullscreen 0"));
OK(getFromSocket("/keyword misc:exit_window_retains_fullscreen true"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen' })"));
OK(getFromSocket("/eval hl.config({ misc = { exit_window_retains_fullscreen = true } })"));
OK(getFromSocket("/dispatch killwindow activewindow"));
OK(getFromSocket("/dispatch hl.dsp.window.kill({ window = 'activewindow' })"));
Tests::waitUntilWindowsN(1);
{
@@ -270,8 +270,8 @@ static bool test() {
Tests::spawnKitty("kitty_A");
Tests::spawnKitty("kitty_B");
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch fullscreen 0 set"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'set' })"));
{
auto str = getFromSocket("/activewindow");
@@ -279,7 +279,7 @@ static bool test() {
EXPECT_CONTAINS(str, "fullscreenClient: 2");
}
OK(getFromSocket("/dispatch fullscreen 0 unset"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'unset' })"));
{
auto str = getFromSocket("/activewindow");
@@ -287,7 +287,7 @@ static bool test() {
EXPECT_CONTAINS(str, "fullscreenClient: 0");
}
OK(getFromSocket("/dispatch fullscreen 1 toggle"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'maximized', action = 'toggle' })"));
{
auto str = getFromSocket("/activewindow");
@@ -295,7 +295,7 @@ static bool test() {
EXPECT_CONTAINS(str, "fullscreenClient: 1");
}
OK(getFromSocket("/dispatch fullscreen 1 toggle"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'maximized', action = 'toggle' })"));
{
auto str = getFromSocket("/activewindow");
@@ -303,7 +303,7 @@ static bool test() {
EXPECT_CONTAINS(str, "fullscreenClient: 0");
}
OK(getFromSocket("/dispatch fullscreenstate 3 3 set"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen_state({ internal = 3, client = 3, action = 'set' })"));
{
auto str = getFromSocket("/activewindow");
@@ -311,7 +311,7 @@ static bool test() {
EXPECT_CONTAINS(str, "fullscreenClient: 3");
}
OK(getFromSocket("/dispatch fullscreenstate 3 3 set"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen_state({ internal = 3, client = 3, action = 'set' })"));
{
auto str = getFromSocket("/activewindow");
@@ -319,7 +319,7 @@ static bool test() {
EXPECT_CONTAINS(str, "fullscreenClient: 3");
}
OK(getFromSocket("/dispatch fullscreenstate 2 2 set"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen_state({ internal = 2, client = 2, action = 'set' })"));
{
auto str = getFromSocket("/activewindow");
@@ -327,7 +327,7 @@ static bool test() {
EXPECT_CONTAINS(str, "fullscreenClient: 2");
}
OK(getFromSocket("/dispatch fullscreenstate 2 2 set"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen_state({ internal = 2, client = 2, action = 'set' })"));
{
auto str = getFromSocket("/activewindow");
@@ -335,7 +335,7 @@ static bool test() {
EXPECT_CONTAINS(str, "fullscreenClient: 2");
}
OK(getFromSocket("/dispatch fullscreenstate 2 2 toggle"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen_state({ internal = 2, client = 2, action = 'toggle' })"));
{
auto str = getFromSocket("/activewindow");
@@ -343,7 +343,7 @@ static bool test() {
EXPECT_CONTAINS(str, "fullscreenClient: 0");
}
OK(getFromSocket("/dispatch fullscreenstate 2 2 toggle"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen_state({ internal = 2, client = 2, action = 'toggle' })"));
{
auto str = getFromSocket("/activewindow");
@@ -22,9 +22,9 @@ static bool test() {
NLog::log("{}Testing moveintoorcreategroup", Colors::GREEN);
NLog::log("{}Dispatching workspace `moveintoorcreategroup`", Colors::YELLOW);
getFromSocket("/dispatch workspace name:moveintoorcreategroup");
getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:moveintoorcreategroup' })");
OK(getFromSocket("/keyword group:auto_group false"));
OK(getFromSocket("/eval hl.config({ group = { auto_group = false } })"));
NLog::log("{}Spawning kittyA", Colors::YELLOW);
auto kittyA = Tests::spawnKitty("kitty_A");
@@ -48,10 +48,10 @@ static bool test() {
EXPECT_CONTAINS(str, "grouped: 0");
}
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })"));
NLog::log("{}Move kittyA into group with kittyB (creates group)", Colors::YELLOW);
OK(getFromSocket("/dispatch moveintoorcreategroup r"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ into_or_create_group = 'right' })"));
{
auto str = getFromSocket("/clients");
@@ -82,13 +82,13 @@ static bool test() {
NLog::log("{}Expecting 3 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 3);
OK(getFromSocket("/dispatch focuswindow class:kitty_D"));
OK(getFromSocket("/dispatch togglegroup"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_D' })"));
OK(getFromSocket("/dispatch hl.dsp.group.toggle()"));
OK(getFromSocket("/dispatch focuswindow class:kitty_E"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_E' })"));
NLog::log("{}Move kittyE into existing group with kittyD", Colors::YELLOW);
OK(getFromSocket("/dispatch moveintoorcreategroup l"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ into_or_create_group = 'left' })"));
{
auto str = getFromSocket("/clients");
@@ -103,7 +103,7 @@ static bool test() {
NLog::log("{}Kill windows", Colors::YELLOW);
Tests::killAllWindows();
OK(getFromSocket("/keyword group:auto_group true"));
OK(getFromSocket("/eval hl.config({ group = { auto_group = true } })"));
return !ret;
}
+6 -6
View File
@@ -25,12 +25,12 @@ static bool test() {
// test on workspace "window"
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
getFromSocket("/dispatch workspace 1"); // no OK: we might be on 1 already
getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"); // no OK: we might be on 1 already
OK(getFromSocket("/keyword workspace 5, monitor:HEADLESS-2, persistent:1"));
OK(getFromSocket("/keyword workspace 6, monitor:HEADLESS-PERSISTENT-TEST, persistent:1"));
OK(getFromSocket("/keyword workspace name:PERSIST, monitor:HEADLESS-PERSISTENT-TEST, persistent:1"));
OK(getFromSocket("/keyword workspace name:PERSIST-2, monitor:HEADLESS-PERSISTENT-TEST, persistent:1"));
OK(getFromSocket("/eval hl.workspace_rule({ workspace = '5', monitor = 'HEADLESS-2', persistent = true })"));
OK(getFromSocket("/eval hl.workspace_rule({ workspace = '6', monitor = 'HEADLESS-PERSISTENT-TEST', persistent = true })"));
OK(getFromSocket("/eval hl.workspace_rule({ workspace = 'name:PERSIST', monitor = 'HEADLESS-PERSISTENT-TEST', persistent = true })"));
OK(getFromSocket("/eval hl.workspace_rule({ workspace = 'name:PERSIST-2', monitor = 'HEADLESS-PERSISTENT-TEST', persistent = true })"));
{
auto str = getFromSocket("/workspaces");
@@ -45,7 +45,7 @@ static bool test() {
EXPECT_CONTAINS(str, "HEADLESS-PERSISTENT-TEST");
}
OK(getFromSocket("/dispatch focusmonitor HEADLESS-PERSISTENT-TEST"));
OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-PERSISTENT-TEST' })"));
{
auto str = getFromSocket("/workspaces");
+34 -34
View File
@@ -15,37 +15,37 @@ static void testFocusCycling() {
}
}
OK(getFromSocket("/dispatch focuswindow class:a"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:a' })"));
OK(getFromSocket("/dispatch movefocus r"));
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'right' })"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: b");
}
OK(getFromSocket("/dispatch movefocus r"));
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'right' })"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: c");
}
OK(getFromSocket("/dispatch movefocus r"));
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'right' })"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: d");
}
OK(getFromSocket("/dispatch movewindow l"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ direction = 'left' })"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: d");
}
OK(getFromSocket("/dispatch movefocus u"));
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'up' })"));
{
auto str = getFromSocket("/activewindow");
@@ -68,18 +68,18 @@ static void testFocusWrapping() {
}
// set wrap_focus to true
OK(getFromSocket("/keyword scrolling:wrap_focus true"));
OK(getFromSocket("/eval hl.config({ scrolling = { wrap_focus = true } })"));
OK(getFromSocket("/dispatch focuswindow class:a"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:a' })"));
OK(getFromSocket("/dispatch layoutmsg focus l"));
OK(getFromSocket("/dispatch hl.dsp.layout('focus l')"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: d");
}
OK(getFromSocket("/dispatch layoutmsg focus r"));
OK(getFromSocket("/dispatch hl.dsp.layout('focus r')"));
{
auto str = getFromSocket("/activewindow");
@@ -87,20 +87,20 @@ static void testFocusWrapping() {
}
// set wrap_focus to false
OK(getFromSocket("/keyword scrolling:wrap_focus false"));
OK(getFromSocket("/eval hl.config({ scrolling = { wrap_focus = false } })"));
OK(getFromSocket("/dispatch focuswindow class:a"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:a' })"));
OK(getFromSocket("/dispatch layoutmsg focus l"));
OK(getFromSocket("/dispatch hl.dsp.layout('focus l')"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: a");
}
OK(getFromSocket("/dispatch focuswindow class:d"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:d' })"));
OK(getFromSocket("/dispatch layoutmsg focus r"));
OK(getFromSocket("/dispatch hl.dsp.layout('focus r')"));
{
auto str = getFromSocket("/activewindow");
@@ -123,12 +123,12 @@ static void testSwapcolWrapping() {
}
// set wrap_swapcol to true
OK(getFromSocket("/keyword scrolling:wrap_swapcol true"));
OK(getFromSocket("/eval hl.config({ scrolling = { wrap_swapcol = true } })"));
OK(getFromSocket("/dispatch focuswindow class:a"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:a' })"));
OK(getFromSocket("/dispatch layoutmsg swapcol l"));
OK(getFromSocket("/dispatch layoutmsg focus l"));
OK(getFromSocket("/dispatch hl.dsp.layout('swapcol l')"));
OK(getFromSocket("/dispatch hl.dsp.layout('focus l')"));
{
auto str = getFromSocket("/activewindow");
@@ -148,9 +148,9 @@ static void testSwapcolWrapping() {
}
}
OK(getFromSocket("/dispatch focuswindow class:d"));
OK(getFromSocket("/dispatch layoutmsg swapcol r"));
OK(getFromSocket("/dispatch layoutmsg focus r"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:d' })"));
OK(getFromSocket("/dispatch hl.dsp.layout('swapcol r')"));
OK(getFromSocket("/dispatch hl.dsp.layout('focus r')"));
{
auto str = getFromSocket("/activewindow");
@@ -171,22 +171,22 @@ static void testSwapcolWrapping() {
}
// set wrap_swapcol to false
OK(getFromSocket("/keyword scrolling:wrap_swapcol false"));
OK(getFromSocket("/eval hl.config({ scrolling = { wrap_swapcol = false } })"));
OK(getFromSocket("/dispatch focuswindow class:a"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:a' })"));
OK(getFromSocket("/dispatch layoutmsg swapcol l"));
OK(getFromSocket("/dispatch layoutmsg focus r"));
OK(getFromSocket("/dispatch hl.dsp.layout('swapcol l')"));
OK(getFromSocket("/dispatch hl.dsp.layout('focus r')"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: b");
}
OK(getFromSocket("/dispatch focuswindow class:d"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:d' })"));
OK(getFromSocket("/dispatch layoutmsg swapcol r"));
OK(getFromSocket("/dispatch layoutmsg focus l"));
OK(getFromSocket("/dispatch hl.dsp.layout('swapcol r')"));
OK(getFromSocket("/dispatch hl.dsp.layout('focus l')"));
{
auto str = getFromSocket("/activewindow");
@@ -202,8 +202,8 @@ static bool testWindowRule() {
NLog::log("{}Testing Scrolling Width", Colors::GREEN);
// inject a new rule.
OK(getFromSocket("/keyword windowrule[scrolling-width]:match:class kitty_scroll"));
OK(getFromSocket("/keyword windowrule[scrolling-width]:scrolling_width 0.1"));
OK(getFromSocket("/eval hl.window_rule({ name = 'scrolling-width', match = { class = 'kitty_scroll' } })"));
OK(getFromSocket("/eval hl.window_rule({ name = 'scrolling-width', scrolling_width = 0.1 })"));
if (!Tests::spawnKitty("kitty_scroll")) {
NLog::log("{}Failed to spawn kitty with win class `kitty_scroll`", Colors::RED);
@@ -230,8 +230,8 @@ static bool test() {
NLog::log("{}Testing Scroll layout", Colors::GREEN);
// setup
OK(getFromSocket("/dispatch workspace name:scroll"));
OK(getFromSocket("/keyword general:layout scrolling"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:scroll' })"));
OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })"));
// test
NLog::log("{}Testing focus cycling", Colors::GREEN);
@@ -249,7 +249,7 @@ static bool test() {
// clean up
NLog::log("Cleaning up", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
OK(getFromSocket("/reload"));
return !ret;
+13 -13
View File
@@ -16,8 +16,8 @@ static bool spawnFloatingKitty() {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
OK(getFromSocket("/dispatch setfloating active"));
OK(getFromSocket("/dispatch resizeactive exact 100 100"));
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set' })"));
OK(getFromSocket("/dispatch hl.dsp.window.resize({ x = 100, y = 100 })"));
return true;
}
@@ -40,8 +40,8 @@ static void expectSnapMove(const Vector2D FROM, const Vector2D* TO) {
else
NLog::log("{}Expecting no snap when window is moved to ({},{})", Colors::YELLOW, A.x, A.y);
expectSocket(std::format("/dispatch moveactive exact {} {}", A.x, A.y));
expectSocket("/dispatch plugin:test:snapmove");
expectSocket(std::format("/dispatch hl.dsp.window.move({{ x = {}, y = {} }})", A.x, A.y));
expectSocket("/eval hl.plugin.test.snapmove()");
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("at: {},{}", B.x, B.y));
}
@@ -114,13 +114,13 @@ static bool test() {
// move to monitor HEADLESS-2
NLog::log("{}Moving to monitor HEADLESS-2", Colors::YELLOW);
OK(getFromSocket("/dispatch focusmonitor HEADLESS-2"));
OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-2' })"));
NLog::log("{}Adding reserved monitor area to HEADLESS-2", Colors::YELLOW);
OK(getFromSocket("/keyword monitor HEADLESS-2,addreserved,200,200,200,200"));
OK(getFromSocket("/eval hl.monitor({ output = 'HEADLESS-2', reserved = { top = 200, right = 200, bottom = 200, left = 200 } })"));
// test on workspace "snap"
NLog::log("{}Dispatching workspace `snap`", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace name:snap"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:snap' })"));
// spawn a kitty terminal and move to (500,500)
NLog::log("{}Spawning kittyProcA", Colors::YELLOW);
@@ -131,7 +131,7 @@ static bool test() {
EXPECT(Tests::windowCount(), 1);
NLog::log("{}Move the kitty window to (500,500)", Colors::YELLOW);
OK(getFromSocket("/dispatch moveactive exact 500 500"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ x = 500, y = 500 })"));
// spawn a second kitty terminal
NLog::log("{}Spawning kittyProcB", Colors::YELLOW);
@@ -146,17 +146,17 @@ static bool test() {
testMonitorSnap(false, false);
NLog::log("\n{}Turning on respect_gaps", Colors::YELLOW);
OK(getFromSocket("/keyword general:snap:respect_gaps true"));
OK(getFromSocket("/eval hl.config({ general = { snap = { respect_gaps = true } } })"));
testWindowSnap(true);
testMonitorSnap(true, false);
NLog::log("\n{}Turning on border_overlap", Colors::YELLOW);
OK(getFromSocket("/keyword general:snap:respect_gaps false"));
OK(getFromSocket("/keyword general:snap:border_overlap true"));
OK(getFromSocket("/eval hl.config({ general = { snap = { respect_gaps = false } } })"));
OK(getFromSocket("/eval hl.config({ general = { snap = { border_overlap = true } } })"));
testMonitorSnap(false, true);
NLog::log("\n{}Turning on both border_overlap and respect_gaps", Colors::YELLOW);
OK(getFromSocket("/keyword general:snap:respect_gaps true"));
OK(getFromSocket("/eval hl.config({ general = { snap = { respect_gaps = true } } })"));
testMonitorSnap(true, true);
// kill all
@@ -168,7 +168,7 @@ static bool test() {
NLog::log("{}Reloading the config", Colors::YELLOW);
OK(getFromSocket("/reload"));
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
return !ret;
}
+7 -7
View File
@@ -18,9 +18,9 @@ using namespace Hyprutils::Memory;
static bool test() {
NLog::log("{}Testing solitary clients", Colors::GREEN);
OK(getFromSocket("/keyword general:allow_tearing false"));
OK(getFromSocket("/keyword render:direct_scanout 0"));
OK(getFromSocket("/keyword cursor:no_hardware_cursors 1"));
OK(getFromSocket("/eval hl.config({ general = { allow_tearing = false } })"));
OK(getFromSocket("/eval hl.config({ render = { direct_scanout = 0 } })"));
OK(getFromSocket("/eval hl.config({ cursor = { no_hardware_cursors = 1 } })"));
NLog::log("{}Expecting blocked solitary/DS/tearing", Colors::YELLOW);
{
auto str = getFromSocket("/monitors");
@@ -41,10 +41,10 @@ static bool test() {
// return false;
// }
// OK(getFromSocket("/keyword general:allow_tearing true"));
// OK(getFromSocket("/keyword render:direct_scanout 1"));
// OK(getFromSocket("/eval hl.config({ general = { allow_tearing = true } })"));
// OK(getFromSocket("/eval hl.config({ render = { direct_scanout = 1 } })"));
// NLog::log("{}", getFromSocket("/clients"));
// OK(getFromSocket("/dispatch fullscreen"));
// OK(getFromSocket("/dispatch hl.dsp.window.fullscreen()"));
// NLog::log("{}", getFromSocket("/clients"));
// std::this_thread::sleep_for(std::chrono::milliseconds(100));
// NLog::log("{}Expecting kitty to almost pass for solitary/DS/tearing", Colors::YELLOW);
@@ -56,7 +56,7 @@ static bool test() {
// EXPECT_CONTAINS(str, "tearingBlockedBy: next frame is not torn,not supported by monitor,window settings");
// }
// OK(getFromSocket("/dispatch setprop active immediate 1"));
// OK(getFromSocket("/dispatch hl.dsp.window.set_prop({ window = 'active', prop = 'immediate', value = '1' })"));
// NLog::log("{}Expecting kitty to almost pass for tearing", Colors::YELLOW);
// {
// auto str = getFromSocket("/monitors");
+8 -8
View File
@@ -21,21 +21,21 @@ static bool testTags() {
NLog::log("{}Testing testTag tags", Colors::YELLOW);
OK(getFromSocket("/keyword windowrule[tag-test-1]:tag +testTag"));
OK(getFromSocket("/keyword windowrule[tag-test-1]:match:class tagged"));
OK(getFromSocket("/keyword windowrule[tag-test-2]:match:tag negative:testTag"));
OK(getFromSocket("/keyword windowrule[tag-test-2]:no_shadow true"));
OK(getFromSocket("/keyword windowrule[tag-test-3]:match:tag testTag"));
OK(getFromSocket("/keyword windowrule[tag-test-3]:no_dim true"));
OK(getFromSocket("/eval hl.window_rule({ name = 'tag-test-1', tag = '+testTag' })"));
OK(getFromSocket("/eval hl.window_rule({ name = 'tag-test-1', match = { class = 'tagged' } })"));
OK(getFromSocket("/eval hl.window_rule({ name = 'tag-test-2', match = { tag = 'negative:testTag' } })"));
OK(getFromSocket("/eval hl.window_rule({ name = 'tag-test-2', no_shadow = true })"));
OK(getFromSocket("/eval hl.window_rule({ name = 'tag-test-3', match = { tag = 'testTag' } })"));
OK(getFromSocket("/eval hl.window_rule({ name = 'tag-test-3', no_dim = true })"));
EXPECT(Tests::windowCount(), 2);
OK(getFromSocket("/dispatch focuswindow class:tagged"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:tagged' })"));
NLog::log("{}Testing tagged window for no_dim 0 & no_shadow", Colors::YELLOW);
EXPECT_CONTAINS(getFromSocket("/activewindow"), "testTag");
EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_dim"), "true");
EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_shadow"), "false");
NLog::log("{}Testing untagged window for no_dim & no_shadow", Colors::YELLOW);
OK(getFromSocket("/dispatch focuswindow class:untagged"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:untagged' })"));
EXPECT_NOT_CONTAINS(getFromSocket("/activewindow"), "testTag");
EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_shadow"), "true");
EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_dim"), "false");
+153 -153
View File
@@ -63,7 +63,7 @@ static void testSwapWindow() {
// test on workspace "swapwindow"
NLog::log("{}Switching to workspace \"swapwindow\"", Colors::YELLOW);
getFromSocket("/dispatch workspace name:swapwindow");
getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:swapwindow' })");
if (!Tests::spawnKitty("kitty_A")) {
ret = 1;
@@ -80,62 +80,62 @@ static void testSwapWindow() {
// Test swapwindow by direction
{
getFromSocket("/dispatch focuswindow class:kitty_A");
getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })");
auto pos = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:");
NLog::log("{}Testing kitty_A {}, swapwindow with direction 'r'", Colors::YELLOW, pos);
OK(getFromSocket("/dispatch swapwindow r"));
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
OK(getFromSocket("/dispatch hl.dsp.window.swap({ direction = 'right' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_B' })"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", pos));
}
// Test swapwindow by class
{
getFromSocket("/dispatch focuswindow class:kitty_A");
getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })");
auto pos = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:");
NLog::log("{}Testing kitty_A {}, swapwindow with class:kitty_B", Colors::YELLOW, pos);
OK(getFromSocket("/dispatch swapwindow class:kitty_B"));
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
OK(getFromSocket("/dispatch hl.dsp.window.swap({ target = 'class:kitty_B' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_B' })"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", pos));
}
// Test swapwindow by address
{
getFromSocket("/dispatch focuswindow class:kitty_B");
getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_B' })");
auto addr = getWindowAddress(getFromSocket("/activewindow"));
getFromSocket("/dispatch focuswindow class:kitty_A");
getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })");
auto pos = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:");
NLog::log("{}Testing kitty_A {}, swapwindow with address:0x{}(kitty_B)", Colors::YELLOW, pos, addr);
OK(getFromSocket(std::format("/dispatch swapwindow address:0x{}", addr)));
OK(getFromSocket(std::format("/dispatch focuswindow address:0x{}", addr)));
OK(getFromSocket(std::format("/dispatch hl.dsp.window.swap({{ target = 'address:0x{}' }})", addr)));
OK(getFromSocket(std::format("/dispatch hl.dsp.focus({{ window = 'address:0x{}' }})", addr)));
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", pos));
}
NLog::log("{}Testing swapwindow with fullscreen. Expecting to fail", Colors::YELLOW);
{
OK(getFromSocket("/dispatch fullscreen"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen()"));
auto str = getFromSocket("/dispatch swapwindow l");
auto str = getFromSocket("/dispatch hl.dsp.window.swap({ direction = 'left' })");
EXPECT_CONTAINS(str, "Can't swap fullscreen window");
OK(getFromSocket("/dispatch fullscreen"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen()"));
}
NLog::log("{}Testing swapwindow with different workspace", Colors::YELLOW);
{
getFromSocket("/dispatch focuswindow class:kitty_B");
getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_B' })");
auto addr = getWindowAddress(getFromSocket("/activewindow"));
auto ws = Tests::getWindowAttribute(getFromSocket("/activewindow"), "workspace:");
NLog::log("{}Sending address:0x{}(kitty_B) to workspace \"swapwindow2\"", Colors::YELLOW, addr);
OK(getFromSocket("/dispatch movetoworkspacesilent name:swapwindow2"));
OK(getFromSocket(std::format("/dispatch swapwindow address:0x{}", addr)));
getFromSocket("/dispatch focuswindow class:kitty_B");
OK(getFromSocket("/dispatch hl.dsp.window.move({ workspace = 'name:swapwindow2', follow = false })"));
OK(getFromSocket(std::format("/dispatch hl.dsp.window.swap({{ target = 'address:0x{}' }})", addr)));
getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_B' })");
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", ws));
}
@@ -150,11 +150,11 @@ static void testSwapWindow() {
static void testGroupRules() {
NLog::log("{}Testing group window rules", Colors::YELLOW);
OK(getFromSocket("/keyword general:border_size 8"));
OK(getFromSocket("/keyword workspace w[tv1], bordersize:0"));
OK(getFromSocket("/keyword workspace f[1], bordersize:0"));
OK(getFromSocket("/keyword windowrule match:workspace w[tv1], border_size 0"));
OK(getFromSocket("/keyword windowrule match:workspace f[1], border_size 0"));
OK(getFromSocket("/eval hl.config({ general = { border_size = 8 } })"));
OK(getFromSocket("/eval hl.workspace_rule({ workspace = 'w[tv1]', border_size = 0 })"));
OK(getFromSocket("/eval hl.workspace_rule({ workspace = 'f[1]', border_size = 0 })"));
OK(getFromSocket("/eval hl.window_rule({ match = { workspace = 'w[tv1]' }, border_size = 0 })"));
OK(getFromSocket("/eval hl.window_rule({ match = { workspace = 'f[1]' }, border_size = 0 })"));
if (!Tests::spawnKitty("kitty_A")) {
ret = 1;
@@ -176,17 +176,17 @@ static void testGroupRules() {
EXPECT_CONTAINS(str, "8");
}
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch togglegroup"));
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
OK(getFromSocket("/dispatch moveintogroup l"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })"));
OK(getFromSocket("/dispatch hl.dsp.group.toggle()"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_B' })"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ into_group = 'left' })"));
{
auto str = getFromSocket("/getprop active border_size");
EXPECT_CONTAINS(str, "0");
}
OK(getFromSocket("/dispatch changegroupactive f"));
OK(getFromSocket("/dispatch hl.dsp.group.next()"));
{
auto str = getFromSocket("/getprop active border_size");
@@ -198,7 +198,7 @@ static void testGroupRules() {
return;
}
OK(getFromSocket("/dispatch moveoutofgroup r"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ out_of_group = 'right' })"));
{
auto str = getFromSocket("/getprop active border_size");
@@ -242,85 +242,85 @@ static bool testWindowFocusOnFullscreenConflict() {
if (!spawnKitty("kitty_B"))
return false;
OK(getFromSocket("/keyword misc:focus_on_activate true"));
OK(getFromSocket("/eval hl.config({ misc = { focus_on_activate = true } })"));
// Unfullscreen on conflict
{
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2"));
OK(getFromSocket("/eval hl.config({ misc = { on_focus_under_fullscreen = 2 } })"));
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch fullscreen 0 set"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'set' })"));
EXPECT(isActiveWindow("kitty_A", '2'), true);
// Dispatch-focus the same window
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })"));
EXPECT(isActiveWindow("kitty_A", '2'), true);
// Dispatch-focus a different window
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_B' })"));
EXPECT(isActiveWindow("kitty_B", '0'), true);
// Make a window that will request focus
const std::string removeToActivate = spawnKittyActivating();
if (removeToActivate.empty())
return false;
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch fullscreen 0 set"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'set' })"));
EXPECT(isActiveWindow("kitty_A", '2'), true);
std::filesystem::remove(removeToActivate);
EXPECT(waitForActiveWindow("kitty_activating", '0'), true);
OK(getFromSocket("/dispatch forcekillactive"));
OK(getFromSocket("/dispatch hl.dsp.window.kill()"));
Tests::waitUntilWindowsN(2);
}
// Take over on conflict
{
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 1"));
OK(getFromSocket("/eval hl.config({ misc = { on_focus_under_fullscreen = 1 } })"));
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch fullscreen 0 set"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'set' })"));
EXPECT(isActiveWindow("kitty_A", '2'), true);
// Dispatch-focus the same window
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })"));
EXPECT(isActiveWindow("kitty_A", '2'), true);
// Dispatch-focus a different window
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_B' })"));
EXPECT(isActiveWindow("kitty_B", '2'), true);
OK(getFromSocket("/dispatch fullscreenstate 0 0"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen_state({ internal = 0, client = 0, action = 'set' })"));
// Make a window that will request focus
const std::string removeToActivate = spawnKittyActivating();
if (removeToActivate.empty())
return false;
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch fullscreen 0 set"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'set' })"));
EXPECT(isActiveWindow("kitty_A", '2'), true);
std::filesystem::remove(removeToActivate);
EXPECT(waitForActiveWindow("kitty_activating", '2'), true);
OK(getFromSocket("/dispatch forcekillactive"));
OK(getFromSocket("/dispatch hl.dsp.window.kill()"));
Tests::waitUntilWindowsN(2);
}
// Keep the old focus on conflict
{
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0"));
OK(getFromSocket("/eval hl.config({ misc = { on_focus_under_fullscreen = 0 } })"));
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch fullscreen 0 set"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'set' })"));
EXPECT(isActiveWindow("kitty_A", '2'), true);
// Dispatch-focus the same window
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })"));
EXPECT(isActiveWindow("kitty_A", '2'), true);
// Make a window that will request focus - the setting is treated normally
const std::string removeToActivate = spawnKittyActivating();
if (removeToActivate.empty())
return false;
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch fullscreen 0 set"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'set' })"));
EXPECT(isActiveWindow("kitty_A", '2'), true);
std::filesystem::remove(removeToActivate);
EXPECT(waitForActiveWindow("kitty_A", '2'), true);
@@ -351,7 +351,7 @@ static void testMaximizeSize() {
EXPECT(str.contains("fullscreen: 0"), true);
}
OK(getFromSocket("/dispatch fullscreen 1"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'maximized' })"));
{
auto str = getFromSocket("/clients");
@@ -371,14 +371,14 @@ static void testFloatingFocusOnFullscreen() {
NLog::log("{}Testing floating focus on fullscreen", Colors::GREEN);
EXPECT(spawnKitty("kitty_A"), true);
OK(getFromSocket("/dispatch togglefloating"));
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'toggle' })"));
EXPECT(spawnKitty("kitty_B"), true);
OK(getFromSocket("/dispatch fullscreen 1"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'maximized' })"));
OK(getFromSocket("/dispatch cyclenext"));
OK(getFromSocket("/dispatch hl.dsp.window.cycle_next()"));
OK(getFromSocket("/dispatch plugin:test:floating_focus_on_fullscreen"));
OK(getFromSocket("/eval hl.plugin.test.floating_focus_on_fullscreen()"));
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
@@ -392,7 +392,7 @@ static void testGroupFallbackFocus() {
EXPECT(spawnKitty("kitty_A"), true);
OK(getFromSocket("/dispatch togglegroup"));
OK(getFromSocket("/dispatch hl.dsp.group.toggle()"));
EXPECT(spawnKitty("kitty_B"), true);
EXPECT(spawnKitty("kitty_C"), true);
@@ -403,9 +403,9 @@ static void testGroupFallbackFocus() {
EXPECT(str.contains("class: kitty_D"), true);
}
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
OK(getFromSocket("/dispatch focuswindow class:kitty_D"));
OK(getFromSocket("/dispatch killactive"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_B' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_D' })"));
OK(getFromSocket("/dispatch hl.dsp.window.kill()"));
Tests::waitUntilWindowsN(3);
@@ -426,18 +426,18 @@ static void testBringActiveToTopMouseMovement() {
NLog::log("{}Testing bringactivetotop mouse movement", Colors::GREEN);
Tests::killAllWindows();
OK(getFromSocket("/keyword input:follow_mouse 2"));
OK(getFromSocket("/keyword input:float_switch_override_focus 0"));
OK(getFromSocket("/eval hl.config({ input = { follow_mouse = 2 } })"));
OK(getFromSocket("/eval hl.config({ input = { float_switch_override_focus = 0 } })"));
EXPECT(spawnKitty("a"), true);
OK(getFromSocket("/dispatch setfloating"));
OK(getFromSocket("/dispatch movewindowpixel exact 500 300,activewindow"));
OK(getFromSocket("/dispatch resizewindowpixel exact 400 400,activewindow"));
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set' })"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ x = 500, y = 300 })"));
OK(getFromSocket("/dispatch hl.dsp.window.resize({ x = 400, y = 400 })"));
EXPECT(spawnKitty("b"), true);
OK(getFromSocket("/dispatch setfloating"));
OK(getFromSocket("/dispatch movewindowpixel exact 500 300,activewindow"));
OK(getFromSocket("/dispatch resizewindowpixel exact 400 400,activewindow"));
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set' })"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ x = 500, y = 300 })"));
OK(getFromSocket("/dispatch hl.dsp.window.resize({ x = 400, y = 400 })"));
auto getTopWindow = []() -> std::string {
auto clients = getFromSocket("/clients");
@@ -445,16 +445,16 @@ static void testBringActiveToTopMouseMovement() {
};
EXPECT(getTopWindow(), std::string("b"));
OK(getFromSocket("/dispatch movecursor 700 500"));
OK(getFromSocket("/dispatch hl.dsp.cursor.move({ x = 700, y = 500 })"));
OK(getFromSocket("/dispatch focuswindow class:a"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:a' })"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: a");
OK(getFromSocket("/dispatch bringactivetotop"));
OK(getFromSocket("/dispatch hl.dsp.window.bring_to_top()"));
EXPECT(getTopWindow(), std::string("a"));
OK(getFromSocket("/dispatch plugin:test:click 272,1"));
OK(getFromSocket("/dispatch plugin:test:click 272,0"));
OK(getFromSocket("/eval hl.plugin.test.click(272, 1)"));
OK(getFromSocket("/eval hl.plugin.test.click(272, 0)"));
EXPECT(getTopWindow(), std::string("a"));
@@ -465,8 +465,8 @@ static void testInitialFloatSize() {
NLog::log("{}Testing initial float size", Colors::GREEN);
Tests::killAllWindows();
OK(getFromSocket("/keyword windowrule match:class kitty, float yes"));
OK(getFromSocket("/keyword input:float_switch_override_focus 0"));
OK(getFromSocket("/eval hl.window_rule({ match = { class = 'kitty' }, float = true })"));
OK(getFromSocket("/eval hl.config({ input = { float_switch_override_focus = 0 } })"));
EXPECT(spawnKitty("kitty"), true);
@@ -480,7 +480,7 @@ static void testInitialFloatSize() {
Tests::killAllWindows();
OK(getFromSocket("/dispatch exec [float yes]kitty"));
OK(getFromSocket("/dispatch hl.dsp.exec_cmd('kitty', { float = true })"));
Tests::waitUntilWindowsN(1);
@@ -505,19 +505,19 @@ static bool testWindowRuleFocusOnActivate() {
}
// Do not focus anyone automatically
///////////OK(getFromSocket("/keyword windowrule match:class .*, no_initial_focus true"));
///////////OK(getFromSocket("/eval hl.window_rule({ match = { class = '.*' }, no_initial_focus = true })"));
// `focus_on_activate off` takes over
{
OK(getFromSocket("/keyword misc:focus_on_activate true"));
OK(getFromSocket("/keyword windowrule match:class kitty_antifocus, focus_on_activate off"));
OK(getFromSocket("/eval hl.config({ misc = { focus_on_activate = true } })"));
OK(getFromSocket("/eval hl.window_rule({ match = { class = 'kitty_antifocus' }, focus_on_activate = false })"));
const std::string removeToActivate = spawnKittyActivating("kitty_antifocus");
if (removeToActivate.empty()) {
return false;
}
EXPECT(waitForActiveWindow("kitty_antifocus"), true);
OK(getFromSocket("/dispatch focuswindow class:kitty_default"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_default' })"));
EXPECT(isActiveWindow("kitty_default"), true);
std::filesystem::remove(removeToActivate);
@@ -527,15 +527,15 @@ static bool testWindowRuleFocusOnActivate() {
// `focus_on_activate on` takes over
{
OK(getFromSocket("/keyword misc:focus_on_activate false"));
OK(getFromSocket("/keyword windowrule match:class kitty_superfocus, focus_on_activate on"));
OK(getFromSocket("/eval hl.config({ misc = { focus_on_activate = false } })"));
OK(getFromSocket("/eval hl.window_rule({ match = { class = 'kitty_superfocus' }, focus_on_activate = true })"));
const std::string removeToActivate = spawnKittyActivating("kitty_superfocus");
if (removeToActivate.empty()) {
return false;
}
EXPECT(waitForActiveWindow("kitty_superfocus"), true);
OK(getFromSocket("/dispatch focuswindow class:kitty_default"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_default' })"));
EXPECT(isActiveWindow("kitty_default"), true);
std::filesystem::remove(removeToActivate);
@@ -558,15 +558,15 @@ static bool testWindowRuleFocusOnActivate() {
// tests if a pinned window contains the valid workspace after change
static bool testPinnedWorkspacesValid() {
OK(getFromSocket("/reload"));
getFromSocket("/dispatch workspace 1337");
getFromSocket("/dispatch hl.dsp.focus({ workspace = '1337' })");
if (!spawnKitty("kitty")) {
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
return false;
}
OK(getFromSocket("/dispatch setfloating class:kitty"));
OK(getFromSocket("/dispatch pin class:kitty"));
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set', window = 'class:kitty' })"));
OK(getFromSocket("/dispatch hl.dsp.window.pin({ action = 'toggle', window = 'class:kitty' })"));
{
auto str = getFromSocket("/activewindow");
@@ -574,7 +574,7 @@ static bool testPinnedWorkspacesValid() {
EXPECT(str.contains("pinned: 1"), true);
}
getFromSocket("/dispatch workspace 1338");
getFromSocket("/dispatch hl.dsp.focus({ workspace = '1338' })");
{
auto str = getFromSocket("/activewindow");
@@ -582,7 +582,7 @@ static bool testPinnedWorkspacesValid() {
EXPECT(str.contains("pinned: 1"), true);
}
OK(getFromSocket("/dispatch settiled class:kitty"))
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'unset', window = 'class:kitty' })"))
{
auto str = getFromSocket("/activewindow");
@@ -606,10 +606,10 @@ static bool testWindowRuleWorkspaceEmpty() {
NLog::log("{}Testing windowrule workspace empty", Colors::YELLOW);
OK(getFromSocket("/reload"));
OK(getFromSocket("/keyword windowrule match:class kitty_A, workspace empty"));
OK(getFromSocket("/keyword windowrule match:class kitty_B, workspace emptyn"));
OK(getFromSocket("/eval hl.window_rule({ match = { class = 'kitty_A' }, workspace = 'empty' })"));
OK(getFromSocket("/eval hl.window_rule({ match = { class = 'kitty_B' }, workspace = 'emptyn' })"));
getFromSocket("/dispatch workspace 3");
getFromSocket("/dispatch hl.dsp.focus({ workspace = '3' })");
if (!spawnKitty("kitty")) {
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
@@ -631,7 +631,7 @@ static bool testWindowRuleWorkspaceEmpty() {
EXPECT(str.contains("workspace: 1"), true);
}
getFromSocket("/dispatch workspace 3");
getFromSocket("/dispatch hl.dsp.focus({ workspace = '3' })");
if (!spawnKitty("kitty_B")) {
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
return false;
@@ -652,10 +652,10 @@ static bool testContentRules() {
// kill me PLEASE
OK(getFromSocket("/keyword windowrule match:class kitty_content_string, content game"));
OK(getFromSocket("/keyword windowrule match:class kitty_content_numbers, content 3"));
OK(getFromSocket("/keyword windowrule match:content game, border_size 10"));
OK(getFromSocket("/keyword windowrule match:content 3, opacity 0.5"));
OK(getFromSocket("/eval hl.window_rule({ match = { class = 'kitty_content_string' }, content = 'game' })"));
OK(getFromSocket("/eval hl.window_rule({ match = { class = 'kitty_content_numbers' }, content = '3' })"));
OK(getFromSocket("/eval hl.window_rule({ match = { content = 'game' }, border_size = 10 })"));
OK(getFromSocket("/eval hl.window_rule({ match = { content = '3' }, opacity = '0.5' })"));
const auto testProps = []() {
EXPECT_CONTAINS(getFromSocket("/getprop active border_size"), "10");
@@ -685,10 +685,10 @@ static bool test14038() {
if (!spawnKitty("kitty_14038"))
return false;
OK(getFromSocket("/dispatch movetoworkspacesilent special:a,class:kitty_14038"));
OK(getFromSocket("/dispatch togglefloating class:kitty_14038"));
OK(getFromSocket("/dispatch pin class:kitty_14038"));
OK(getFromSocket("/dispatch togglefloating class:kitty_14038"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ workspace = 'special:a', follow = false, window = 'class:kitty_14038' })"));
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'toggle', window = 'class:kitty_14038' })"));
OK(getFromSocket("/dispatch hl.dsp.window.pin({ action = 'toggle', window = 'class:kitty_14038' })"));
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'toggle', window = 'class:kitty_14038' })"));
// this should not crash hyprland. If we are alive, we good.
@@ -702,7 +702,7 @@ static bool test() {
// test on workspace "window"
NLog::log("{}Switching to workspace `window`", Colors::YELLOW);
getFromSocket("/dispatch workspace name:window");
getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:window' })");
if (!spawnKitty("kitty_A"))
return false;
@@ -746,7 +746,7 @@ static bool test() {
const int WIDTH2 = calculateFinalWidth(geomBoxWidthB_R2, false);
const int WIDTH_A_FINAL = calculateFinalWidth(geomBoxWidthA_R2, true);
OK(getFromSocket("/keyword dwindle:default_split_ratio 1.25"));
OK(getFromSocket("/eval hl.config({ dwindle = { default_split_ratio = 1.25 } })"));
if (!spawnKitty("kitty_B"))
return false;
@@ -754,11 +754,11 @@ static bool test() {
NLog::log("{}Expecting kitty_B size: {},{}", Colors::YELLOW, WIDTH1, HEIGHT);
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH1, HEIGHT));
OK(getFromSocket("/dispatch killwindow activewindow"));
OK(getFromSocket("/dispatch hl.dsp.window.kill({ window = 'activewindow' })"));
Tests::waitUntilWindowsN(1);
NLog::log("{}Inverting the split ratio", Colors::YELLOW);
OK(getFromSocket("/keyword dwindle:default_split_ratio 0.75"));
OK(getFromSocket("/eval hl.config({ dwindle = { default_split_ratio = 0.75 } })"));
if (!spawnKitty("kitty_B"))
return false;
@@ -777,7 +777,7 @@ static bool test() {
EXPECT_MAX_DELTA(std::stoi(std::string{sizes[1]}), HEIGHT, 2);
}
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })"));
NLog::log("{}Expecting kitty_A size: {},{}", Colors::YELLOW, WIDTH_A_FINAL, HEIGHT);
{
@@ -796,12 +796,12 @@ static bool test() {
EXPECT(false, true);
}
OK(getFromSocket("/keyword dwindle:default_split_ratio 1"));
OK(getFromSocket("/eval hl.config({ dwindle = { default_split_ratio = 1 } })"));
}
// open xeyes
NLog::log("{}Spawning xeyes", Colors::YELLOW);
getFromSocket("/dispatch exec xeyes");
getFromSocket("/dispatch hl.dsp.exec_cmd('xeyes')");
NLog::log("{}Keep checking if xeyes spawned", Colors::YELLOW);
Tests::waitUntilWindowsN(3);
@@ -814,7 +814,7 @@ static bool test() {
{
auto str = getFromSocket("/clients");
EXPECT_NOT_CONTAINS(str, "floating: 1");
getFromSocket("/dispatch setfloating class:XEyes");
getFromSocket("/dispatch hl.dsp.window.float({ action = 'set', window = 'class:XEyes' })");
std::this_thread::sleep_for(std::chrono::milliseconds(200));
str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "floating: 1");
@@ -829,7 +829,7 @@ static bool test() {
testSwapWindow();
getFromSocket("/dispatch workspace 1");
getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })");
if (!testWindowFocusOnFullscreenConflict()) {
ret = 1;
@@ -840,17 +840,17 @@ static bool test() {
{
if (!spawnKitty("kitty_A"))
return false;
OK(getFromSocket("/dispatch fullscreen 0 set"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'set' })"));
EXPECT(Tests::windowCount(), 1);
OK(getFromSocket("/dispatch exec [float] kitty"));
OK(getFromSocket("/dispatch hl.dsp.exec_cmd('kitty', { float = true })"));
Tests::waitUntilWindowsN(2);
OK(getFromSocket("/dispatch focuswindow class:^kitty$"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:^kitty$' })"));
const auto focused1 = getFromSocket("/activewindow");
EXPECT_CONTAINS(focused1, "class: kitty\n");
OK(getFromSocket("/dispatch killwindow activewindow"));
OK(getFromSocket("/dispatch hl.dsp.window.kill({ window = 'activewindow' })"));
Tests::waitUntilWindowsN(1);
// The old window should be focused again
@@ -864,10 +864,10 @@ static bool test() {
NLog::log("{}Testing minsize/maxsize rules for tiled windows", Colors::YELLOW);
{
// Enable the config for testing, test max/minsize for tiled windows and centering
OK(getFromSocket("/keyword misc:size_limits_tiled 1"));
OK(getFromSocket("/keyword windowrule[kitty-max-rule]:match:class kitty_maxsize"));
OK(getFromSocket("/keyword windowrule[kitty-max-rule]:max_size 1500 500"));
OK(getFromSocket("r/keyword windowrule[kitty-max-rule]:min_size 1200 500"));
OK(getFromSocket("/eval hl.config({ misc = { size_limits_tiled = 1 } })"));
OK(getFromSocket("/eval hl.window_rule({ name = 'kitty-max-rule', match = { class = 'kitty_maxsize' } })"));
OK(getFromSocket("/eval hl.window_rule({ name = 'kitty-max-rule', max_size = '1500 500' })"));
OK(getFromSocket("r/eval hl.window_rule({ name = 'kitty-max-rule', min_size = '1200 500' })"));
if (!spawnKitty("kitty_maxsize"))
return false;
@@ -883,7 +883,7 @@ static bool test() {
Tests::killAllWindows();
EXPECT(Tests::windowCount(), 0);
OK(getFromSocket("/keyword general:layout master"));
OK(getFromSocket("r/eval hl.config({ general = { layout = 'master' } })"));
if (!spawnKitty("kitty_maxsize"))
return false;
@@ -896,7 +896,7 @@ static bool test() {
return false;
// FIXME: I can't be arsed.
OK(getFromSocket("/dispatch focuswindow class:kitty_maxsize"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_maxsize' })"));
// EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 1200,500")
NLog::log("{}Reloading config", Colors::YELLOW);
@@ -908,10 +908,10 @@ static bool test() {
NLog::log("{}Testing minsize/maxsize rules", Colors::YELLOW);
{
// Disable size limits tiled and check if props are working and not getting skipped
OK(getFromSocket("/keyword misc:size_limits_tiled 0"));
OK(getFromSocket("/keyword windowrule[kitty-max-rule]:match:class kitty_maxsize"));
OK(getFromSocket("/keyword windowrule[kitty-max-rule]:max_size 1500 500"));
OK(getFromSocket("r/keyword windowrule[kitty-max-rule]:min_size 1200 500"));
OK(getFromSocket("/eval hl.config({ misc = { size_limits_tiled = 0 } })"));
OK(getFromSocket("/eval hl.window_rule({ name = 'kitty-max-rule', match = { class = 'kitty_maxsize' } })"));
OK(getFromSocket("/eval hl.window_rule({ name = 'kitty-max-rule', max_size = '1500 500' })"));
OK(getFromSocket("r/eval hl.window_rule({ name = 'kitty-max-rule', min_size = '1200 500' })"));
if (!spawnKitty("kitty_maxsize"))
return false;
@@ -935,10 +935,10 @@ static bool test() {
{
// Set float
OK(getFromSocket("/keyword windowrule[kitty-max-rule]:match:class kitty_maxsize"));
OK(getFromSocket("/keyword windowrule[kitty-max-rule]:max_size 1200 500"));
OK(getFromSocket("r/keyword windowrule[kitty-max-rule]:min_size 1200 500"));
OK(getFromSocket("r/keyword windowrule[kitty-max-rule]:float yes"));
OK(getFromSocket("/eval hl.window_rule({ name = 'kitty-max-rule', match = { class = 'kitty_maxsize' } })"));
OK(getFromSocket("/eval hl.window_rule({ name = 'kitty-max-rule', max_size = '1200 500' })"));
OK(getFromSocket("r/eval hl.window_rule({ name = 'kitty-max-rule', min_size = '1200 500' })"));
OK(getFromSocket("r/eval hl.window_rule({ name = 'kitty-max-rule', float = true })"));
if (!spawnKitty("kitty_maxsize"))
return false;
@@ -976,15 +976,15 @@ static bool test() {
EXPECT_NOT_CONTAINS(str, "pinned: 1");
}
OK(getFromSocket("/keyword windowrule[wr-kitty-stuff]:opacity 0.5 0.5 override"));
OK(getFromSocket("/eval hl.window_rule({ name = 'wr-kitty-stuff', opacity = '0.5 0.5 override' })"));
{
auto str = getFromSocket("/getprop active opacity");
EXPECT_CONTAINS(str, "0.5");
}
OK(getFromSocket("/keyword windowrule[special-magic-kitty]:match:class magic_kitty"));
OK(getFromSocket("/keyword windowrule[special-magic-kitty]:workspace special:magic"));
OK(getFromSocket("/eval hl.window_rule({ name = 'special-magic-kitty', match = { class = 'magic_kitty' } })"));
OK(getFromSocket("/eval hl.window_rule({ name = 'special-magic-kitty', workspace = 'special:magic' })"));
if (!spawnKitty("magic_kitty"))
return false;
@@ -996,18 +996,18 @@ static bool test() {
}
if (auto str = getFromSocket("/monitors"); str.contains("magic)")) {
OK(getFromSocket("/dispatch togglespecialworkspace magic"));
OK(getFromSocket("/dispatch hl.dsp.workspace.toggle_special('magic')"));
}
Tests::killAllWindows();
OK(getFromSocket("/keyword windowrule[border-magic-kitty]:match:class border_kitty"));
OK(getFromSocket("/keyword windowrule[border-magic-kitty]:border_color rgba(c6ff00ff) rgba(ff0000ee) 45deg"));
OK(getFromSocket("/eval hl.window_rule({ name = 'border-magic-kitty', match = { class = 'border_kitty' } })"));
OK(getFromSocket("/eval hl.window_rule({ name = 'border-magic-kitty', border_color = 'rgba(c6ff00ff) rgba(ff0000ee) 45deg' })"));
if (!spawnKitty("border_kitty"))
return false;
OK(getFromSocket("/dispatch focuswindow class:border_kitty"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:border_kitty' })"));
{
auto str = getFromSocket("/getprop active active_border_color");
@@ -1030,8 +1030,8 @@ static bool test() {
Tests::killAllWindows();
// test rules that overlap effects but don't overlap props
OK(getFromSocket("/keyword windowrule match:class overlap_kitty, border_size 0"));
OK(getFromSocket("/keyword windowrule match:fullscreen false, border_size 10"));
OK(getFromSocket("/eval hl.window_rule({ match = { class = 'overlap_kitty' }, border_size = 0 })"));
OK(getFromSocket("/eval hl.window_rule({ match = { fullscreen = false }, border_size = 10 })"));
if (!spawnKitty("overlap_kitty"))
return false;
@@ -1045,12 +1045,12 @@ static bool test() {
Tests::killAllWindows();
// test persistent_size between floating window launches
OK(getFromSocket("/keyword windowrule match:class persistent_size_kitty, persistent_size true, float true"));
OK(getFromSocket("/eval hl.window_rule({ match = { class = 'persistent_size_kitty' }, persistent_size = true, float = true })"));
if (!spawnKitty("persistent_size_kitty"))
return false;
OK(getFromSocket("/dispatch resizeactive exact 600 400"))
OK(getFromSocket("/dispatch hl.dsp.window.resize({ x = 600, y = 400 })"))
{
auto str = getFromSocket("/activewindow");
@@ -1072,8 +1072,8 @@ static bool test() {
OK(getFromSocket("/reload"));
Tests::killAllWindows();
OK(getFromSocket("/keyword general:border_size 0"));
OK(getFromSocket("/keyword windowrule match:float true, border_size 10"));
OK(getFromSocket("/eval hl.config({ general = { border_size = 0 } })"));
OK(getFromSocket("/eval hl.window_rule({ match = { float = true }, border_size = 10 })"));
if (!spawnKitty("border_kitty"))
return false;
@@ -1083,14 +1083,14 @@ static bool test() {
EXPECT_CONTAINS(str, "0");
}
OK(getFromSocket("/dispatch togglefloating"));
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'toggle' })"));
{
auto str = getFromSocket("/getprop active border_size");
EXPECT_CONTAINS(str, "10");
}
OK(getFromSocket("/dispatch togglefloating"));
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'toggle' })"));
{
auto str = getFromSocket("/getprop active border_size");
@@ -1101,8 +1101,8 @@ static bool test() {
Tests::killAllWindows();
// test expression rules
OK(getFromSocket("/keyword windowrule match:class expr_kitty, float yes, size monitor_w*0.5 monitor_h*0.5, min_size monitor_w*0.25 monitor_h*0.25, "
"max_size monitor_w*0.75 monitor_h*0.75, move 20+(monitor_w*0.1) monitor_h*0.5"));
OK(getFromSocket("/eval hl.window_rule({ match = { class = 'expr_kitty' }, float = true, size = 'monitor_w*0.5 monitor_h*0.5', min_size = 'monitor_w*0.25 monitor_h*0.25', "
"max_size = 'monitor_w*0.75 monitor_h*0.75', move = '20+(monitor_w*0.1) monitor_h*0.5' })"));
if (!spawnKitty("expr_kitty"))
return false;
@@ -1125,29 +1125,29 @@ static bool test() {
OK(getFromSocket("/reload"));
Tests::killAllWindows();
OK(getFromSocket("/dispatch plugin:test:add_window_rule"));
OK(getFromSocket("/eval hl.plugin.test.add_window_rule()"));
OK(getFromSocket("/reload"));
OK(getFromSocket("/keyword windowrule match:class plugin_kitty, plugin_rule effect"));
OK(getFromSocket("/eval hl.window_rule({ match = { class = 'plugin_kitty' }, plugin_rule = 'effect' })"));
if (!spawnKitty("plugin_kitty"))
return false;
OK(getFromSocket("/dispatch plugin:test:check_window_rule"));
OK(getFromSocket("/eval hl.plugin.test.check_window_rule()"));
OK(getFromSocket("/reload"));
Tests::killAllWindows();
OK(getFromSocket("/dispatch plugin:test:add_window_rule"));
OK(getFromSocket("/eval hl.plugin.test.add_window_rule()"));
OK(getFromSocket("/reload"));
OK(getFromSocket("/keyword windowrule[test-plugin-rule]:match:class plugin_kitty"));
OK(getFromSocket("/keyword windowrule[test-plugin-rule]:plugin_rule effect"));
OK(getFromSocket("/eval hl.window_rule({ name = 'test-plugin-rule', match = { class = 'plugin_kitty' } })"));
OK(getFromSocket("/eval hl.window_rule({ name = 'test-plugin-rule', plugin_rule = 'effect' })"));
if (!spawnKitty("plugin_kitty"))
return false;
OK(getFromSocket("/dispatch plugin:test:check_window_rule"));
OK(getFromSocket("/eval hl.plugin.test.check_window_rule()"));
OK(getFromSocket("/reload"));
Tests::killAllWindows();
+131 -131
View File
@@ -28,17 +28,17 @@ static bool testSpecialWorkspaceFullscreen() {
// Close special workspace if open
auto monitors = getFromSocket("/monitors");
if (monitors.contains("(special:test_fs_special)") && !monitors.contains("special workspace: 0 ()"))
getFromSocket("/dispatch togglespecialworkspace test_fs_special");
getFromSocket("/dispatch hl.dsp.workspace.toggle_special('test_fs_special')");
Tests::killAllWindows();
OK(getFromSocket("/reload"));
}};
getFromSocket("/dispatch workspace 1");
getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })");
EXPECT(Tests::windowCount(), 0);
NLog::log("{}Test 1: Fullscreen detection on special workspace", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace special:test_fs_special"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'special:test_fs_special' })"));
if (!Tests::spawnKitty("kitty_special"))
return false;
@@ -49,7 +49,7 @@ static bool testSpecialWorkspaceFullscreen() {
EXPECT_CONTAINS(str, "(special:test_fs_special)");
}
OK(getFromSocket("/dispatch fullscreen 0"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen' })"));
{
auto str = getFromSocket("/activewindow");
@@ -64,13 +64,13 @@ static bool testSpecialWorkspaceFullscreen() {
NLog::log("{}Test 2: Special workspace fullscreen precedence", Colors::YELLOW);
// Close special workspace before spawning on regular workspace
OK(getFromSocket("/dispatch togglespecialworkspace test_fs_special"));
getFromSocket("/dispatch workspace 1");
OK(getFromSocket("/dispatch hl.dsp.workspace.toggle_special('test_fs_special')"));
getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })");
if (!Tests::spawnKitty("kitty_regular"))
return false;
OK(getFromSocket("/dispatch fullscreen 0"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen' })"));
{
auto str = getFromSocket("/activewindow");
@@ -78,8 +78,8 @@ static bool testSpecialWorkspaceFullscreen() {
EXPECT_CONTAINS(str, "fullscreen: 2");
}
OK(getFromSocket("/dispatch togglespecialworkspace test_fs_special"));
OK(getFromSocket("/dispatch focuswindow class:kitty_special"));
OK(getFromSocket("/dispatch hl.dsp.workspace.toggle_special('test_fs_special')"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_special' })"));
{
auto str = getFromSocket("/activewindow");
@@ -88,8 +88,8 @@ static bool testSpecialWorkspaceFullscreen() {
NLog::log("{}Test 3: Toggle special workspace hides it", Colors::YELLOW);
OK(getFromSocket("/dispatch togglespecialworkspace test_fs_special"));
OK(getFromSocket("/dispatch focuswindow class:kitty_regular"));
OK(getFromSocket("/dispatch hl.dsp.workspace.toggle_special('test_fs_special')"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_regular' })"));
{
auto str = getFromSocket("/activewindow");
@@ -115,74 +115,74 @@ static bool testAsymmetricGaps() {
OK(getFromSocket("/reload"));
}};
OK(getFromSocket("/dispatch workspace name:gap_split_test"));
OK(getFromSocket("r/keyword general:gaps_in 0"));
OK(getFromSocket("r/keyword general:border_size 0"));
OK(getFromSocket("r/keyword dwindle:split_width_multiplier 1.0"));
OK(getFromSocket("r/keyword workspace name:gap_split_test,gapsout:0 1000 0 0"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:gap_split_test' })"));
OK(getFromSocket("r/eval hl.config({ general = { gaps_in = 0 } })"));
OK(getFromSocket("r/eval hl.config({ general = { border_size = 0 } })"));
OK(getFromSocket("r/eval hl.config({ dwindle = { split_width_multiplier = 1.0 } })"));
OK(getFromSocket("r/eval hl.workspace_rule({ workspace = 'name:gap_split_test', gaps_out = { top = 0, right = 1000, bottom = 0, left = 0 } })"));
NLog::log("{}Testing default split (force_split = 0)", Colors::YELLOW);
OK(getFromSocket("r/keyword dwindle:force_split 0"));
OK(getFromSocket("r/eval hl.config({ dwindle = { force_split = 0 } })"));
if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B"))
return false;
NLog::log("{}Expecting vertical split (B below A)", Colors::YELLOW);
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_A' })"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_B' })"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540");
Tests::killAllWindows();
EXPECT(Tests::windowCount(), 0);
NLog::log("{}Testing force_split = 1", Colors::YELLOW);
OK(getFromSocket("r/keyword dwindle:force_split 1"));
OK(getFromSocket("r/eval hl.config({ dwindle = { force_split = 1 } })"));
if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B"))
return false;
NLog::log("{}Expecting vertical split (B above A)", Colors::YELLOW);
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_B' })"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_A' })"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540");
NLog::log("{}Expecting horizontal split (C left of B)", Colors::YELLOW);
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_B' })"));
if (!Tests::spawnKitty("gaps_kitty_C"))
return false;
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_C"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_C' })"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_B' })"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 460,0");
Tests::killAllWindows();
EXPECT(Tests::windowCount(), 0);
NLog::log("{}Testing force_split = 2", Colors::YELLOW);
OK(getFromSocket("r/keyword dwindle:force_split 2"));
OK(getFromSocket("r/eval hl.config({ dwindle = { force_split = 2 } })"));
if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B"))
return false;
NLog::log("{}Expecting vertical split (B below A)", Colors::YELLOW);
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_A' })"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_B' })"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540");
NLog::log("{}Expecting horizontal split (C right of A)", Colors::YELLOW);
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_A' })"));
if (!Tests::spawnKitty("gaps_kitty_C"))
return false;
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_A' })"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_C"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:gaps_kitty_C' })"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 460,0");
}
@@ -197,22 +197,22 @@ static void testWorkspaceHistoryMultiMon() {
NLog::log("{}Testing multimon workspace history tracker", Colors::YELLOW);
// Initial state:
OK(getFromSocket("/dispatch focusmonitor HEADLESS-2"));
OK(getFromSocket("/dispatch workspace 10"));
OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-2' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '10' })"));
Tests::spawnKitty();
OK(getFromSocket("/dispatch workspace 11"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '11' })"));
Tests::spawnKitty();
OK(getFromSocket("/dispatch focusmonitor HEADLESS-3"));
OK(getFromSocket("/dispatch workspace 12"));
OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-3' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '12' })"));
Tests::spawnKitty();
OK(getFromSocket("/dispatch focusmonitor HEADLESS-2"));
OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-2' })"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 11");
}
OK(getFromSocket("/dispatch workspace previous_per_monitor"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'previous_per_monitor' })"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 10");
@@ -225,56 +225,56 @@ static void testWorkspaceHistoryMultiMon() {
static void testMultimonBAF() {
NLog::log("{}Testing multimon back and forth", Colors::YELLOW);
OK(getFromSocket("/keyword binds:workspace_back_and_forth 1"));
OK(getFromSocket("/eval hl.config({ binds = { workspace_back_and_forth = 1 } })"));
OK(getFromSocket("/dispatch focusmonitor HEADLESS-2"));
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-2' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
Tests::spawnKitty();
OK(getFromSocket("/dispatch workspace 2"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '2' })"));
Tests::spawnKitty();
OK(getFromSocket("/dispatch focusmonitor HEADLESS-3"));
OK(getFromSocket("/dispatch workspace 3"));
OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-3' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '3' })"));
Tests::spawnKitty();
OK(getFromSocket("/dispatch workspace 3"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '3' })"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 2 ");
}
OK(getFromSocket("/dispatch workspace 4"));
OK(getFromSocket("/dispatch workspace 4"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '4' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '4' })"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 2 ");
}
OK(getFromSocket("/dispatch workspace 2"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '2' })"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 4 ");
}
OK(getFromSocket("/dispatch workspace 3"));
OK(getFromSocket("/dispatch workspace 3"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '3' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '3' })"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 4 ");
}
OK(getFromSocket("/dispatch workspace 2"));
OK(getFromSocket("/dispatch workspace 3"));
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '2' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '3' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
{
auto str = getFromSocket("/activeworkspace");
@@ -287,11 +287,11 @@ static void testMultimonBAF() {
static void testMultimonFocus() {
NLog::log("{}Testing multimon focus and move", Colors::YELLOW);
OK(getFromSocket("/keyword input:follow_mouse 0"));
OK(getFromSocket("/dispatch focusmonitor HEADLESS-3"));
OK(getFromSocket("/dispatch workspace 8"));
OK(getFromSocket("/dispatch focusmonitor HEADLESS-2"));
OK(getFromSocket("/dispatch workspace 7"));
OK(getFromSocket("/eval hl.config({ input = { follow_mouse = 0 } })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-3' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '8' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-2' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '7' })"));
for (auto const& win : {"a", "b"}) {
if (!Tests::spawnKitty(win)) {
@@ -302,8 +302,8 @@ static void testMultimonFocus() {
}
}
OK(getFromSocket("/dispatch focuswindow class:a"));
OK(getFromSocket("/dispatch movefocus r"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:a' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'right' })"));
{
auto str = getFromSocket("/activeworkspace");
@@ -315,7 +315,7 @@ static void testMultimonFocus() {
EXPECT_CONTAINS(str, "class: b");
}
OK(getFromSocket("/dispatch movefocus r"));
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'right' })"));
{
auto str = getFromSocket("/activeworkspace");
@@ -334,7 +334,7 @@ static void testMultimonFocus() {
EXPECT_CONTAINS(str, "workspace ID 8 ");
}
OK(getFromSocket("/dispatch movefocus l"));
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'left' })"));
{
auto str = getFromSocket("/activewindow");
@@ -346,7 +346,7 @@ static void testMultimonFocus() {
EXPECT_CONTAINS(str, "workspace ID 7 ");
}
OK(getFromSocket("/dispatch movewindow r"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ direction = 'right' })"));
{
auto str = getFromSocket("/activewindow");
@@ -358,7 +358,7 @@ static void testMultimonFocus() {
EXPECT_CONTAINS(str, "workspace ID 8 ");
}
OK(getFromSocket("/dispatch movefocus r"));
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'right' })"));
{
auto str = getFromSocket("/activewindow");
@@ -370,12 +370,12 @@ static void testMultimonFocus() {
EXPECT_CONTAINS(str, "workspace ID 8 ");
}
OK(getFromSocket("/dispatch movefocus l"));
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'left' })"));
OK(getFromSocket("/keyword general:no_focus_fallback true"));
OK(getFromSocket("/keyword binds:window_direction_monitor_fallback false"));
OK(getFromSocket("/eval hl.config({ general = { no_focus_fallback = true } })"));
OK(getFromSocket("/eval hl.config({ binds = { window_direction_monitor_fallback = false } })"));
EXPECT_NOT(getFromSocket("/dispatch movefocus l"), "ok");
EXPECT_NOT(getFromSocket("/dispatch hl.dsp.focus({ direction = 'left' })"), "ok");
{
auto str = getFromSocket("/activewindow");
@@ -387,7 +387,7 @@ static void testMultimonFocus() {
EXPECT_CONTAINS(str, "workspace ID 8 ");
}
OK(getFromSocket("/dispatch movewindow l"));
OK(getFromSocket("/dispatch hl.dsp.window.move({ direction = 'left' })"));
{
auto str = getFromSocket("/activewindow");
@@ -407,12 +407,12 @@ static void testMultimonFocus() {
static void testDynamicWsEffects() {
// test dynamic workspace effects, they shouldn't lag
OK(getFromSocket("/dispatch workspace 69"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '69' })"));
Tests::spawnKitty("bitch");
OK(getFromSocket("r/keyword workspace 69,bordersize:20"));
OK(getFromSocket("r/keyword workspace 69,rounding:false"));
OK(getFromSocket("r/eval hl.workspace_rule({ workspace = '69', border_size = 20 })"));
OK(getFromSocket("r/eval hl.workspace_rule({ workspace = '69', no_rounding = true })"));
EXPECT(getFromSocket("/getprop class:bitch border_size"), "20");
EXPECT(getFromSocket("/getprop class:bitch rounding"), "0");
@@ -429,10 +429,10 @@ static bool test() {
// test on workspace "window"
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
getFromSocket("/dispatch workspace 1");
getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })");
NLog::log("{}Checking persistent no-mon", Colors::YELLOW);
OK(getFromSocket("r/keyword workspace 966,persistent:1"));
OK(getFromSocket("r/eval hl.workspace_rule({ workspace = '966', persistent = true })"));
{
auto str = getFromSocket("/workspaces");
@@ -450,7 +450,7 @@ static bool test() {
}
NLog::log("{}Switching to workspace 3", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 3"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '3' })"));
NLog::log("{}Spawning kittyProc on ws 3", Colors::YELLOW);
auto kittyProcB = Tests::spawnKitty();
@@ -461,10 +461,10 @@ static bool test() {
}
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
NLog::log("{}Switching to workspace +1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace +1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '+1' })"));
{
auto str = getFromSocket("/activeworkspace");
@@ -479,7 +479,7 @@ static bool test() {
}
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
{
auto str = getFromSocket("/workspaces");
@@ -487,7 +487,7 @@ static bool test() {
}
NLog::log("{}Switching to workspace m+1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace m+1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'm+1' })"));
{
auto str = getFromSocket("/activeworkspace");
@@ -495,7 +495,7 @@ static bool test() {
}
NLog::log("{}Switching to workspace -1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace -1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '-1' })"));
{
auto str = getFromSocket("/activeworkspace");
@@ -503,10 +503,10 @@ static bool test() {
}
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
NLog::log("{}Switching to workspace r+1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace r+1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'r+1' })"));
{
auto str = getFromSocket("/activeworkspace");
@@ -514,7 +514,7 @@ static bool test() {
}
NLog::log("{}Switching to workspace r+1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace r+1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'r+1' })"));
{
auto str = getFromSocket("/activeworkspace");
@@ -522,7 +522,7 @@ static bool test() {
}
NLog::log("{}Switching to workspace r~1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace r~1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'r~1' })"));
{
auto str = getFromSocket("/activeworkspace");
@@ -530,7 +530,7 @@ static bool test() {
}
NLog::log("{}Switching to workspace empty", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace empty"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'empty' })"));
{
auto str = getFromSocket("/activeworkspace");
@@ -538,7 +538,7 @@ static bool test() {
}
NLog::log("{}Switching to workspace previous", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace previous"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'previous' })"));
{
auto str = getFromSocket("/activeworkspace");
@@ -546,7 +546,7 @@ static bool test() {
}
NLog::log("{}Switching to workspace name:TEST_WORKSPACE_NULL", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace name:TEST_WORKSPACE_NULL"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'name:TEST_WORKSPACE_NULL' })"));
{
auto str = getFromSocket("/activeworkspace");
@@ -554,7 +554,7 @@ static bool test() {
}
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
// add a new monitor
NLog::log("{}Adding a new monitor", Colors::YELLOW);
@@ -569,7 +569,7 @@ static bool test() {
}
// focus the first monitor
OK(getFromSocket("/dispatch focusmonitor 0"));
OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-2' })"));
{
auto str = getFromSocket("/activeworkspace");
@@ -577,7 +577,7 @@ static bool test() {
}
NLog::log("{}Switching to workspace r+1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace r+1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'r+1' })"));
{
auto str = getFromSocket("/activeworkspace");
@@ -585,8 +585,8 @@ static bool test() {
}
NLog::log("{}Switching to workspace r~2", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch workspace r~2"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'r~2' })"));
{
auto str = getFromSocket("/activeworkspace");
@@ -594,7 +594,7 @@ static bool test() {
}
NLog::log("{}Switching to workspace m+1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace m+1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'm+1' })"));
{
auto str = getFromSocket("/activeworkspace");
@@ -603,7 +603,7 @@ static bool test() {
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
// no OK: this will throw an error as it should
getFromSocket("/dispatch workspace 1");
getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })");
{
auto str = getFromSocket("/activeworkspace");
@@ -611,19 +611,19 @@ static bool test() {
}
NLog::log("{}Testing back_and_forth", Colors::YELLOW);
OK(getFromSocket("/keyword binds:workspace_back_and_forth true"));
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/eval hl.config({ binds = { workspace_back_and_forth = true } })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
}
OK(getFromSocket("/keyword binds:workspace_back_and_forth false"));
OK(getFromSocket("/eval hl.config({ binds = { workspace_back_and_forth = false } })"));
NLog::log("{}Testing hide_special_on_workspace_change", Colors::YELLOW);
OK(getFromSocket("/keyword binds:hide_special_on_workspace_change true"));
OK(getFromSocket("/dispatch workspace special:HELLO"));
OK(getFromSocket("/eval hl.config({ binds = { hide_special_on_workspace_change = true } })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'special:HELLO' })"));
{
auto str = getFromSocket("/monitors");
@@ -632,82 +632,82 @@ static bool test() {
}
// no OK: will err (it shouldn't prolly but oh well)
getFromSocket("/dispatch workspace 3");
getFromSocket("/dispatch hl.dsp.focus({ workspace = '3' })");
{
auto str = getFromSocket("/monitors");
EXPECT_COUNT_STRING(str, "special workspace: 0 ()", 2);
}
OK(getFromSocket("/keyword binds:hide_special_on_workspace_change false"));
OK(getFromSocket("/eval hl.config({ binds = { hide_special_on_workspace_change = false } })"));
NLog::log("{}Testing allow_workspace_cycles", Colors::YELLOW);
OK(getFromSocket("/keyword binds:allow_workspace_cycles true"));
OK(getFromSocket("/eval hl.config({ binds = { allow_workspace_cycles = true } })"));
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch workspace 3"));
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '3' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
OK(getFromSocket("/dispatch workspace previous"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'previous' })"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
}
OK(getFromSocket("/dispatch workspace previous"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'previous' })"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 1 (1)");
}
OK(getFromSocket("/dispatch workspace previous"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'previous' })"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
}
OK(getFromSocket("/keyword binds:allow_workspace_cycles false"));
OK(getFromSocket("/eval hl.config({ binds = { allow_workspace_cycles = false } })"));
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
// spawn 3 kitties
NLog::log("{}Testing focus_preferred_method", Colors::YELLOW);
OK(getFromSocket("/keyword dwindle:force_split 2"));
OK(getFromSocket("/eval hl.config({ dwindle = { force_split = 2 } })"));
Tests::spawnKitty("kitty_A");
Tests::spawnKitty("kitty_B");
Tests::spawnKitty("kitty_C");
OK(getFromSocket("/keyword dwindle:force_split 0"));
OK(getFromSocket("/eval hl.config({ dwindle = { force_split = 0 } })"));
// focus kitty 2: will be top right (dwindle)
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_B' })"));
// resize it to be a bit taller
OK(getFromSocket("/dispatch resizeactive +20 +20"));
OK(getFromSocket("/dispatch hl.dsp.window.resize({ x = 20, y = 20, relative = true })"));
// now we test focus methods.
OK(getFromSocket("/keyword binds:focus_preferred_method 0"));
OK(getFromSocket("/eval hl.config({ binds = { focus_preferred_method = 0 } })"));
OK(getFromSocket("/dispatch focuswindow class:kitty_C"));
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_C' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })"));
OK(getFromSocket("/dispatch movefocus r"));
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'right' })"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_C");
}
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })"));
OK(getFromSocket("/keyword binds:focus_preferred_method 1"));
OK(getFromSocket("/eval hl.config({ binds = { focus_preferred_method = 1 } })"));
OK(getFromSocket("/dispatch movefocus r"));
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'right' })"));
{
auto str = getFromSocket("/activewindow");
@@ -715,41 +715,41 @@ static bool test() {
}
NLog::log("{}Testing movefocus_cycles_fullscreen", Colors::YELLOW);
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch focusmonitor HEADLESS-3"));
OK(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:kitty_A' })"));
OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-3' })"));
Tests::spawnKitty("kitty_D");
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_D");
}
OK(getFromSocket("/dispatch focusmonitor l"));
OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'l' })"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_A");
}
OK(getFromSocket("/keyword binds:movefocus_cycles_fullscreen false"));
OK(getFromSocket("/dispatch fullscreen 0"));
OK(getFromSocket("/eval hl.config({ binds = { movefocus_cycles_fullscreen = false } })"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen' })"));
OK(getFromSocket("/dispatch movefocus r"));
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'right' })"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_D");
}
OK(getFromSocket("/dispatch focusmonitor l"));
OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'l' })"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_A");
}
OK(getFromSocket("/keyword binds:movefocus_cycles_fullscreen true"));
OK(getFromSocket("/eval hl.config({ binds = { movefocus_cycles_fullscreen = true } })"));
OK(getFromSocket("/dispatch movefocus r"));
OK(getFromSocket("/dispatch hl.dsp.focus({ direction = 'right' })"));
{
auto str = getFromSocket("/activewindow");
+2 -2
View File
@@ -11,7 +11,7 @@
#include "../shared.hpp"
bool testPlugin() {
const auto RESPONSE = getFromSocket("/dispatch plugin:test:test");
const auto RESPONSE = getFromSocket("/eval hl.plugin.test.test()");
if (RESPONSE != "ok") {
NLog::log("{}Plugin tests failed, plugin returned:\n{}{}", Colors::RED, Colors::RESET, RESPONSE);
@@ -21,7 +21,7 @@ bool testPlugin() {
}
bool testVkb() {
const auto RESPONSE = getFromSocket("/dispatch plugin:test:vkb");
const auto RESPONSE = getFromSocket("/eval hl.plugin.test.vkb()");
if (RESPONSE != "ok") {
NLog::log("{}Vkb tests failed, tests returned:\n{}{}", Colors::RED, Colors::RESET, RESPONSE);
+1 -1
View File
@@ -98,7 +98,7 @@ bool Tests::killAllWindows() {
auto pos = str.find("Window ");
while (pos != std::string::npos) {
auto pos2 = str.find(" -> ", pos);
getFromSocket("/dispatch killwindow address:0x" + str.substr(pos + 7, pos2 - pos - 7));
getFromSocket("/dispatch hl.dsp.window.kill({ window = 'address:0x" + str.substr(pos + 7, pos2 - pos - 7) + "' })");
pos = str.find("Window ", pos + 5);
}
-413
View File
@@ -1,413 +0,0 @@
# This is an example Hyprland config file.
# Refer to the wiki for more information.
# https://wiki.hyprland.org/Configuring/
# Please note not all available settings / options are set here.
# For a full list, see the wiki
# You can split this configuration into multiple files
# Create your files separately and then link them to this file like this:
# source = ~/.config/hypr/myColors.conf
################
### MONITORS ###
################
# See https://wiki.hyprland.org/Configuring/Monitors/
monitor=HEADLESS-1,1920x1080@60,auto-right,1
monitor=HEADLESS-2,1920x1080@60,auto-right,1
monitor=HEADLESS-3,1920x1080@60,auto-right,1
monitor=HEADLESS-4,1920x1080@60,auto-right,1
monitor=HEADLESS-5,1920x1080@60,auto-right,1
monitor=HEADLESS-6,1920x1080@60,auto-right,1
monitor=HEADLESS-PERSISTENT-TEST,1920x1080@60,auto-right,1
monitor=,disabled
###################
### MY PROGRAMS ###
###################
# See https://wiki.hyprland.org/Configuring/Keywords/
# Set programs that you use
$terminal = kitty
$fileManager = dolphin
$menu = wofi --show drun
#################
### AUTOSTART ###
#################
# Autostart necessary processes (like notifications daemons, status bars, etc.)
# Or execute your favorite apps at launch like this:
exec-once = sleep 0 # Terminates very quickly
# exec-once = $terminal
# exec-once = nm-applet &
# exec-once = waybar & hyprpaper & firefox
#############################
### ENVIRONMENT VARIABLES ###
#############################
# See https://wiki.hyprland.org/Configuring/Environment-variables/
env = XCURSOR_SIZE,24
env = HYPRCURSOR_SIZE,24
#####################
### LOOK AND FEEL ###
#####################
# Refer to https://wiki.hyprland.org/Configuring/Variables/
# https://wiki.hyprland.org/Configuring/Variables/#general
general {
gaps_in = 5
gaps_out = 20
border_size = 2
snap {
enabled = true
window_gap = 8
monitor_gap = 10
respect_gaps = false
border_overlap = false
}
# https://wiki.hyprland.org/Configuring/Variables/#variable-types for info about colors
col.active_border = rgba(33ccffee) rgba(00ff99ee) 45deg
col.inactive_border = rgba(595959aa)
# Set to true enable resizing windows by clicking and dragging on borders and gaps
resize_on_border = false
# Please see https://wiki.hyprland.org/Configuring/Tearing/ before you turn this on
allow_tearing = false
layout = dwindle
}
# https://wiki.hyprland.org/Configuring/Variables/#decoration
decoration {
rounding = 10
rounding_power = 2
# Change transparency of focused and unfocused windows
active_opacity = 1.0
inactive_opacity = 1.0
shadow {
enabled = true
range = 4
render_power = 3
color = rgba(1a1a1aee)
}
# https://wiki.hyprland.org/Configuring/Variables/#blur
blur {
enabled = true
size = 3
passes = 1
vibrancy = 0.1696
}
}
# https://wiki.hyprland.org/Configuring/Variables/#animations
animations {
enabled = 0
# Default animations, see https://wiki.hyprland.org/Configuring/Animations/ for more
bezier = easeOutQuint,0.23,1,0.32,1
bezier = easeInOutCubic,0.65,0.05,0.36,1
bezier = linear,0,0,1,1
bezier = almostLinear,0.5,0.5,0.75,1.0
bezier = quick,0.15,0,0.1,1
animation = global, 1, 10, default
animation = border, 1, 5.39, easeOutQuint
animation = windows, 1, 4.79, easeOutQuint
animation = windowsIn, 1, 4.1, easeOutQuint, popin 87%
animation = windowsOut, 1, 1.49, linear, popin 87%
animation = fadeIn, 1, 1.73, almostLinear
animation = fadeOut, 1, 1.46, almostLinear
animation = fade, 1, 3.03, quick
animation = layers, 1, 3.81, easeOutQuint
animation = layersIn, 1, 4, easeOutQuint, fade
animation = layersOut, 1, 1.5, linear, fade
animation = fadeLayersIn, 1, 1.79, almostLinear
animation = fadeLayersOut, 1, 1.39, almostLinear
animation = workspaces, 1, 1.94, almostLinear, fade
animation = workspacesIn, 1, 1.21, almostLinear, fade
animation = workspacesOut, 1, 1.94, almostLinear, fade
}
device {
name = test-mouse-1
enabled = true
}
# Ref https://wiki.hyprland.org/Configuring/Workspace-Rules/
# "Smart gaps" / "No gaps when only"
# uncomment all if you wish to use that.
# workspace = w[tv1], gapsout:0, gapsin:0
# workspace = f[1], gapsout:0, gapsin:0
# windowrulev2 = bordersize 0, floating:0, onworkspace:w[tv1]
# windowrulev2 = rounding 0, floating:0, onworkspace:w[tv1]
# windowrulev2 = bordersize 0, floating:0, onworkspace:f[1]
# windowrulev2 = rounding 0, floating:0, onworkspace:f[1]
# See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more
dwindle {
pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
preserve_split = true # You probably want this
split_bias = 1
}
# See https://wiki.hyprland.org/Configuring/Master-Layout/ for more
master {
new_status = master
}
scrolling {
fullscreen_on_one_column = true
column_width = 0.5
focus_fit_method = 1
follow_focus = true
follow_min_visible = 1
explicit_column_widths = 0.25, 0.333, 0.5, 0.667, 0.75, 1.0
wrap_focus = true
wrap_swapcol = true
}
# https://wiki.hyprland.org/Configuring/Variables/#misc
misc {
force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers
disable_hyprland_logo = false # If true disables the random hyprland logo / anime girl background. :(
}
#############
### INPUT ###
#############
# https://wiki.hyprland.org/Configuring/Variables/#input
input {
kb_layout = us
kb_variant =
kb_model =
kb_options =
kb_rules =
follow_mouse = 1
sensitivity = 0 # -1.0 - 1.0, 0 means no modification.
touchpad {
natural_scroll = false
}
}
# https://wiki.hyprland.org/Configuring/Variables/#gestures
gestures {
}
# Example per-device config
# See https://wiki.hyprland.org/Configuring/Keywords/#per-device-input-configs for more
device {
name = epic-mouse-v1
sensitivity = -0.5
}
debug {
disable_logs = false
}
###################
### KEYBINDINGS ###
###################
# See https://wiki.hyprland.org/Configuring/Keywords/
$mainMod = SUPER # Sets "Windows" key as main modifier
# Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more
bind = $mainMod, Q, exec, $terminal
bind = $mainMod, C, killactive,
bind = $mainMod, M, exit,
bind = $mainMod, E, exec, $fileManager
bind = $mainMod, V, togglefloating,
bind = $mainMod, R, exec, $menu
bind = $mainMod, P, pseudo, # dwindle
bind = $mainMod, J, layoutmsg, togglesplit, # dwindle
# Move focus with mainMod + arrow keys
bind = $mainMod, left, movefocus, l
bind = $mainMod, right, movefocus, r
bind = $mainMod, up, movefocus, u
bind = $mainMod, down, movefocus, d
# Switch workspaces with mainMod + [0-9]
bind = $mainMod, 1, workspace, 1
bind = $mainMod, 2, workspace, 2
bind = $mainMod, 3, workspace, 3
bind = $mainMod, 4, workspace, 4
bind = $mainMod, 5, workspace, 5
bind = $mainMod, 6, workspace, 6
bind = $mainMod, 7, workspace, 7
bind = $mainMod, 8, workspace, 8
bind = $mainMod, 9, workspace, 9
bind = $mainMod, 0, workspace, 10
# Move active window to a workspace with mainMod + SHIFT + [0-9]
bind = $mainMod SHIFT, 1, movetoworkspace, 1
bind = $mainMod SHIFT, 2, movetoworkspace, 2
bind = $mainMod SHIFT, 3, movetoworkspace, 3
bind = $mainMod SHIFT, 4, movetoworkspace, 4
bind = $mainMod SHIFT, 5, movetoworkspace, 5
bind = $mainMod SHIFT, 6, movetoworkspace, 6
bind = $mainMod SHIFT, 7, movetoworkspace, 7
bind = $mainMod SHIFT, 8, movetoworkspace, 8
bind = $mainMod SHIFT, 9, movetoworkspace, 9
bind = $mainMod SHIFT, 0, movetoworkspace, 10
# Example special workspace (scratchpad)
bind = $mainMod, S, togglespecialworkspace, magic
bind = $mainMod SHIFT, S, movetoworkspace, special:magic
# Scroll through existing workspaces with mainMod + scroll
bind = $mainMod, mouse_down, workspace, e+1
bind = $mainMod, mouse_up, workspace, e-1
# Move/resize windows with mainMod + LMB/RMB and dragging
bindm = $mainMod, mouse:272, movewindow
bindm = $mainMod, mouse:273, resizewindow
# Laptop multimedia keys for volume and LCD brightness
bindel = ,XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+
bindel = ,XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
bindel = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
bindel = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
bindel = ,XF86MonBrightnessUp, exec, brightnessctl s 10%+
bindel = ,XF86MonBrightnessDown, exec, brightnessctl s 10%-
# Requires playerctl
bindl = , XF86AudioNext, exec, playerctl next
bindl = , XF86AudioPause, exec, playerctl play-pause
bindl = , XF86AudioPlay, exec, playerctl play-pause
bindl = , XF86AudioPrev, exec, playerctl previous
bind = $mainMod, u, submap, submap1
submap = submap1
bind = , u, submap, submap2
bind = , i, submap, submap3
bind = , o, exec, $terminal
bind = , p, submap, reset
submap = submap2, submap1
bind = , o, exec, $terminal
submap = submap3, reset
bind = , o, exec, $terminal
submap = reset
##############################
### WINDOWS AND WORKSPACES ###
##############################
windowrule {
# Ignore maximize requests from apps. You'll probably like this.
name = suppress-maximize-events
match:class = .*
suppress_event = maximize
}
windowrule {
# Fix some dragging issues with XWayland
name = fix-xwayland-drags
match:class = ^$
match:title = ^$
match:xwayland = true
match:float = true
match:fullscreen = false
match:pin = false
no_focus = true
}
workspace = n[s:window] w[tv1], gapsout:0, gapsin:0
workspace = n[s:window] f[1], gapsout:0, gapsin:0
windowrule {
name = smart-gaps-1
match:float = false
match:workspace = n[s:window] w[tv1]
border_size = 0
rounding = 0
}
windowrule {
name = smart-gaps-2
match:float = false
match:workspace = n[s:window] f[1]
border_size = 0
rounding = 0
}
windowrule {
name = wr-kitty-stuff
match:class = wr_kitty
float = true
size = 200 200
pin = false
}
windowrule {
name = tagged-kitty-floats
match:tag = tag_kitty
float = true
}
windowrule {
name = static-kitty-tag
match:class = tag_kitty
tag = +tag_kitty
}
gesture = 3, left, dispatcher, exec, kitty
gesture = 3, right, float
gesture = 3, up, close
gesture = 3, down, fullscreen
gesture = 3, down, mod:ALT, float
gesture = 3, horizontal, mod:ALT, workspace
gesture = 5, up, dispatcher, sendshortcut, , e, activewindow
gesture = 5, down, dispatcher, sendshortcut, , x, activewindow
gesture = 5, left, dispatcher, sendshortcut, , i, activewindow
gesture = 5, right, dispatcher, sendshortcut, , t, activewindow
gesture = 4, right, dispatcher, sendshortcut, , return, activewindow
gesture = 4, left, dispatcher, movecursortocorner, 1
gesturep = 2, right, float
+294
View File
@@ -0,0 +1,294 @@
-- Hyprtester Lua config
hl.monitor({ output = "HEADLESS-1", mode = "1920x1080@60", position = "auto-right", scale = "1" })
hl.monitor({ output = "HEADLESS-2", mode = "1920x1080@60", position = "auto-right", scale = "1" })
hl.monitor({ output = "HEADLESS-3", mode = "1920x1080@60", position = "auto-right", scale = "1" })
hl.monitor({ output = "HEADLESS-4", mode = "1920x1080@60", position = "auto-right", scale = "1" })
hl.monitor({ output = "HEADLESS-5", mode = "1920x1080@60", position = "auto-right", scale = "1" })
hl.monitor({ output = "HEADLESS-6", mode = "1920x1080@60", position = "auto-right", scale = "1" })
hl.monitor({ output = "HEADLESS-PERSISTENT-TEST", mode = "1920x1080@60", position = "auto-right", scale = "1" })
hl.monitor({ output = "", disabled = true })
local terminal = "kitty"
local fileManager = "dolphin"
local menu = "wofi --show drun"
hl.on("hyprland.start", function()
hl.dispatch(hl.dsp.exec_cmd("sleep 0"))
end)
hl.env("XCURSOR_SIZE", "24")
hl.env("HYPRCURSOR_SIZE", "24")
hl.config({
general = {
gaps_in = 5,
gaps_out = 20,
border_size = 2,
snap = {
enabled = true,
window_gap = 8,
monitor_gap = 10,
respect_gaps = false,
border_overlap = false,
},
col = {
active_border = { colors = { "rgba(33ccffee)", "rgba(00ff99ee)" }, angle = 45 },
inactive_border = "rgba(595959aa)",
},
resize_on_border = false,
allow_tearing = false,
layout = "dwindle",
},
})
hl.config({
decoration = {
rounding = 10,
rounding_power = 2,
active_opacity = 1.0,
inactive_opacity = 1.0,
shadow = {
enabled = true,
range = 4,
render_power = 3,
color = "rgba(1a1a1aee)",
},
blur = {
enabled = true,
size = 3,
passes = 1,
vibrancy = 0.1696,
},
},
})
hl.config({
animations = {
enabled = false,
},
})
hl.curve("easeOutQuint", { type = "bezier", points = { {0.23, 1}, {0.32, 1} } })
hl.curve("easeInOutCubic", { type = "bezier", points = { {0.65, 0.05}, {0.36, 1} } })
hl.curve("linear", { type = "bezier", points = { {0, 0}, {1, 1} } })
hl.curve("almostLinear", { type = "bezier", points = { {0.5, 0.5}, {0.75, 1.0} } })
hl.curve("quick", { type = "bezier", points = { {0.15, 0}, {0.1, 1} } })
hl.animation({ leaf = "global", enabled = true, speed = 10, bezier = "default" })
hl.animation({ leaf = "border", enabled = true, speed = 5.39, bezier = "easeOutQuint" })
hl.animation({ leaf = "windows", enabled = true, speed = 4.79, bezier = "easeOutQuint" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 4.1, bezier = "easeOutQuint", style = "popin 87%" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 1.49, bezier = "linear", style = "popin 87%" })
hl.animation({ leaf = "fadeIn", enabled = true, speed = 1.73, bezier = "almostLinear" })
hl.animation({ leaf = "fadeOut", enabled = true, speed = 1.46, bezier = "almostLinear" })
hl.animation({ leaf = "fade", enabled = true, speed = 3.03, bezier = "quick" })
hl.animation({ leaf = "layers", enabled = true, speed = 3.81, bezier = "easeOutQuint" })
hl.animation({ leaf = "layersIn", enabled = true, speed = 4, bezier = "easeOutQuint", style = "fade" })
hl.animation({ leaf = "layersOut", enabled = true, speed = 1.5, bezier = "linear", style = "fade" })
hl.animation({ leaf = "fadeLayersIn", enabled = true, speed = 1.79, bezier = "almostLinear" })
hl.animation({ leaf = "fadeLayersOut", enabled = true, speed = 1.39, bezier = "almostLinear" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 1.94, bezier = "almostLinear", style = "fade" })
hl.animation({ leaf = "workspacesIn", enabled = true, speed = 1.21, bezier = "almostLinear", style = "fade" })
hl.animation({ leaf = "workspacesOut", enabled = true, speed = 1.94, bezier = "almostLinear", style = "fade" })
hl.device({ name = "test-mouse-1", enabled = true })
hl.config({
dwindle = {
preserve_split = true,
split_bias = 1,
},
})
hl.config({
master = {
new_status = "master",
},
})
hl.config({
scrolling = {
fullscreen_on_one_column = true,
column_width = 0.5,
focus_fit_method = 1,
follow_focus = true,
follow_min_visible = 1,
explicit_column_widths = "0.25, 0.333, 0.5, 0.667, 0.75, 1.0",
wrap_focus = true,
wrap_swapcol = true,
},
})
hl.config({
misc = {
force_default_wallpaper = -1,
disable_hyprland_logo = false,
},
})
hl.config({
input = {
kb_layout = "us",
kb_variant = "",
kb_model = "",
kb_options = "",
kb_rules = "",
follow_mouse = 1,
sensitivity = 0,
touchpad = {
natural_scroll = false,
},
},
})
hl.device({
name = "epic-mouse-v1",
sensitivity = -0.5,
})
hl.config({
debug = {
disable_logs = false,
},
})
local mainMod = "SUPER"
hl.bind(mainMod .. " + Q", hl.dsp.exec_cmd(terminal))
hl.bind(mainMod .. " + C", hl.dsp.window.close())
hl.bind(mainMod .. " + M", hl.dsp.exit())
hl.bind(mainMod .. " + E", hl.dsp.exec_cmd(fileManager))
hl.bind(mainMod .. " + V", hl.dsp.window.float({ action = "toggle" }))
hl.bind(mainMod .. " + R", hl.dsp.exec_cmd(menu))
hl.bind(mainMod .. " + P", hl.dsp.window.pseudo())
hl.bind(mainMod .. " + J", hl.dsp.layout("togglesplit"))
hl.bind(mainMod .. " + left", hl.dsp.focus({ direction = "left" }))
hl.bind(mainMod .. " + right", hl.dsp.focus({ direction = "right" }))
hl.bind(mainMod .. " + up", hl.dsp.focus({ direction = "up" }))
hl.bind(mainMod .. " + down", hl.dsp.focus({ direction = "down" }))
for i = 1, 10 do
local key = i % 10
hl.bind(mainMod .. " + " .. key, hl.dsp.focus({ workspace = tostring(i) }))
hl.bind(mainMod .. " + SHIFT + " .. key, hl.dsp.window.move({ workspace = tostring(i) }))
end
hl.bind(mainMod .. " + S", hl.dsp.workspace.toggle_special("magic"))
hl.bind(mainMod .. " + SHIFT + S", hl.dsp.window.move({ workspace = "special:magic" }))
hl.bind(mainMod .. " + mouse_down", hl.dsp.focus({ workspace = "e+1" }))
hl.bind(mainMod .. " + mouse_up", hl.dsp.focus({ workspace = "e-1" }))
hl.bind(mainMod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
hl.bind(mainMod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true })
hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+"), { locked = true, repeating = true })
hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"), { locked = true, repeating = true })
hl.bind("XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"), { locked = true, repeating = true })
hl.bind("XF86AudioMicMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"), { locked = true, repeating = true })
hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd("brightnessctl s 10%+"), { locked = true, repeating = true })
hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd("brightnessctl s 10%-"), { locked = true, repeating = true })
hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"), { locked = true })
hl.bind("XF86AudioPause", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true })
hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true })
hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true })
hl.bind(mainMod .. " + u", hl.dsp.submap("submap1"))
hl.define_submap("submap1", function()
hl.bind("u", hl.dsp.submap("submap2"))
hl.bind("i", hl.dsp.submap("submap3"))
hl.bind("o", hl.dsp.exec_cmd(terminal))
hl.bind("p", hl.dsp.submap("reset"))
end)
hl.define_submap("submap2", "submap1", function()
hl.bind("o", hl.dsp.exec_cmd(terminal))
end)
hl.define_submap("submap3", "reset", function()
hl.bind("o", hl.dsp.exec_cmd(terminal))
end)
hl.window_rule({
name = "suppress-maximize-events",
match = { class = ".*" },
suppress_event = "maximize",
})
hl.window_rule({
name = "fix-xwayland-drags",
match = {
class = "^$",
title = "^$",
xwayland = true,
float = true,
fullscreen = false,
pin = false,
},
no_focus = true,
})
hl.workspace_rule({ workspace = "n[s:window] w[tv1]", gaps_out = { top = 0, right = 0, bottom = 0, left = 0 }, gaps_in = { top = 0, right = 0, bottom = 0, left = 0 } })
hl.workspace_rule({ workspace = "n[s:window] f[1]", gaps_out = { top = 0, right = 0, bottom = 0, left = 0 }, gaps_in = { top = 0, right = 0, bottom = 0, left = 0 } })
hl.window_rule({
name = "smart-gaps-1",
match = { float = false, workspace = "n[s:window] w[tv1]" },
border_size = 0,
rounding = 0,
})
hl.window_rule({
name = "smart-gaps-2",
match = { float = false, workspace = "n[s:window] f[1]" },
border_size = 0,
rounding = 0,
})
hl.window_rule({
name = "wr-kitty-stuff",
match = { class = "wr_kitty" },
float = true,
size = "200 200",
pin = false,
})
hl.window_rule({
name = "tagged-kitty-floats",
match = { tag = "tag_kitty" },
float = true,
})
hl.window_rule({
name = "static-kitty-tag",
match = { class = "tag_kitty" },
tag = "+tag_kitty",
})
hl.gesture({
fingers = 3,
direction = "left",
action = function()
hl.dispatch(hl.dsp.exec_cmd("kitty"))
end,
})
hl.gesture({ fingers = 3, direction = "right", action = "float" })
hl.gesture({ fingers = 3, direction = "up", action = "close" })
hl.gesture({ fingers = 3, direction = "down", action = "fullscreen" })
hl.gesture({ fingers = 3, direction = "down", mods = "ALT", action = "float" })
hl.gesture({ fingers = 3, direction = "horizontal", mods = "ALT", action = "workspace" })
hl.gesture({ fingers = 5, direction = "up", action = function() hl.dispatch(hl.dsp.send_shortcut({ mods = "", key = "e", window = "activewindow" })) end })
hl.gesture({ fingers = 5, direction = "down", action = function() hl.dispatch(hl.dsp.send_shortcut({ mods = "", key = "x", window = "activewindow" })) end })
hl.gesture({ fingers = 5, direction = "left", action = function() hl.dispatch(hl.dsp.send_shortcut({ mods = "", key = "i", window = "activewindow" })) end })
hl.gesture({ fingers = 5, direction = "right", action = function() hl.dispatch(hl.dsp.send_shortcut({ mods = "", key = "t", window = "activewindow" })) end })
hl.gesture({ fingers = 4, direction = "right", action = function() hl.dispatch(hl.dsp.send_shortcut({ mods = "", key = "return", window = "activewindow" })) end })
hl.gesture({ fingers = 4, direction = "left", action = function() hl.dispatch(hl.dsp.cursor.move_to_corner({ corner = 1, window = "activewindow" })) end })
hl.gesture({ fingers = 2, direction = "right", action = "float", disable_inhibit = true })
+745
View File
@@ -0,0 +1,745 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import re
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Iterable
@dataclass
class ApiNode:
methods: set[str] = field(default_factory=set)
children: dict[str, "ApiNode"] = field(default_factory=dict)
@dataclass
class ObjectClass:
name: str
methods: set[str] = field(default_factory=set)
fields: dict[str, str] = field(default_factory=dict)
def read_text(path: Path) -> str:
return path.read_text(encoding="utf-8")
def find_matching_brace(text: str, open_brace_idx: int) -> int:
depth = 0
in_string = False
string_char = ""
escaped = False
for i in range(open_brace_idx, len(text)):
c = text[i]
if in_string:
if escaped:
escaped = False
continue
if c == "\\":
escaped = True
continue
if c == string_char:
in_string = False
continue
if c in ('"', "'"):
in_string = True
string_char = c
continue
if c == "{":
depth += 1
elif c == "}":
depth -= 1
if depth == 0:
return i
raise ValueError("Unbalanced braces while parsing C++ source")
def extract_function_bodies(source: str, header_pattern: re.Pattern[str]) -> list[tuple[re.Match[str], str]]:
out: list[tuple[re.Match[str], str]] = []
for match in header_pattern.finditer(source):
open_idx = source.find("{", match.end() - 1)
if open_idx < 0:
continue
close_idx = find_matching_brace(source, open_idx)
out.append((match, source[open_idx + 1 : close_idx]))
return out
def merge_node(dst: ApiNode, src: ApiNode) -> None:
dst.methods |= src.methods
for key, child in src.children.items():
if key not in dst.children:
dst.children[key] = child
else:
merge_node(dst.children[key], child)
def parse_binding_tree(root: Path) -> tuple[ApiNode, set[str]]:
bindings_dir = root / "src/config/lua/bindings"
root_node = ApiNode()
callable_namespaces: set[str] = set()
register_header = re.compile(
r"void\s+Internal::register\w+Bindings\s*\([^)]*\)\s*\{", re.MULTILINE
)
set_fn = re.compile(r'Internal::set(?:Mgr)?Fn\(\s*L\s*,(?:\s*mgr\s*,)?\s*"([^"]+)"\s*,')
set_field = re.compile(r'lua_setfield\(L,\s*-2,\s*"([^"]+)"\s*\);')
for cpp in sorted(bindings_dir.glob("*.cpp")):
source = read_text(cpp)
for _, body in extract_function_bodies(source, register_header):
local_root = ApiNode()
stack: list[ApiNode] = [local_root]
if re.search(
r'lua_setfield\(L,\s*-2,\s*"__call"\s*\);.*?lua_setfield\(L,\s*-2,\s*"([^"]+)"\s*\);',
body,
flags=re.DOTALL,
):
for ns in re.findall(
r'lua_setfield\(L,\s*-2,\s*"__call"\s*\);.*?lua_setfield\(L,\s*-2,\s*"([^"]+)"\s*\);',
body,
flags=re.DOTALL,
):
callable_namespaces.add(ns)
for raw_line in body.splitlines():
line = raw_line.strip()
if not line:
continue
if "lua_newtable(L)" in line:
stack.append(ApiNode())
continue
m = set_fn.search(line)
if m:
stack[-1].methods.add(m.group(1))
continue
if "lua_setmetatable(L" in line:
if len(stack) > 1:
stack.pop()
continue
m = set_field.search(line)
if m:
field_name = m.group(1)
if field_name == "__call":
continue
if len(stack) > 1:
node = stack.pop()
if field_name in stack[-1].children:
merge_node(stack[-1].children[field_name], node)
else:
stack[-1].children[field_name] = node
merge_node(root_node, local_root)
return root_node, callable_namespaces
def parse_object_classes(root: Path) -> dict[str, ObjectClass]:
objects_dir = root / "src/config/lua/objects"
mt_regex = re.compile(r'static constexpr const char\* MT = "([^"]+)";')
index_header = re.compile(r"static int\s+\w*Index\s*\(lua_State\* L\)\s*\{", re.MULTILINE)
cond_regex = re.compile(r"(?:if|else\s+if)\s*\(([^)]*\bkey\b[^)]*)\)")
push_class_regex = re.compile(r"Objects::CLua([A-Za-z0-9_]+)::push")
out: dict[str, ObjectClass] = {}
for cpp in sorted(objects_dir.glob("*.cpp")):
source = read_text(cpp)
mt_match = mt_regex.search(source)
if not mt_match:
continue
mt_name = mt_match.group(1)
if not mt_name.startswith("HL."):
continue
class_name = mt_name
obj = ObjectClass(name=class_name)
bodies = extract_function_bodies(source, index_header)
if not bodies:
out[class_name] = obj
continue
body = bodies[0][1]
cond_matches = list(cond_regex.finditer(body))
for i, cond in enumerate(cond_matches):
start = cond.start()
end = cond_matches[i + 1].start() if i + 1 < len(cond_matches) else len(body)
segment = body[start:end]
keys = re.findall(r'"([^"]+)"', cond.group(1))
if not keys:
continue
is_method = "lua_pushcfunction" in segment
if is_method:
for key in keys:
obj.methods.add(key)
continue
inferred_types: set[str] = set()
if "lua_pushboolean" in segment:
inferred_types.add("boolean")
if "lua_pushstring" in segment or "lua_pushfstring" in segment:
inferred_types.add("string")
if "lua_pushinteger" in segment:
inferred_types.add("integer")
if "lua_pushnumber" in segment:
inferred_types.add("number")
if "lua_newtable" in segment:
inferred_types.add("table")
if "lua_pushnil" in segment:
inferred_types.add("nil")
for pushed in push_class_regex.findall(segment):
inferred_types.add(f"HL.{pushed}")
if not inferred_types:
type_str = "any"
else:
ordered = sorted(inferred_types, key=lambda t: (t == "nil", t))
type_str = "|".join(ordered)
for key in keys:
if key in obj.fields:
existing = set(obj.fields[key].split("|"))
merged = existing | set(type_str.split("|"))
obj.fields[key] = "|".join(sorted(merged, key=lambda t: (t == "nil", t)))
else:
obj.fields[key] = type_str
out[class_name] = obj
return out
def lua_type_from_config_ctor(ctor: str) -> str:
mapping = {
"CLuaConfigBool": "boolean",
"CLuaConfigInt": "integer|boolean",
"CLuaConfigFloat": "number|boolean",
"CLuaConfigString": "string",
"CLuaConfigColor": "string",
"CLuaConfigVec2": "HL.Vec2Like",
"CLuaConfigCssGap": "integer|HL.CssGap",
"CLuaConfigFontWeight": "integer|string",
"CLuaConfigGradient": "string|HL.Gradient",
}
return mapping.get(ctor, "any")
def parse_config_values(root: Path) -> dict[str, str]:
cfg = root / "src/config/values/ConfigValues.cpp"
source = read_text(cfg)
pattern = re.compile(r'MS<([A-Za-z0-9_]+)>\("([^"]+)"')
type_map = {
"Bool": "boolean",
"Int": "integer|boolean",
"Float": "number|boolean",
"String": "string",
"Color": "string",
"Vec2": "HL.Vec2Like",
"CssGap": "integer|HL.CssGap",
"FontWeight": "integer|string",
"Gradient": "string|HL.Gradient",
}
out: dict[str, str] = {}
for vtype, key in pattern.findall(source):
out[key.replace(":", ".").replace("-", "_")] = type_map.get(vtype, "any")
return out
def extract_initializer_body(source: str, array_name: str) -> str:
marker = f"{array_name}[]"
idx = source.find(marker)
if idx < 0:
return ""
eq_idx = source.find("=", idx)
if eq_idx < 0:
return ""
open_idx = source.find("{", eq_idx)
if open_idx < 0:
return ""
close_idx = find_matching_brace(source, open_idx)
return source[open_idx + 1 : close_idx]
def parse_descriptor_fields(root: Path) -> dict[str, dict[str, str]]:
source = read_text(root / "src/config/lua/bindings/LuaBindingsConfigRules.cpp")
arrays = {
"MONITOR_FIELDS": "HL.MonitorSpec",
"DEVICE_FIELDS": "HL.DeviceSpec",
"WORKSPACE_RULE_FIELDS": "HL.WorkspaceRuleSpec",
"WINDOW_RULE_EFFECT_DESCS": "HL.WindowRuleSpec",
"LAYER_RULE_EFFECT_DESCS": "HL.LayerRuleSpec",
}
entry_regex = re.compile(
r'\{\s*"([^"]+)"\s*,\s*\[\]\(\)\s*->\s*ILuaConfigValue\*\s*\{\s*return\s+new\s+([A-Za-z0-9_]+)\((.*?)\);\s*\}',
re.DOTALL,
)
out: dict[str, dict[str, str]] = {class_name: {} for class_name in arrays.values()}
for array_name, class_name in arrays.items():
body = extract_initializer_body(source, array_name)
if not body:
continue
for name, ctor, _ in entry_regex.findall(body):
out[class_name][name] = lua_type_from_config_ctor(ctor)
# required / conventional fields not included in descriptor arrays
out["HL.MonitorSpec"]["output"] = "string"
out["HL.DeviceSpec"]["name"] = "string"
out["HL.WorkspaceRuleSpec"]["workspace"] = "string"
out["HL.WorkspaceRuleSpec"]["enabled"] = "boolean"
out["HL.WorkspaceRuleSpec"]["layout_opts"] = "table<string, string|number|boolean>"
out["HL.WindowRuleSpec"]["name"] = "string"
out["HL.WindowRuleSpec"]["enabled"] = "boolean"
out["HL.WindowRuleSpec"]["match"] = "table<string, string|number|boolean>"
out["HL.LayerRuleSpec"]["name"] = "string"
out["HL.LayerRuleSpec"]["enabled"] = "boolean"
out["HL.LayerRuleSpec"]["match"] = "table<string, string|boolean>"
return out
def parse_known_events(root: Path) -> list[str]:
source = read_text(root / "src/config/lua/LuaEventHandler.cpp")
block_match = re.search(
r"static const std::unordered_set<std::string> EVENTS = \{(.*?)\};",
source,
flags=re.DOTALL,
)
if not block_match:
return []
events = sorted(set(re.findall(r'"([^"]+)"', block_match.group(1))))
return events
def helper_to_lua_type(helper: str) -> str:
mapping = {
"Str": "string",
"Num": "number",
"Bool": "boolean",
"Monitor": "HL.MonitorSelector",
"Workspace": "HL.WorkspaceSelector",
"Window": "HL.WindowSelector",
"MonitorSelector": "string",
"WorkspaceSelector": "string",
"WindowSelector": "string",
}
return mapping.get(helper, "any")
def query_struct_to_type(struct_name: str) -> str:
name = struct_name
if name.startswith("S") and len(name) > 1:
name = name[1:]
if name.endswith("Query"):
name = name + "Filter"
return f"HL.{name}"
def parse_query_filter_types(root: Path) -> tuple[dict[str, dict[str, str]], dict[str, str]]:
source = read_text(root / "src/config/lua/bindings/LuaBindingsQuery.cpp")
parse_header = re.compile(
r"static void\s+(\w+)\s*\(\s*lua_State\* L,\s*int idx,\s*const char\* fnName,\s*(\w+)&\s*\w+\s*\)\s*\{"
)
query_types: dict[str, dict[str, str]] = {}
parse_fn_to_type: dict[str, str] = {}
for m, body in extract_function_bodies(source, parse_header):
parse_fn = m.group(1)
struct_name = m.group(2)
type_name = query_struct_to_type(struct_name)
parse_fn_to_type[parse_fn] = type_name
query_types.setdefault(type_name, {})
direct_assign = re.finditer(
r'query\.([A-Za-z_][A-Za-z0-9_]*)\s*=\s*Internal::tableOpt([A-Za-z_]+)\(L,\s*idx,\s*"([^"]+)"',
body,
)
for dm in direct_assign:
field_name = dm.group(3)
helper = dm.group(2)
query_types[type_name][field_name] = helper_to_lua_type(helper)
helper_calls = re.finditer(r'Internal::tableOpt([A-Za-z_]+)\(L,\s*idx,\s*"([^"]+)"', body)
for hm in helper_calls:
helper = hm.group(1)
field_name = hm.group(2)
query_types[type_name].setdefault(field_name, helper_to_lua_type(helper))
api_overrides: dict[str, str] = {}
for parse_fn, type_name in parse_fn_to_type.items():
for api in re.findall(rf'{parse_fn}\(L,\s*1,\s*"([^"]+)"\s*,', source):
if api == "hl.get_windows":
api_overrides[api] = f"fun(filters?: {type_name}): HL.Window[]"
elif api == "hl.get_layers":
api_overrides[api] = f"fun(filters?: {type_name}): HL.LayerSurface[]"
else:
api_overrides[api] = f"fun(filters?: {type_name}): any"
return query_types, api_overrides
def namespace_class_name(path: list[str]) -> str:
if not path:
return "HL.API"
parts = [p[:1].upper() + p[1:] for p in path]
return f"HL.{''.join(parts)}Namespace"
def format_union_alias(name: str, values: Iterable[str]) -> list[str]:
values = list(values)
if not values:
return []
lines = [f"---@alias {name}"]
for value in values:
lines.append(f'---| "{value}"')
return lines
def emit_class_block(class_name: str, fields: list[tuple[str, str, bool]], operator_call: str | None = None) -> list[str]:
lines = [f"---@class {class_name}"]
if operator_call:
lines.append(f"---@operator call:{operator_call}")
for field_name, type_name, optional in fields:
if field_name.startswith("[") and field_name.endswith("]"):
# preformatted index field, e.g. [string]
lines.append(f"---@field {field_name} {type_name}")
continue
if re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", field_name):
suffix = "?" if optional else ""
lines.append(f"---@field {field_name}{suffix} {type_name}")
continue
quoted = field_name.replace("'", "\\'")
type_with_optional = f"{type_name}|nil" if optional else type_name
lines.append(f"---@field ['{quoted}'] {type_with_optional}")
local_name = "__" + class_name.replace(".", "_")
lines.append(f"local {local_name} = {{}}")
return lines
def generate_stub(root: Path) -> str:
api_tree, callable_namespaces = parse_binding_tree(root)
object_classes = parse_object_classes(root)
config_values = parse_config_values(root)
descriptor_classes = parse_descriptor_fields(root)
events = parse_known_events(root)
query_types, query_overrides = parse_query_filter_types(root)
api_signatures: dict[str, str] = {
"hl.on": "fun(event: HL.EventName, cb: fun(...)): HL.EventSubscription",
"hl.bind": "fun(keys: string, dispatcher: function, opts?: HL.BindOptions): HL.Keybind",
"hl.define_submap": "fun(name: string, reset_or_fn: string|function, fn?: function): nil",
"hl.timer": "fun(callback: function, opts: HL.TimerOptions): HL.Timer",
"hl.config": "fun(config: table): nil",
"hl.get_config": "fun(key: HL.ConfigKey|string): any, string?",
"hl.device": "fun(spec: HL.DeviceSpec): nil",
"hl.monitor": "fun(spec: HL.MonitorSpec): nil",
"hl.window_rule": "fun(spec: HL.WindowRuleSpec): HL.WindowRule",
"hl.layer_rule": "fun(spec: HL.LayerRuleSpec): HL.LayerRule",
"hl.workspace_rule": "fun(spec: HL.WorkspaceRuleSpec): nil",
"hl.permission": "fun(spec: HL.PermissionSpec): nil",
"hl.gesture": "fun(spec: HL.GestureSpec): nil",
"hl.get_windows": "fun(filters?: HL.WindowQueryFilter): HL.Window[]",
"hl.get_window": "fun(selector: HL.WindowSelector): HL.Window|nil",
"hl.get_active_window": "fun(): HL.Window|nil",
"hl.get_urgent_window": "fun(): HL.Window|nil",
"hl.get_workspaces": "fun(): HL.Workspace[]",
"hl.get_workspace": "fun(selector: HL.WorkspaceSelector): HL.Workspace|nil",
"hl.get_active_workspace": "fun(monitor?: HL.MonitorSelector): HL.Workspace|nil",
"hl.get_active_special_workspace": "fun(monitor?: HL.MonitorSelector): HL.Workspace|nil",
"hl.get_monitors": "fun(): HL.Monitor[]",
"hl.get_monitor": "fun(selector: HL.MonitorSelector): HL.Monitor|nil",
"hl.get_active_monitor": "fun(): HL.Monitor|nil",
"hl.get_monitor_at": "fun(x: number|HL.Vec2, y?: number): HL.Monitor|nil",
"hl.get_monitor_at_cursor": "fun(): HL.Monitor|nil",
"hl.get_layers": "fun(filters?: HL.LayerQueryFilter): HL.LayerSurface[]",
"hl.get_workspace_windows": "fun(workspace: HL.WorkspaceSelector): HL.Window[]",
"hl.get_cursor_pos": "fun(): HL.Vec2|nil",
"hl.get_last_window": "fun(): HL.Window|nil",
"hl.get_last_workspace": "fun(monitor?: HL.MonitorSelector): HL.Workspace|nil",
"hl.get_current_submap": "fun(): string",
"hl.notification.create": "fun(opts?: HL.NotificationOptions): HL.Notification",
"hl.notification.get": "fun(): HL.Notification[]",
"hl.exec_cmd": "fun(cmd: string, rules?: table<string, string|number|boolean>): nil",
}
api_signatures.update(query_overrides)
lines: list[str] = []
lines.append("-- This file is autogenerated. Do not edit by hand.")
lines.append("-- Generator: scripts/generateLuaStubs.py")
lines.append("---@meta")
lines.append("")
lines.extend(format_union_alias("HL.EventName", events))
lines.append("")
lines.extend(format_union_alias("HL.ConfigKey", sorted(config_values.keys())))
lines.append("")
lines.append("---@alias HL.MonitorSelector string|integer|HL.Monitor")
lines.append("---@alias HL.WorkspaceSelector string|integer|HL.Workspace")
lines.append("---@alias HL.WindowSelector string|integer|HL.Window")
lines.append("---@alias HL.Vec2Like HL.Vec2|{x:number, y:number}|{number, number}|string")
lines.append("---@alias HL.CssGap integer|{top?:integer, right?:integer, bottom?:integer, left?:integer}")
lines.append("---@alias HL.Gradient string|{colors:string[], angle?:number}")
lines.append("")
lines.extend(
emit_class_block(
"HL.Vec2",
[
("x", "number", False),
("y", "number", False),
],
)
)
lines.append("")
lines.extend(
emit_class_block(
"HL.BindOptions",
[
("repeating", "boolean", True),
("locked", "boolean", True),
("release", "boolean", True),
("non_consuming", "boolean", True),
("transparent", "boolean", True),
("ignore_mods", "boolean", True),
("dont_inhibit", "boolean", True),
("long_press", "boolean", True),
("submap_universal", "boolean", True),
("click", "boolean", True),
("drag", "boolean", True),
("description", "string", True),
("desc", "string", True),
("device", "{inclusive?: boolean, list?: string[]}", True),
],
)
)
lines.append("")
lines.extend(
emit_class_block(
"HL.TimerOptions",
[
("timeout", "integer", False),
("type", '"repeat"|"oneshot"', False),
],
)
)
lines.append("")
lines.extend(
emit_class_block(
"HL.GestureSpec",
[
("fingers", "integer", False),
("direction", "string", False),
("action", "string", False),
("mods", "string", True),
("scale", "number", True),
("arg", "string", True),
("arg2", "string", True),
("disable_inhibit", "boolean", True),
],
)
)
lines.append("")
lines.extend(
emit_class_block(
"HL.PermissionSpec",
[
("binary", "string", False),
("type", "string", False),
("allow", "string", False),
],
)
)
lines.append("")
lines.extend(
emit_class_block(
"HL.NotificationOptions",
[
("color", "string", True),
("timeout", "number", True),
("icon", "integer", True),
("font_size", "number", True),
],
)
)
lines.append("")
for class_name in sorted(query_types.keys()):
fields = [(name, typ, True) for name, typ in sorted(query_types[class_name].items())]
lines.extend(emit_class_block(class_name, fields))
lines.append("")
for class_name in sorted(descriptor_classes.keys()):
required_fields = {
("HL.MonitorSpec", "output"),
("HL.DeviceSpec", "name"),
("HL.WorkspaceRuleSpec", "workspace"),
}
fields: list[tuple[str, str, bool]] = []
for key, typ in sorted(descriptor_classes[class_name].items()):
optional = (class_name, key) not in required_fields
fields.append((key, typ, optional))
lines.extend(emit_class_block(class_name, fields))
lines.append("")
for class_name in sorted(object_classes.keys()):
obj = object_classes[class_name]
fields: list[tuple[str, str, bool]] = []
for key in sorted(obj.methods):
fields.append((key, f"fun(self: {class_name}, ...): any", False))
for key, typ in sorted(obj.fields.items()):
if key in obj.methods:
continue
fields.append((key, typ, False))
lines.extend(emit_class_block(class_name, fields))
lines.append("")
def emit_namespace(node: ApiNode, path: list[str]) -> None:
class_name = namespace_class_name(path)
fields: list[tuple[str, str, bool]] = []
full_prefix = "hl" + ("." + ".".join(path) if path else "")
for method in sorted(node.methods):
full_name = f"{full_prefix}.{method}"
method_type = api_signatures.get(full_name, "fun(...): any")
fields.append((method, method_type, False))
for child_name in sorted(node.children.keys()):
fields.append((child_name, namespace_class_name(path + [child_name]), False))
if path == ["plugin"]:
fields.append(("[string]", "any", False))
operator_call = None
if path and path[-1] in callable_namespaces:
operator_call = "fun(...): any"
lines.extend(emit_class_block(class_name, fields, operator_call=operator_call))
lines.append("")
for child_name in sorted(node.children.keys()):
emit_namespace(node.children[child_name], path + [child_name])
emit_namespace(api_tree, [])
lines.append("---@type HL.API")
lines.append("hl = {}")
lines.append("")
# include a tiny map of config key value types for users who query values dynamically
lines.append("---@class HL.ConfigValueTypes")
for key, typ in sorted(config_values.items()):
lines.append(f"---@field ['{key}'] {typ}")
lines.append("local __HL_ConfigValueTypes = {}")
lines.append("")
return "\n".join(lines)
def write_if_changed(path: Path, content: str) -> bool:
if path.exists():
existing = read_text(path)
if existing == content:
return False
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
return True
def main() -> int:
parser = argparse.ArgumentParser(description="Generate LuaLS stubs for Hyprland Lua config API")
parser.add_argument(
"--root",
type=Path,
default=Path(__file__).resolve().parents[1],
help="Repository root",
)
parser.add_argument(
"--output",
type=Path,
default=None,
help="Output .lua stub path (defaults to ./meta/hl.meta.lua)",
)
parser.add_argument(
"--check",
action="store_true",
help="Check mode: fail if output differs from generated content",
)
args = parser.parse_args()
root = args.root.resolve()
output = args.output.resolve() if args.output else root / "meta/hl.meta.lua"
content = generate_stub(root)
if args.check:
if not output.exists():
print(f"[lua-stubs] missing generated file: {output}", file=sys.stderr)
return 1
existing = read_text(output)
if existing != content:
print(f"[lua-stubs] generated stubs are out of date: {output}", file=sys.stderr)
return 1
print(f"[lua-stubs] up to date: {output}")
return 0
changed = write_if_changed(output, content)
state = "updated" if changed else "unchanged"
print(f"[lua-stubs] {state}: {output}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+1181
View File
File diff suppressed because it is too large Load Diff
+6 -1
View File
@@ -36,9 +36,11 @@
libxkbcommon,
libuuid,
libgbm,
lua5_5,
muparser,
pango,
pciutils,
python3,
re2,
systemd,
tomlplusplus,
@@ -116,13 +118,14 @@ customStdenv.mkDerivation (finalAttrs: {
../hyprland.pc.in
../hyprpm
../LICENSE
../meta
../protocols
../src
../start
../systemd
../VERSION
(fs.fileFilter (file: file.hasExt "1") ../docs)
(fs.fileFilter (file: file.hasExt "conf" || file.hasExt "in") ../example)
(fs.fileFilter (file: file.hasExt "conf" || file.hasExt "in" || file.hasExt "lua" ) ../example)
(fs.fileFilter (file: file.hasExt "sh") ../scripts)
(fs.fileFilter (file: file.name == "CMakeLists.txt") ../.)
(optional withTests [
@@ -160,6 +163,7 @@ customStdenv.mkDerivation (finalAttrs: {
makeWrapper
cmake
pkg-config
python3
];
outputs = [
@@ -190,6 +194,7 @@ customStdenv.mkDerivation (finalAttrs: {
libuuid
libxcursor
libxkbcommon
lua5_5
muparser
pango
pciutils
+2 -2
View File
@@ -43,7 +43,7 @@ in
};
# Test configuration
environment.etc."test.conf".source = "${hyprland}/share/hypr/test.conf";
environment.etc."test.lua".source = "${hyprland}/share/hypr/test.lua";
# Disable portals
xdg.portal.enable = pkgs.lib.mkForce false;
@@ -87,7 +87,7 @@ in
# Run hyprtester testing framework/suite
print("Running hyprtester")
exit_status, _out = machine.execute("su - alice -c 'hyprtester -b ${hyprland}/bin/Hyprland -c /etc/test.conf -p ${hyprland}/lib/hyprtestplugin.so 2>&1 | tee /tmp/testerlog; exit ''${PIPESTATUS[0]}'")
exit_status, _out = machine.execute("su - alice -c 'hyprtester -b ${hyprland}/bin/Hyprland -c /etc/test.lua -p ${hyprland}/lib/hyprtestplugin.so 2>&1 | tee /tmp/testerlog; exit ''${PIPESTATUS[0]}'")
print(f"Hyprtester exited with {exit_status}")
# Print logs for visibility in CI
+24 -25
View File
@@ -2,6 +2,7 @@
#include <re2/re2.h>
#include "Compositor.hpp"
#include "config/supplementary/executor/Executor.hpp"
#include "debug/log/Logger.hpp"
#include "desktop/DesktopTypes.hpp"
#include "desktop/state/FocusState.hpp"
@@ -534,7 +535,7 @@ void CCompositor::cleanEnvironment() {
"dbus-update-activation-environment 2>/dev/null && "
#endif
"dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP HYPRLAND_INSTANCE_SIGNATURE QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS";
CKeybindManager::spawn(CMD);
Config::Supplementary::executor()->spawn(CMD);
}
}
@@ -652,6 +653,9 @@ void CCompositor::initManagers(eManagersInitStage stage) {
Log::logger->log(Log::DEBUG, "Creating the TokenManager!");
g_pTokenManager = makeUnique<CTokenManager>();
// create executor
Config::Supplementary::executor();
Config::mgr()->init();
Log::logger->log(Log::DEBUG, "Creating the PointerManager!");
@@ -781,7 +785,7 @@ void CCompositor::startCompositor() {
"dbus-update-activation-environment 2>/dev/null && "
#endif
"dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP HYPRLAND_INSTANCE_SIGNATURE QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS";
CKeybindManager::spawn(CMD);
Config::Supplementary::executor()->spawn(CMD);
}
Log::logger->log(Log::DEBUG, "Running on WAYLAND_DISPLAY: {}", m_wlDisplaySocket);
@@ -900,12 +904,12 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint16_t prope
if (!PMONITOR)
return nullptr;
static auto PRESIZEONBORDER = CConfigValue<Hyprlang::INT>("general:resize_on_border");
static auto PBORDERSIZE = CConfigValue<Hyprlang::INT>("general:border_size");
static auto PBORDERGRABEXTEND = CConfigValue<Hyprlang::INT>("general:extend_border_grab_area");
static auto PSPECIALFALLTHRU = CConfigValue<Hyprlang::INT>("input:special_fallthrough");
static auto PMODALPARENTBLOCKING = CConfigValue<Hyprlang::INT>("general:modal_parent_blocking");
static auto PFOLLOWMOUSESHRINK = CConfigValue<Hyprlang::INT>("input:follow_mouse_shrink");
static auto PRESIZEONBORDER = CConfigValue<Config::INTEGER>("general:resize_on_border");
static auto PBORDERSIZE = CConfigValue<Config::INTEGER>("general:border_size");
static auto PBORDERGRABEXTEND = CConfigValue<Config::INTEGER>("general:extend_border_grab_area");
static auto PSPECIALFALLTHRU = CConfigValue<Config::INTEGER>("input:special_fallthrough");
static auto PMODALPARENTBLOCKING = CConfigValue<Config::INTEGER>("general:modal_parent_blocking");
static auto PFOLLOWMOUSESHRINK = CConfigValue<Config::INTEGER>("input:follow_mouse_shrink");
const auto BORDER_GRAB_AREA = *PRESIZEONBORDER ? *PBORDERSIZE + *PBORDERGRABEXTEND : 0;
const bool ONLY_PRIORITY = properties & Desktop::View::FOCUS_PRIORITY;
const bool FOLLOW_MOUSE_CHECK = properties & Desktop::View::FOLLOW_MOUSE_CHECK;
@@ -1470,8 +1474,8 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks
return nullptr;
// 0 -> history, 1 -> shared length
static auto PMETHOD = CConfigValue<Hyprlang::INT>("binds:focus_preferred_method");
static auto PMONITORFALLBACK = CConfigValue<Hyprlang::INT>("binds:window_direction_monitor_fallback");
static auto PMETHOD = CConfigValue<Config::INTEGER>("binds:focus_preferred_method");
static auto PMONITORFALLBACK = CConfigValue<Config::INTEGER>("binds:window_direction_monitor_fallback");
const auto POSA = box.pos();
const auto SIZEA = box.size();
@@ -1729,7 +1733,7 @@ bool CCompositor::isPointOnReservedArea(const Vector2D& point, const PHLMONITOR
}
std::optional<CBox> CCompositor::calculateX11WorkArea() {
static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>("xwayland:force_zero_scaling");
static auto PXWLFORCESCALEZERO = CConfigValue<Config::INTEGER>("xwayland:force_zero_scaling");
// We more than likely won't be able to calculate one
// and even if we could this is minor
if (m_monitors.size() > 1 || m_monitors.empty())
@@ -2007,7 +2011,7 @@ PHLMONITOR CCompositor::getMonitorFromString(const std::string& name) {
}
void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMonitor, bool noWarpCursor) {
static auto PHIDESPECIALONWORKSPACECHANGE = CConfigValue<Hyprlang::INT>("binds:hide_special_on_workspace_change");
static auto PHIDESPECIALONWORKSPACECHANGE = CConfigValue<Config::INTEGER>("binds:hide_special_on_workspace_change");
if (!pWorkspace || !pMonitor)
return;
@@ -2181,8 +2185,8 @@ void CCompositor::setWindowFullscreenClient(const PHLWINDOW PWINDOW, const eFull
}
void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, Desktop::View::SFullscreenState state) {
static auto PDIRECTSCANOUT = CConfigValue<Hyprlang::INT>("render:direct_scanout");
static auto PALLOWPINFULLSCREEN = CConfigValue<Hyprlang::INT>("binds:allow_pin_fullscreen");
static auto PDIRECTSCANOUT = CConfigValue<Config::INTEGER>("render:direct_scanout");
static auto PALLOWPINFULLSCREEN = CConfigValue<Config::INTEGER>("binds:allow_pin_fullscreen");
if (!validMapped(PWINDOW) || g_pCompositor->m_unsafeState)
return;
@@ -2428,7 +2432,7 @@ void CCompositor::warpCursorTo(const Vector2D& pos, bool force) {
// warpCursorTo should only be used for warps that
// should be disabled with no_warps
static auto PNOWARPS = CConfigValue<Hyprlang::INT>("cursor:no_warps");
static auto PNOWARPS = CConfigValue<Config::INTEGER>("cursor:no_warps");
if (*PNOWARPS && !force) {
const auto PMONITORNEW = getMonitorFromVector(pos);
@@ -2442,11 +2446,6 @@ void CCompositor::warpCursorTo(const Vector2D& pos, bool force) {
Desktop::focusState()->rawMonitorFocus(PMONITORNEW);
}
void CCompositor::closeWindow(PHLWINDOW pWindow) {
if (pWindow && validMapped(pWindow))
g_pXWaylandManager->sendCloseWindow(pWindow);
}
PHLLS CCompositor::getLayerSurfaceFromSurface(SP<CWLSurfaceResource> pSurface) {
std::pair<SP<CWLSurfaceResource>, bool> result = {pSurface, false};
@@ -2576,9 +2575,9 @@ std::vector<PHLWORKSPACE> CCompositor::getWorkspacesCopy() {
}
void CCompositor::performUserChecks() {
static auto PNOCHECKXDG = CConfigValue<Hyprlang::INT>("misc:disable_xdg_env_checks");
static auto PNOCHECKGUIUTILS = CConfigValue<Hyprlang::INT>("misc:disable_hyprland_guiutils_check");
static auto PNOWATCHDOG = CConfigValue<Hyprlang::INT>("misc:disable_watchdog_warning");
static auto PNOCHECKXDG = CConfigValue<Config::INTEGER>("misc:disable_xdg_env_checks");
static auto PNOCHECKGUIUTILS = CConfigValue<Config::INTEGER>("misc:disable_hyprland_guiutils_check");
static auto PNOWATCHDOG = CConfigValue<Config::INTEGER>("misc:disable_watchdog_warning");
if (!*PNOCHECKXDG) {
const auto CURRENT_DESKTOP_ENV = getenv("XDG_CURRENT_DESKTOP");
@@ -2674,7 +2673,7 @@ void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWor
pWindow->moveToWorkspace(pWorkspace);
pWindow->m_monitor = pWorkspace->m_monitor;
static auto PGROUPONMOVETOWORKSPACE = CConfigValue<Hyprlang::INT>("group:group_on_movetoworkspace");
static auto PGROUPONMOVETOWORKSPACE = CConfigValue<Config::INTEGER>("group:group_on_movetoworkspace");
if (*PGROUPONMOVETOWORKSPACE && visibleWindowsOnWorkspace == 1 && pFirstWindowOnWorkspace && pFirstWindowOnWorkspace != pWindow && pFirstWindowOnWorkspace->m_group &&
pWindow->canBeGroupedInto(pFirstWindowOnWorkspace->m_group)) {
pFirstWindowOnWorkspace->m_group->add(pWindow);
@@ -2751,7 +2750,7 @@ void CCompositor::checkMonitorOverlaps() {
}
void CCompositor::arrangeMonitors() {
static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>("xwayland:force_zero_scaling");
static auto PXWLFORCESCALEZERO = CConfigValue<Config::INTEGER>("xwayland:force_zero_scaling");
std::vector<PHLMONITOR> toArrange(m_monitors.begin(), m_monitors.end());
std::vector<PHLMONITOR> arranged;
-1
View File
@@ -143,7 +143,6 @@ class CCompositor {
PHLWINDOW getWindowByRegex(const std::string&);
void warpCursorTo(const Vector2D&, bool force = false);
PHLLS getLayerSurfaceFromSurface(SP<CWLSurfaceResource>);
void closeWindow(PHLWINDOW);
Vector2D parseWindowVectorArgsRelative(const std::string&, const Vector2D&);
[[nodiscard]] PHLWORKSPACE createNewWorkspace(const WORKSPACEID&, const MONITORID&, const std::string& name = "",
bool isEmpty = true); // will be deleted next frame if left empty and unfocused!
+43 -6
View File
@@ -1,6 +1,7 @@
#include "ConfigManager.hpp"
#include "supplementary/jeremy/Jeremy.hpp"
#include "legacy/ConfigManager.hpp"
#include "lua/ConfigManager.hpp"
#include "../debug/log/Logger.hpp"
#include <hyprutils/path/Path.hpp>
@@ -19,21 +20,49 @@ bool Config::initConfigManager() {
const auto CFG_PATH = Supplementary::Jeremy::getMainConfigPath();
if (!CFG_PATH) {
Log::logger->log(Log::CRIT, "Couldn't load config: {}", CFG_PATH.error());
Log::logger->log(Log::CRIT, "[cfg] Couldn't load config: {}", CFG_PATH.error());
return false;
}
std::filesystem::path filePath = *CFG_PATH;
std::filesystem::path filePath = CFG_PATH->path;
// TODO:
// filePath.replace_extension(".lua");
if (CFG_PATH->type == Supplementary::Jeremy::CONFIG_TYPE_REGULAR) {
Log::logger->log(Log::DEBUG, "[cfg] Regular config at {}", filePath.string());
g_mgr = makeUnique<Legacy::CConfigManager>();
std::error_code ec;
if (std::filesystem::exists(filePath, ec) && !ec && filePath.extension() == ".lua") {
// we have lua!
Log::logger->log(Log::DEBUG, "[cfg] Using lua config found at {}", filePath.string());
g_mgr = makeUnique<Lua::CConfigManager>();
} else {
filePath.replace_extension(".conf");
Log::logger->log(Log::DEBUG, "[cfg] Lua config not found, using legacy config at {}", filePath.string());
g_mgr = makeUnique<Legacy::CConfigManager>();
}
} else {
Log::logger->log(Log::DEBUG, "[cfg] Config is either explicit or special.");
if (filePath.extension() == ".lua" || filePath.extension() == "lua") {
Log::logger->log(Log::DEBUG, "[cfg] Config is lua, loading lua mgr");
g_mgr = makeUnique<Lua::CConfigManager>();
} else {
Log::logger->log(Log::DEBUG, "[cfg] Config is NOT lua, loading regular mgr");
g_mgr = makeUnique<Legacy::CConfigManager>();
}
}
RASSERT(g_mgr, "failed to create a suitable config manager");
std::error_code ec;
if (!std::filesystem::exists(filePath, ec) || ec) {
if (ec) {
Log::logger->log(Log::CRIT, "Couldn't load config: {}", ec.message());
Log::logger->log(Log::CRIT, "[cfg] Couldn't load config: {}", ec.message());
return false;
}
// generate default
if (const auto v = g_mgr->generateDefaultConfig(filePath); !v) {
Log::logger->log(Log::CRIT, "[cfg] Couldn't generate default config: {}", v.error());
return false;
}
}
@@ -44,3 +73,11 @@ bool Config::initConfigManager() {
UP<IConfigManager>& Config::mgr() {
return g_mgr;
}
const char* Config::typeToString(eConfigManagerType t) {
switch (t) {
case CONFIG_LUA: return "lua";
case CONFIG_LEGACY: return "hyprlang";
default: return "error";
}
}
+13
View File
@@ -7,8 +7,16 @@
#include "./shared/Types.hpp"
#include "../helpers/memory/Memory.hpp"
#include "values/types/IValue.hpp"
extern "C" {
struct lua_State;
}
namespace Config {
using PLUGIN_LUA_FN = int (*)(lua_State*);
struct SConfigOptionReply {
// <type>* const*
void* const* dataptr = nullptr;
@@ -21,6 +29,8 @@ namespace Config {
CONFIG_LUA
};
const char* typeToString(eConfigManagerType t);
class IConfigManager {
protected:
IConfigManager() = default;
@@ -55,6 +65,9 @@ namespace Config {
virtual std::expected<void, std::string> generateDefaultConfig(const std::filesystem::path&, bool safeMode = false) = 0;
virtual void handlePluginLoads() = 0;
virtual std::expected<void, std::string> registerPluginValue(void* handle, SP<Config::Values::IValue> value) = 0;
virtual void onPluginUnload(void* handle) = 0;
};
bool initConfigManager();
+11 -2
View File
@@ -1,9 +1,18 @@
#include "ConfigValue.hpp"
#include "ConfigManager.hpp"
void local__configValuePopulate(void* const** p, const std::string& val) {
void local__configValuePopulate(void* const** p, void* const** hlangp, std::type_index* ti, const std::string& val) {
const auto BIGP = Config::mgr()->getConfigValue(val);
*p = BIGP.dataptr;
RASSERT(BIGP.dataptr, "Something went really fucking wrong with config values");
*ti = std::type_index(*BIGP.type);
if (std::type_index(*BIGP.type) == typeid(void*) || std::type_index(*BIGP.type) == typeid(const char*)) {
// this is a special, cursed case. ew.
*hlangp = BIGP.dataptr;
} else
*p = BIGP.dataptr;
}
std::type_index local__configValueTypeIdx(const std::string& val) {
+46 -25
View File
@@ -2,42 +2,59 @@
#include <string>
#include <typeindex>
#include <typeinfo>
#include <hyprlang.hpp>
#include "../macros.hpp"
#include "../config/shared/complex/ComplexDataType.hpp"
#include "../config/shared/Types.hpp"
// Welcome to wonky fucking pointer + type hell
// Enjoy your stay
// giga hack to avoid including configManager here
// NOLINTNEXTLINE
void local__configValuePopulate(void* const** p, const std::string& val);
void local__configValuePopulate(void* const** p, void* const** hlangp, std::type_index* ti, const std::string& val);
std::type_index local__configValueTypeIdx(const std::string& val);
template <typename T>
class CConfigValue {
public:
// creates an empty value. Deref'ing this will be a crash
CConfigValue() = default;
CConfigValue(const std::string& val) {
#ifdef HYPRLAND_DEBUG
// verify type
const auto TYPE = local__configValueTypeIdx(val);
// TODO: fix this or leave it idk I'm tired.
// const auto TYPE = local__configValueTypeIdx(val);
// exceptions
const bool STRINGEX = (typeid(T) == typeid(std::string) && TYPE == typeid(Hyprlang::STRING));
const bool CUSTOMEX = (typeid(T) == typeid(Hyprlang::CUSTOMTYPE) && (TYPE == typeid(Hyprlang::CUSTOMTYPE*) || TYPE == typeid(void*) /* dunno why it does this? */));
// // exceptions
// const bool STRINGEX = (typeid(T) == typeid(std::string) && TYPE == typeid(Hyprlang::STRING));
// const bool CUSTOMEX = ((typeid(T) == typeid(Hyprlang::CUSTOMTYPE) || typeid(T) == typeid(Config::IComplexConfigValue)) &&
// (TYPE == typeid(Hyprlang::CUSTOMTYPE*) || TYPE == typeid(Config::IComplexConfigValue*) || TYPE == typeid(void*) /* dunno why it does this? */));
RASSERT(typeid(T) == TYPE || STRINGEX || CUSTOMEX, "Mismatched type in CConfigValue<T>, got {} but has {}", typeid(T).name(), TYPE.name());
// RASSERT(typeid(T) == TYPE || STRINGEX || CUSTOMEX, "Mismatched type in CConfigValue<T>, got {} but has {}", typeid(T).name(), TYPE.name());
#endif
local__configValuePopulate(&p_, val);
local__configValuePopulate(&m_p, &m_hlangp, &m_typeIndex, val);
}
T* ptr() const {
return *rc<T* const*>(p_);
return *rc<T* const*>(m_p);
}
T operator*() const {
return *ptr();
}
bool good() const {
return m_p || m_hlangp;
}
private:
void* const* p_ = nullptr;
void* const* m_p = nullptr;
void* const* m_hlangp = nullptr;
std::type_index m_typeIndex = typeid(void);
};
template <>
@@ -48,26 +65,30 @@ inline std::string* CConfigValue<std::string>::ptr() const {
template <>
inline std::string CConfigValue<std::string>::operator*() const {
return std::string{*rc<const Hyprlang::STRING*>(p_)};
if (m_typeIndex == typeid(std::string))
return **rc<const std::string* const*>(m_p);
else if (m_typeIndex == typeid(const char*))
return std::string{*rc<const Hyprlang::STRING*>(m_hlangp)};
else
RASSERT(false, "CConfigValue<std::string> on a FUCKED type");
return "FUCK";
}
template <>
inline Hyprlang::STRING* CConfigValue<Hyprlang::STRING>::ptr() const {
return rc<Hyprlang::STRING*>(*p_);
inline Config::INTEGER CConfigValue<Config::INTEGER>::operator*() const {
if (m_typeIndex == typeid(bool))
return **rc<const bool* const*>(m_p);
else if (m_typeIndex == typeid(Config::INTEGER))
return **rc<const Config::INTEGER* const*>(m_p);
else
RASSERT(false, "CConfigValue<Config::INTEGER> on a FUCKED type");
return -1;
}
template <>
inline Hyprlang::STRING CConfigValue<Hyprlang::STRING>::operator*() const {
return *rc<const Hyprlang::STRING*>(p_);
}
template <>
inline Hyprlang::CUSTOMTYPE* CConfigValue<Hyprlang::CUSTOMTYPE>::ptr() const {
return *rc<Hyprlang::CUSTOMTYPE* const*>(p_);
}
template <>
inline Hyprlang::CUSTOMTYPE CConfigValue<Hyprlang::CUSTOMTYPE>::operator*() const {
RASSERT(false, "Impossible to implement operator* of CConfigValue<Hyprlang::CUSTOMTYPE>, use ptr()");
return *ptr();
inline Config::IComplexConfigValue* CConfigValue<Config::IComplexConfigValue>::ptr() const {
if (m_hlangp)
return rc<Config::IComplexConfigValue*>((*rc<Hyprlang::CUSTOMTYPE* const*>(m_hlangp))->getData());
else
return *rc<Config::IComplexConfigValue* const*>(m_p);
}
+87 -379
View File
@@ -1,6 +1,8 @@
#include <re2/re2.h>
#include "ConfigManager.hpp"
#include "DefaultConfig.hpp"
#include "../values/ConfigValues.hpp"
#include "../shared/inotify/ConfigWatcher.hpp"
#include "../../managers/KeybindManager.hpp"
#include "../../Compositor.hpp"
@@ -26,7 +28,6 @@
#include "../../desktop/state/FocusState.hpp"
#include "../../layout/space/Space.hpp"
#include "../../layout/supplementary/WorkspaceAlgoMatcher.hpp"
#include "../defaultConfig.hpp"
#include "../../render/Renderer.hpp"
#include "../../errorOverlay/Overlay.hpp"
@@ -36,6 +37,15 @@
#include "../../managers/permissions/DynamicPermissionManager.hpp"
#include "../../notification/NotificationOverlay.hpp"
#include "../../plugins/PluginSystem.hpp"
#include "../values/types/IntValue.hpp"
#include "../values/types/FloatValue.hpp"
#include "../values/types/BoolValue.hpp"
#include "../values/types/StringValue.hpp"
#include "../values/types/ColorValue.hpp"
#include "../values/types/Vec2Value.hpp"
#include "../values/types/CssGapValue.hpp"
#include "../values/types/FontWeightValue.hpp"
#include "../values/types/GradientValue.hpp"
#include "../../managers/input/trackpad/TrackpadGestures.hpp"
#include "../../managers/input/trackpad/gestures/DispatcherGesture.hpp"
@@ -79,9 +89,7 @@ using namespace Config::Legacy;
using enum NContentType::eContentType;
//NOLINTNEXTLINE
extern "C" char** environ;
#include "../supplementary/ConfigDescriptions.hpp"
extern "C" char** environ;
WP<CConfigManager> Config::Legacy::mgr() {
if (Config::mgr() && Config::mgr()->type() == CONFIG_LEGACY)
@@ -469,373 +477,40 @@ void CConfigManager::registerConfigVar(const char* name, Hyprlang::CUSTOMTYPE&&
CConfigManager::CConfigManager() {
const auto ERR = verifyConfigExists();
m_mainConfigPath = *Supplementary::Jeremy::getMainConfigPath();
m_mainConfigPath = Supplementary::Jeremy::getMainConfigPath()->path;
m_configPaths.emplace_back(m_mainConfigPath);
m_config = makeUnique<Hyprlang::CConfig>(m_configPaths.begin()->c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = true});
registerConfigVar("general:border_size", Hyprlang::INT{1});
registerConfigVar("general:gaps_in", Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, "5"});
registerConfigVar("general:gaps_out", Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, "20"});
registerConfigVar("general:float_gaps", Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, "0"});
registerConfigVar("general:gaps_workspaces", Hyprlang::INT{0});
registerConfigVar("general:no_focus_fallback", Hyprlang::INT{0});
registerConfigVar("general:resize_on_border", Hyprlang::INT{0});
registerConfigVar("general:extend_border_grab_area", Hyprlang::INT{15});
registerConfigVar("general:hover_icon_on_border", Hyprlang::INT{1});
registerConfigVar("general:layout", {"dwindle"});
registerConfigVar("general:allow_tearing", Hyprlang::INT{0});
registerConfigVar("general:resize_corner", Hyprlang::INT{0});
registerConfigVar("general:snap:enabled", Hyprlang::INT{0});
registerConfigVar("general:snap:window_gap", Hyprlang::INT{10});
registerConfigVar("general:snap:monitor_gap", Hyprlang::INT{10});
registerConfigVar("general:snap:border_overlap", Hyprlang::INT{0});
registerConfigVar("general:snap:respect_gaps", Hyprlang::INT{0});
registerConfigVar("general:col.active_border", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0xffffffff"});
registerConfigVar("general:col.inactive_border", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0xff444444"});
registerConfigVar("general:col.nogroup_border", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0xffffaaff"});
registerConfigVar("general:col.nogroup_border_active", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0xffff00ff"});
registerConfigVar("general:modal_parent_blocking", Hyprlang::INT{1});
registerConfigVar("general:locale", {""});
for (const auto& v : Values::CONFIG_VALUES) {
const char* NAME = v->name();
registerConfigVar("misc:disable_hyprland_logo", Hyprlang::INT{0});
registerConfigVar("misc:disable_splash_rendering", Hyprlang::INT{0});
registerConfigVar("misc:col.splash", Hyprlang::INT{0x55ffffff});
registerConfigVar("misc:splash_font_family", {STRVAL_EMPTY});
registerConfigVar("misc:font_family", {"Sans"});
registerConfigVar("misc:force_default_wallpaper", Hyprlang::INT{-1});
registerConfigVar("misc:vrr", Hyprlang::INT{0});
registerConfigVar("misc:mouse_move_enables_dpms", Hyprlang::INT{0});
registerConfigVar("misc:key_press_enables_dpms", Hyprlang::INT{0});
registerConfigVar("misc:name_vk_after_proc", Hyprlang::INT{1});
registerConfigVar("misc:always_follow_on_dnd", Hyprlang::INT{1});
registerConfigVar("misc:layers_hog_keyboard_focus", Hyprlang::INT{1});
registerConfigVar("misc:animate_manual_resizes", Hyprlang::INT{0});
registerConfigVar("misc:animate_mouse_windowdragging", Hyprlang::INT{0});
registerConfigVar("misc:disable_autoreload", Hyprlang::INT{0});
registerConfigVar("misc:enable_swallow", Hyprlang::INT{0});
registerConfigVar("misc:swallow_regex", {STRVAL_EMPTY});
registerConfigVar("misc:swallow_exception_regex", {STRVAL_EMPTY});
registerConfigVar("misc:focus_on_activate", Hyprlang::INT{0});
registerConfigVar("misc:mouse_move_focuses_monitor", Hyprlang::INT{1});
registerConfigVar("misc:allow_session_lock_restore", Hyprlang::INT{0});
registerConfigVar("misc:session_lock_xray", Hyprlang::INT{0});
registerConfigVar("misc:close_special_on_empty", Hyprlang::INT{1});
registerConfigVar("misc:background_color", Hyprlang::INT{0xff111111});
registerConfigVar("misc:on_focus_under_fullscreen", Hyprlang::INT{2});
registerConfigVar("misc:exit_window_retains_fullscreen", Hyprlang::INT{0});
registerConfigVar("misc:initial_workspace_tracking", Hyprlang::INT{1});
registerConfigVar("misc:middle_click_paste", Hyprlang::INT{1});
registerConfigVar("misc:render_unfocused_fps", Hyprlang::INT{15});
registerConfigVar("misc:disable_xdg_env_checks", Hyprlang::INT{0});
registerConfigVar("misc:disable_hyprland_guiutils_check", Hyprlang::INT{0});
registerConfigVar("misc:disable_watchdog_warning", Hyprlang::INT{0});
registerConfigVar("misc:lockdead_screen_delay", Hyprlang::INT{1000});
registerConfigVar("misc:enable_anr_dialog", Hyprlang::INT{1});
registerConfigVar("misc:anr_missed_pings", Hyprlang::INT{5});
registerConfigVar("misc:screencopy_force_8b", Hyprlang::INT{1});
registerConfigVar("misc:disable_scale_notification", Hyprlang::INT{0});
registerConfigVar("misc:size_limits_tiled", Hyprlang::INT{0});
registerConfigVar("group:insert_after_current", Hyprlang::INT{1});
registerConfigVar("group:focus_removed_window", Hyprlang::INT{1});
registerConfigVar("group:merge_groups_on_drag", Hyprlang::INT{1});
registerConfigVar("group:merge_groups_on_groupbar", Hyprlang::INT{1});
registerConfigVar("group:merge_floated_into_tiled_on_groupbar", Hyprlang::INT{0});
registerConfigVar("group:auto_group", Hyprlang::INT{1});
registerConfigVar("group:drag_into_group", Hyprlang::INT{1});
registerConfigVar("group:group_on_movetoworkspace", Hyprlang::INT{0});
registerConfigVar("group:groupbar:enabled", Hyprlang::INT{1});
registerConfigVar("group:groupbar:font_family", {STRVAL_EMPTY});
registerConfigVar("group:groupbar:font_weight_active", Hyprlang::CConfigCustomValueType{&configHandleFontWeightSet, configHandleFontWeightDestroy, "normal"});
registerConfigVar("group:groupbar:font_weight_inactive", Hyprlang::CConfigCustomValueType{&configHandleFontWeightSet, configHandleFontWeightDestroy, "normal"});
registerConfigVar("group:groupbar:font_size", Hyprlang::INT{8});
registerConfigVar("group:groupbar:gradients", Hyprlang::INT{0});
registerConfigVar("group:groupbar:height", Hyprlang::INT{14});
registerConfigVar("group:groupbar:indicator_gap", Hyprlang::INT{0});
registerConfigVar("group:groupbar:indicator_height", Hyprlang::INT{3});
registerConfigVar("group:groupbar:priority", Hyprlang::INT{3});
registerConfigVar("group:groupbar:render_titles", Hyprlang::INT{1});
registerConfigVar("group:groupbar:scrolling", Hyprlang::INT{1});
registerConfigVar("group:groupbar:text_color", Hyprlang::INT{0xffffffff});
registerConfigVar("group:groupbar:text_color_inactive", Hyprlang::INT{-1});
registerConfigVar("group:groupbar:text_color_locked_active", Hyprlang::INT{-1});
registerConfigVar("group:groupbar:text_color_locked_inactive", Hyprlang::INT{-1});
registerConfigVar("group:groupbar:stacked", Hyprlang::INT{0});
registerConfigVar("group:groupbar:rounding", Hyprlang::INT{1});
registerConfigVar("group:groupbar:rounding_power", {2.F});
registerConfigVar("group:groupbar:gradient_rounding", Hyprlang::INT{2});
registerConfigVar("group:groupbar:gradient_rounding_power", {2.F});
registerConfigVar("group:groupbar:round_only_edges", Hyprlang::INT{1});
registerConfigVar("group:groupbar:gradient_round_only_edges", Hyprlang::INT{1});
registerConfigVar("group:groupbar:gaps_out", Hyprlang::INT{2});
registerConfigVar("group:groupbar:gaps_in", Hyprlang::INT{2});
registerConfigVar("group:groupbar:keep_upper_gap", Hyprlang::INT{1});
registerConfigVar("group:groupbar:text_offset", Hyprlang::INT{0});
registerConfigVar("group:groupbar:text_padding", Hyprlang::INT{0});
registerConfigVar("group:groupbar:blur", Hyprlang::INT{0});
registerConfigVar("debug:log_damage", Hyprlang::INT{0});
registerConfigVar("debug:overlay", Hyprlang::INT{0});
registerConfigVar("debug:damage_blink", Hyprlang::INT{0});
registerConfigVar("debug:vfr", Hyprlang::INT{1});
registerConfigVar("debug:pass", Hyprlang::INT{0});
registerConfigVar("debug:gl_debugging", Hyprlang::INT{0});
registerConfigVar("debug:disable_logs", Hyprlang::INT{1});
registerConfigVar("debug:disable_time", Hyprlang::INT{1});
registerConfigVar("debug:enable_stdout_logs", Hyprlang::INT{0});
registerConfigVar("debug:damage_tracking", {sc<Hyprlang::INT>(Render::DAMAGE_TRACKING_FULL)});
registerConfigVar("debug:manual_crash", Hyprlang::INT{0});
registerConfigVar("debug:suppress_errors", Hyprlang::INT{0});
registerConfigVar("debug:error_limit", Hyprlang::INT{5});
registerConfigVar("debug:error_position", Hyprlang::INT{0});
registerConfigVar("debug:disable_scale_checks", Hyprlang::INT{0});
registerConfigVar("debug:colored_stdout_logs", Hyprlang::INT{1});
registerConfigVar("debug:full_cm_proto", Hyprlang::INT{0});
registerConfigVar("debug:ds_handle_same_buffer", Hyprlang::INT{1});
registerConfigVar("debug:ds_handle_same_buffer_fifo", Hyprlang::INT{1});
registerConfigVar("debug:fifo_pending_workaround", Hyprlang::INT{0});
registerConfigVar("debug:render_solitary_wo_damage", Hyprlang::INT{0});
registerConfigVar("debug:invalidate_fp16", Hyprlang::INT{2});
registerConfigVar("decoration:rounding", Hyprlang::INT{0});
registerConfigVar("decoration:rounding_power", {2.F});
registerConfigVar("decoration:blur:enabled", Hyprlang::INT{1});
registerConfigVar("decoration:blur:size", Hyprlang::INT{8});
registerConfigVar("decoration:blur:passes", Hyprlang::INT{1});
registerConfigVar("decoration:blur:ignore_opacity", Hyprlang::INT{1});
registerConfigVar("decoration:blur:new_optimizations", Hyprlang::INT{1});
registerConfigVar("decoration:blur:xray", Hyprlang::INT{0});
registerConfigVar("decoration:blur:contrast", {0.8916F});
registerConfigVar("decoration:blur:brightness", {1.0F});
registerConfigVar("decoration:blur:vibrancy", {0.1696F});
registerConfigVar("decoration:blur:vibrancy_darkness", {0.0F});
registerConfigVar("decoration:blur:noise", {0.0117F});
registerConfigVar("decoration:blur:special", Hyprlang::INT{0});
registerConfigVar("decoration:blur:popups", Hyprlang::INT{0});
registerConfigVar("decoration:blur:popups_ignorealpha", {0.2F});
registerConfigVar("decoration:blur:input_methods", Hyprlang::INT{0});
registerConfigVar("decoration:blur:input_methods_ignorealpha", {0.2F});
registerConfigVar("decoration:active_opacity", {1.F});
registerConfigVar("decoration:inactive_opacity", {1.F});
registerConfigVar("decoration:fullscreen_opacity", {1.F});
registerConfigVar("decoration:shadow:enabled", Hyprlang::INT{1});
registerConfigVar("decoration:shadow:range", Hyprlang::INT{4});
registerConfigVar("decoration:shadow:render_power", Hyprlang::INT{3});
registerConfigVar("decoration:shadow:offset", Hyprlang::VEC2{0, 0});
registerConfigVar("decoration:shadow:scale", {1.f});
registerConfigVar("decoration:shadow:sharp", Hyprlang::INT{0});
registerConfigVar("decoration:shadow:color", Hyprlang::INT{0xee1a1a1a});
registerConfigVar("decoration:shadow:color_inactive", Hyprlang::INT{-1});
registerConfigVar("decoration:glow:enabled", Hyprlang::INT{0});
registerConfigVar("decoration:glow:range", Hyprlang::INT{10});
registerConfigVar("decoration:glow:render_power", Hyprlang::INT{3});
registerConfigVar("decoration:glow:color", Hyprlang::INT{0xee33ccff});
registerConfigVar("decoration:glow:color_inactive", Hyprlang::INT{0x0033ccff});
registerConfigVar("decoration:dim_inactive", Hyprlang::INT{0});
registerConfigVar("decoration:dim_modal", Hyprlang::INT{1});
registerConfigVar("decoration:dim_strength", {0.5f});
registerConfigVar("decoration:dim_special", {0.2f});
registerConfigVar("decoration:dim_around", {0.4f});
registerConfigVar("decoration:screen_shader", {STRVAL_EMPTY});
registerConfigVar("decoration:border_part_of_window", Hyprlang::INT{1});
registerConfigVar("layout:single_window_aspect_ratio", Hyprlang::VEC2{0, 0});
registerConfigVar("layout:single_window_aspect_ratio_tolerance", {0.1f});
registerConfigVar("dwindle:pseudotile", Hyprlang::INT{0});
registerConfigVar("dwindle:force_split", Hyprlang::INT{0});
registerConfigVar("dwindle:permanent_direction_override", Hyprlang::INT{0});
registerConfigVar("dwindle:preserve_split", Hyprlang::INT{0});
registerConfigVar("dwindle:special_scale_factor", {1.f});
registerConfigVar("dwindle:split_width_multiplier", {1.0f});
registerConfigVar("dwindle:use_active_for_splits", Hyprlang::INT{1});
registerConfigVar("dwindle:default_split_ratio", {1.f});
registerConfigVar("dwindle:split_bias", Hyprlang::INT{0});
registerConfigVar("dwindle:smart_split", Hyprlang::INT{0});
registerConfigVar("dwindle:smart_resizing", Hyprlang::INT{1});
registerConfigVar("dwindle:precise_mouse_move", Hyprlang::INT{0});
registerConfigVar("master:special_scale_factor", {1.f});
registerConfigVar("master:mfact", {0.55f});
registerConfigVar("master:new_status", {"slave"});
registerConfigVar("master:slave_count_for_center_master", Hyprlang::INT{2});
registerConfigVar("master:center_master_fallback", {"left"});
registerConfigVar("master:center_ignores_reserved", Hyprlang::INT{0});
registerConfigVar("master:new_on_active", {"none"});
registerConfigVar("master:new_on_top", Hyprlang::INT{0});
registerConfigVar("master:orientation", {"left"});
registerConfigVar("master:allow_small_split", Hyprlang::INT{0});
registerConfigVar("master:smart_resizing", Hyprlang::INT{1});
registerConfigVar("master:drop_at_cursor", Hyprlang::INT{1});
registerConfigVar("master:always_keep_position", Hyprlang::INT{0});
registerConfigVar("scrolling:fullscreen_on_one_column", Hyprlang::INT{1});
registerConfigVar("scrolling:column_width", Hyprlang::FLOAT{0.5F});
registerConfigVar("scrolling:focus_fit_method", Hyprlang::INT{1});
registerConfigVar("scrolling:follow_focus", Hyprlang::INT{1});
registerConfigVar("scrolling:follow_min_visible", Hyprlang::FLOAT{0.4});
registerConfigVar("scrolling:explicit_column_widths", Hyprlang::STRING{"0.333, 0.5, 0.667, 1.0"});
registerConfigVar("scrolling:direction", Hyprlang::STRING{"right"});
registerConfigVar("scrolling:wrap_focus", Hyprlang::INT{1});
registerConfigVar("scrolling:wrap_swapcol", Hyprlang::INT{1});
registerConfigVar("animations:enabled", Hyprlang::INT{1});
registerConfigVar("animations:workspace_wraparound", Hyprlang::INT{0});
registerConfigVar("input:follow_mouse", Hyprlang::INT{1});
registerConfigVar("input:follow_mouse_shrink", Hyprlang::INT{0});
registerConfigVar("input:follow_mouse_threshold", Hyprlang::FLOAT{0});
registerConfigVar("input:focus_on_close", Hyprlang::INT{0});
registerConfigVar("input:mouse_refocus", Hyprlang::INT{1});
registerConfigVar("input:special_fallthrough", Hyprlang::INT{0});
registerConfigVar("input:off_window_axis_events", Hyprlang::INT{1});
registerConfigVar("input:sensitivity", {0.f});
registerConfigVar("input:accel_profile", {STRVAL_EMPTY});
registerConfigVar("input:rotation", Hyprlang::INT{0});
registerConfigVar("input:kb_file", {STRVAL_EMPTY});
registerConfigVar("input:kb_layout", {"us"});
registerConfigVar("input:kb_variant", {STRVAL_EMPTY});
registerConfigVar("input:kb_options", {STRVAL_EMPTY});
registerConfigVar("input:kb_rules", {STRVAL_EMPTY});
registerConfigVar("input:kb_model", {STRVAL_EMPTY});
registerConfigVar("input:repeat_rate", Hyprlang::INT{25});
registerConfigVar("input:repeat_delay", Hyprlang::INT{600});
registerConfigVar("input:natural_scroll", Hyprlang::INT{0});
registerConfigVar("input:numlock_by_default", Hyprlang::INT{0});
registerConfigVar("input:resolve_binds_by_sym", Hyprlang::INT{0});
registerConfigVar("input:force_no_accel", Hyprlang::INT{0});
registerConfigVar("input:float_switch_override_focus", Hyprlang::INT{1});
registerConfigVar("input:left_handed", Hyprlang::INT{0});
registerConfigVar("input:scroll_method", {STRVAL_EMPTY});
registerConfigVar("input:scroll_button", Hyprlang::INT{0});
registerConfigVar("input:scroll_button_lock", Hyprlang::INT{0});
registerConfigVar("input:scroll_factor", {1.f});
registerConfigVar("input:scroll_points", {STRVAL_EMPTY});
registerConfigVar("input:emulate_discrete_scroll", Hyprlang::INT{1});
registerConfigVar("input:touchpad:natural_scroll", Hyprlang::INT{0});
registerConfigVar("input:touchpad:disable_while_typing", Hyprlang::INT{1});
registerConfigVar("input:touchpad:clickfinger_behavior", Hyprlang::INT{0});
registerConfigVar("input:touchpad:tap_button_map", {STRVAL_EMPTY});
registerConfigVar("input:touchpad:middle_button_emulation", Hyprlang::INT{0});
registerConfigVar("input:touchpad:tap-to-click", Hyprlang::INT{1});
registerConfigVar("input:touchpad:tap-and-drag", Hyprlang::INT{1});
registerConfigVar("input:touchpad:drag_lock", Hyprlang::INT{0});
registerConfigVar("input:touchpad:scroll_factor", {1.f});
registerConfigVar("input:touchpad:flip_x", Hyprlang::INT{0});
registerConfigVar("input:touchpad:flip_y", Hyprlang::INT{0});
registerConfigVar("input:touchpad:drag_3fg", Hyprlang::INT{0});
registerConfigVar("input:touchdevice:transform", Hyprlang::INT{-1});
registerConfigVar("input:touchdevice:output", {"[[Auto]]"});
registerConfigVar("input:touchdevice:enabled", Hyprlang::INT{1});
registerConfigVar("input:virtualkeyboard:share_states", Hyprlang::INT{2});
registerConfigVar("input:virtualkeyboard:release_pressed_on_close", Hyprlang::INT{0});
registerConfigVar("input:tablet:transform", Hyprlang::INT{0});
registerConfigVar("input:tablet:output", {STRVAL_EMPTY});
registerConfigVar("input:tablet:region_position", Hyprlang::VEC2{0, 0});
registerConfigVar("input:tablet:absolute_region_position", Hyprlang::INT{0});
registerConfigVar("input:tablet:region_size", Hyprlang::VEC2{0, 0});
registerConfigVar("input:tablet:relative_input", Hyprlang::INT{0});
registerConfigVar("input:tablet:left_handed", Hyprlang::INT{0});
registerConfigVar("input:tablet:active_area_position", Hyprlang::VEC2{0, 0});
registerConfigVar("input:tablet:active_area_size", Hyprlang::VEC2{0, 0});
registerConfigVar("binds:pass_mouse_when_bound", Hyprlang::INT{0});
registerConfigVar("binds:scroll_event_delay", Hyprlang::INT{300});
registerConfigVar("binds:workspace_back_and_forth", Hyprlang::INT{0});
registerConfigVar("binds:hide_special_on_workspace_change", Hyprlang::INT{0});
registerConfigVar("binds:allow_workspace_cycles", Hyprlang::INT{0});
registerConfigVar("binds:workspace_center_on", Hyprlang::INT{1});
registerConfigVar("binds:focus_preferred_method", Hyprlang::INT{0});
registerConfigVar("binds:ignore_group_lock", Hyprlang::INT{0});
registerConfigVar("binds:movefocus_cycles_fullscreen", Hyprlang::INT{0});
registerConfigVar("binds:movefocus_cycles_groupfirst", Hyprlang::INT{0});
registerConfigVar("binds:disable_keybind_grabbing", Hyprlang::INT{0});
registerConfigVar("binds:allow_pin_fullscreen", Hyprlang::INT{0});
registerConfigVar("binds:drag_threshold", Hyprlang::INT{0});
registerConfigVar("binds:window_direction_monitor_fallback", Hyprlang::INT{1});
registerConfigVar("gestures:workspace_swipe_distance", Hyprlang::INT{300});
registerConfigVar("gestures:workspace_swipe_invert", Hyprlang::INT{1});
registerConfigVar("gestures:workspace_swipe_min_speed_to_force", Hyprlang::INT{30});
registerConfigVar("gestures:workspace_swipe_cancel_ratio", {0.5f});
registerConfigVar("gestures:workspace_swipe_create_new", Hyprlang::INT{1});
registerConfigVar("gestures:workspace_swipe_direction_lock", Hyprlang::INT{1});
registerConfigVar("gestures:workspace_swipe_direction_lock_threshold", Hyprlang::INT{10});
registerConfigVar("gestures:workspace_swipe_forever", Hyprlang::INT{0});
registerConfigVar("gestures:workspace_swipe_use_r", Hyprlang::INT{0});
registerConfigVar("gestures:workspace_swipe_touch", Hyprlang::INT{0});
registerConfigVar("gestures:workspace_swipe_touch_invert", Hyprlang::INT{0});
registerConfigVar("gestures:close_max_timeout", Hyprlang::INT{1000});
registerConfigVar("xwayland:enabled", Hyprlang::INT{1});
registerConfigVar("xwayland:use_nearest_neighbor", Hyprlang::INT{1});
registerConfigVar("xwayland:force_zero_scaling", Hyprlang::INT{0});
registerConfigVar("xwayland:create_abstract_socket", Hyprlang::INT{0});
registerConfigVar("opengl:nvidia_anti_flicker", Hyprlang::INT{1});
registerConfigVar("cursor:invisible", Hyprlang::INT{0});
registerConfigVar("cursor:no_hardware_cursors", Hyprlang::INT{2});
registerConfigVar("cursor:no_break_fs_vrr", Hyprlang::INT{2});
registerConfigVar("cursor:min_refresh_rate", Hyprlang::INT{24});
registerConfigVar("cursor:hotspot_padding", Hyprlang::INT{0});
registerConfigVar("cursor:inactive_timeout", {0.f});
registerConfigVar("cursor:no_warps", Hyprlang::INT{0});
registerConfigVar("cursor:persistent_warps", Hyprlang::INT{0});
registerConfigVar("cursor:warp_on_change_workspace", Hyprlang::INT{0});
registerConfigVar("cursor:warp_on_toggle_special", Hyprlang::INT{0});
registerConfigVar("cursor:default_monitor", {STRVAL_EMPTY});
registerConfigVar("cursor:zoom_factor", {1.f});
registerConfigVar("cursor:zoom_rigid", Hyprlang::INT{0});
registerConfigVar("cursor:zoom_disable_aa", Hyprlang::INT{0});
registerConfigVar("cursor:zoom_detached_camera", Hyprlang::INT{1});
registerConfigVar("cursor:enable_hyprcursor", Hyprlang::INT{1});
registerConfigVar("cursor:sync_gsettings_theme", Hyprlang::INT{1});
registerConfigVar("cursor:hide_on_key_press", Hyprlang::INT{0});
registerConfigVar("cursor:hide_on_touch", Hyprlang::INT{1});
registerConfigVar("cursor:hide_on_tablet", Hyprlang::INT{0});
registerConfigVar("cursor:use_cpu_buffer", Hyprlang::INT{2});
registerConfigVar("cursor:warp_back_after_non_mouse_input", Hyprlang::INT{0});
if (auto p = dc<Config::Values::CIntValue*>(v.get()))
registerConfigVar(NAME, Hyprlang::INT{p->defaultVal()});
else if (auto p = dc<Config::Values::CFloatValue*>(v.get()))
registerConfigVar(NAME, Hyprlang::FLOAT{p->defaultVal()});
else if (auto p = dc<Config::Values::CBoolValue*>(v.get()))
registerConfigVar(NAME, Hyprlang::INT{p->defaultVal() ? 1 : 0});
else if (auto p = dc<Config::Values::CStringValue*>(v.get()))
registerConfigVar(NAME, Hyprlang::STRING{p->defaultVal().c_str()});
else if (auto p = dc<Config::Values::CColorValue*>(v.get()))
registerConfigVar(NAME, Hyprlang::INT{p->defaultVal()});
else if (auto p = dc<Config::Values::CVec2Value*>(v.get()))
registerConfigVar(NAME, Hyprlang::VEC2{p->defaultVal().x, p->defaultVal().y});
else if (auto p = dc<Config::Values::CCssGapValue*>(v.get()))
registerConfigVar(NAME, Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, std::to_string(p->defaultVal().m_top).c_str()});
else if (auto p = dc<Config::Values::CFontWeightValue*>(v.get()))
registerConfigVar(NAME,
Hyprlang::CConfigCustomValueType{&configHandleFontWeightSet, configHandleFontWeightDestroy, std::format("{}", p->defaultVal().m_value).c_str()});
else if (auto p = dc<Config::Values::CGradientValue*>(v.get()))
registerConfigVar(NAME,
Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy,
std::format("0x{:x}", (int64_t)p->defaultVal().m_colors.begin()->getAsHex()).c_str()});
else
RASSERT(false, "legacy cfg: bad value {}", NAME);
}
registerConfigVar("autogenerated", Hyprlang::INT{0});
registerConfigVar("group:col.border_active", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66ffff00"});
registerConfigVar("group:col.border_inactive", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66777700"});
registerConfigVar("group:col.border_locked_active", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66ff5500"});
registerConfigVar("group:col.border_locked_inactive", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66775500"});
registerConfigVar("group:groupbar:col.active", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66ffff00"});
registerConfigVar("group:groupbar:col.inactive", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66777700"});
registerConfigVar("group:groupbar:col.locked_active", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66ff5500"});
registerConfigVar("group:groupbar:col.locked_inactive", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66775500"});
registerConfigVar("render:direct_scanout", Hyprlang::INT{0});
registerConfigVar("render:expand_undersized_textures", Hyprlang::INT{1});
registerConfigVar("render:xp_mode", Hyprlang::INT{0});
registerConfigVar("render:ctm_animation", Hyprlang::INT{2});
registerConfigVar("render:cm_enabled", Hyprlang::INT{1});
registerConfigVar("render:send_content_type", Hyprlang::INT{1});
registerConfigVar("render:cm_auto_hdr", Hyprlang::INT{1});
registerConfigVar("render:new_render_scheduling", Hyprlang::INT{0});
registerConfigVar("render:non_shader_cm", Hyprlang::INT{2});
registerConfigVar("render:non_shader_cm_interop", Hyprlang::INT{2});
registerConfigVar("render:cm_sdr_eotf", {"default"});
registerConfigVar("render:commit_timing_enabled", Hyprlang::INT{1});
registerConfigVar("render:icc_vcgt_enabled", Hyprlang::INT{1});
registerConfigVar("render:use_shader_blur_blend", Hyprlang::INT{0});
registerConfigVar("render:use_fp16", Hyprlang::INT{2});
registerConfigVar("render:keep_unmodified_copy", Hyprlang::INT{2});
registerConfigVar("ecosystem:no_update_news", Hyprlang::INT{0});
registerConfigVar("ecosystem:no_donation_nag", Hyprlang::INT{0});
registerConfigVar("ecosystem:enforce_permissions", Hyprlang::INT{0});
registerConfigVar("experimental:wp_cm_1_2", Hyprlang::INT{0});
registerConfigVar("quirks:prefer_hdr", Hyprlang::INT{0});
registerConfigVar("quirks:skip_non_kms_dmabuf_formats", Hyprlang::INT{0});
// devices
m_config->addSpecialCategory("device", {"name"});
m_config->addSpecialConfigValue("device", "sensitivity", {0.F});
@@ -946,10 +621,6 @@ CConfigManager::CConfigManager() {
resetHLConfig();
if (Config::Supplementary::CONFIG_OPTIONS.size() != m_configValueNumber - 1 /* autogenerated is special */)
Log::logger->log(Log::DEBUG, "Warning: config descriptions have {} entries, but there are {} config values. This should fail tests!!",
Config::Supplementary::CONFIG_OPTIONS.size(), m_configValueNumber);
if (!g_pCompositor->m_onlyConfigVerification) {
Log::logger->log(
Log::DEBUG,
@@ -992,13 +663,13 @@ std::optional<std::string> CConfigManager::verifyConfigExists() {
return "Broken config directory";
std::error_code ec;
const bool VALID_CFG = std::filesystem::exists(*mainConfigPath, ec) && !ec;
const bool VALID_CFG = std::filesystem::exists(mainConfigPath->path, ec) && !ec;
if (!VALID_CFG && !g_pCompositor->m_explicitConfigPath.empty())
return "Invalid config file provided as explicit";
if (!VALID_CFG) {
if (const auto res = generateDefaultConfig(*mainConfigPath, g_pCompositor->m_safeMode); !res)
if (const auto res = generateDefaultConfig(mainConfigPath->path, g_pCompositor->m_safeMode); !res)
return res.error();
}
@@ -1053,7 +724,7 @@ void CConfigManager::reload() {
auto oldConfigPath = m_mainConfigPath;
m_mainConfigPath = *Supplementary::Jeremy::getMainConfigPath();
m_mainConfigPath = Supplementary::Jeremy::getMainConfigPath()->path;
m_configCurrentPath = m_mainConfigPath;
if (m_mainConfigPath != oldConfigPath)
@@ -1074,7 +745,7 @@ void CConfigManager::reload() {
std::string CConfigManager::verify() {
Config::animationTree()->reset();
resetHLConfig();
m_configCurrentPath = *Supplementary::Jeremy::getMainConfigPath();
m_configCurrentPath = Supplementary::Jeremy::getMainConfigPath()->path;
const auto ERR = m_config->parse();
m_lastConfigVerificationWasSuccessful = !ERR.error;
if (ERR.error)
@@ -1554,7 +1225,7 @@ std::optional<std::string> CConfigManager::handleRawExec(const std::string& comm
return {};
}
g_pKeybindManager->spawnRaw(args);
Config::Supplementary::executor()->spawnRaw(args);
return {};
}
@@ -1564,7 +1235,7 @@ std::optional<std::string> CConfigManager::handleExec(const std::string& command
return {};
}
g_pKeybindManager->spawn(args);
Config::Supplementary::executor()->spawn(args);
return {};
}
@@ -1584,7 +1255,7 @@ std::optional<std::string> CConfigManager::handleExecRawOnce(const std::string&
std::optional<std::string> CConfigManager::handleExecShutdown(const std::string& command, const std::string& args) {
if (g_pCompositor->m_finalRequests) {
g_pKeybindManager->spawn(args);
Config::Supplementary::executor()->spawn(args);
return {};
}
@@ -1859,15 +1530,15 @@ std::optional<std::string> CConfigManager::handleBind(const std::string& command
else if ((ARGS.size() > sc<size_t>(4) + DESCR_OFFSET + DEVICE_OFFSET && !mouse) || (ARGS.size() > sc<size_t>(3) + DESCR_OFFSET + DEVICE_OFFSET && mouse))
return "bind: too many args";
std::set<xkb_keysym_t> KEYSYMS;
std::set<xkb_keysym_t> MODS;
std::vector<xkb_keysym_t> KEYSYMS;
std::vector<xkb_keysym_t> MODS;
if (multiKey) {
for (const auto& splitKey : CVarList(ARGS[1], 8, '&')) {
KEYSYMS.insert(xkb_keysym_from_name(splitKey.c_str(), XKB_KEYSYM_CASE_INSENSITIVE));
KEYSYMS.emplace_back(xkb_keysym_from_name(splitKey.c_str(), XKB_KEYSYM_CASE_INSENSITIVE));
}
for (const auto& splitMod : CVarList(ARGS[0], 8, '&')) {
MODS.insert(xkb_keysym_from_name(splitMod.c_str(), XKB_KEYSYM_CASE_INSENSITIVE));
MODS.emplace_back(xkb_keysym_from_name(splitMod.c_str(), XKB_KEYSYM_CASE_INSENSITIVE));
}
}
const auto MOD = g_pKeybindManager->stringToModMask(ARGS[0]);
@@ -2412,3 +2083,40 @@ std::string CConfigManager::getMainConfigPath() {
std::string CConfigManager::currentConfigPath() {
return m_configCurrentPath;
}
void CConfigManager::onPluginUnload(void* handle) {
removePluginConfig(handle);
}
std::expected<void, std::string> CConfigManager::registerPluginValue(void* handle, SP<Config::Values::IValue> value) {
const std::string NAME = value->name();
if (!NAME.starts_with("plugin:"))
return std::unexpected("name must start with plugin:");
if (auto p = dc<Config::Values::CIntValue*>(value.get()))
addPluginConfigVar(handle, NAME, Hyprlang::INT{p->defaultVal()});
else if (auto p = dc<Config::Values::CFloatValue*>(value.get()))
addPluginConfigVar(handle, NAME, Hyprlang::FLOAT{p->defaultVal()});
else if (auto p = dc<Config::Values::CBoolValue*>(value.get()))
addPluginConfigVar(handle, NAME, Hyprlang::INT{p->defaultVal() ? 1 : 0});
else if (auto p = dc<Config::Values::CStringValue*>(value.get()))
addPluginConfigVar(handle, NAME, Hyprlang::STRING{p->defaultVal().c_str()});
else if (auto p = dc<Config::Values::CColorValue*>(value.get()))
addPluginConfigVar(handle, NAME, Hyprlang::INT{p->defaultVal()});
else if (auto p = dc<Config::Values::CVec2Value*>(value.get()))
addPluginConfigVar(handle, NAME, Hyprlang::VEC2{p->defaultVal().x, p->defaultVal().y});
else if (auto p = dc<Config::Values::CCssGapValue*>(value.get()))
addPluginConfigVar(handle, NAME, Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, std::to_string(p->defaultVal().m_top).c_str()});
else if (auto p = dc<Config::Values::CFontWeightValue*>(value.get()))
addPluginConfigVar(handle, NAME,
Hyprlang::CConfigCustomValueType{&configHandleFontWeightSet, configHandleFontWeightDestroy, std::format("{}", p->defaultVal().m_value).c_str()});
else if (auto p = dc<Config::Values::CGradientValue*>(value.get()))
addPluginConfigVar(handle, NAME,
Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy,
std::format("{:x}", (int64_t)p->defaultVal().m_colors.begin()->getAsHex()).c_str()});
else
return std::unexpected("unknown value type");
return {};
}
+3
View File
@@ -66,6 +66,9 @@ namespace Config::Legacy {
virtual void handlePluginLoads() override;
virtual bool configVerifPassed() override;
virtual std::expected<void, std::string> registerPluginValue(void* handle, SP<Config::Values::IValue> value) override;
virtual void onPluginUnload(void* handle) override;
void addPluginConfigVar(HANDLE handle, const std::string& name, const Hyprlang::CConfigValue& value);
void addPluginKeyword(HANDLE handle, const std::string& name, Hyprlang::PCONFIGHANDLERFUNC fun, Hyprlang::SHandlerOptions opts = {});
void removePluginConfig(HANDLE handle);
@@ -1,6 +1,6 @@
#pragma once
#include <string>
#include <string_view>
inline constexpr std::string_view AUTOGENERATED_PREFIX = R"#(
# #######################################################################################
@@ -12,7 +12,7 @@ autogenerated = 1 # remove this line to remove the warning
)#";
inline constexpr char EXAMPLE_CONFIG_BYTES[] = {
#embed "../../example/hyprland.conf"
#embed "../../../example/hyprland.conf"
};
inline constexpr std::string_view EXAMPLE_CONFIG = {EXAMPLE_CONFIG_BYTES, sizeof(EXAMPLE_CONFIG_BYTES)};
+859
View File
@@ -0,0 +1,859 @@
#include "DispatcherTranslator.hpp"
#include "../shared/actions/ConfigActions.hpp"
#include "../supplementary/executor/Executor.hpp"
#include "../../Compositor.hpp"
#include "../../desktop/state/FocusState.hpp"
#include "../../helpers/Monitor.hpp"
#include "../../desktop/view/Group.hpp"
#include "../../managers/KeybindManager.hpp"
#include "../../managers/SeatManager.hpp"
#include "../../managers/input/InputManager.hpp"
#include "../../layout/LayoutManager.hpp"
#include <hyprutils/string/String.hpp>
#include <hyprutils/string/VarList2.hpp>
using namespace Hyprutils::String;
using namespace Config;
using namespace Config::Legacy;
using namespace Config::Actions;
UP<CDispatcherTranslator>& Legacy::translator() {
static UP<CDispatcherTranslator> p = makeUnique<CDispatcherTranslator>();
return p;
}
SDispatchResult CDispatcherTranslator::run(const std::string& d, const std::string& w) {
if (!m_dispMap.contains(d))
return {.success = false, .error = "Bad dispatcher"};
return m_dispMap.at(d)(w);
}
// helper: convert ActionResult to SDispatchResult
static SDispatchResult wrap(ActionResult res) {
if (!res)
return {.success = false, .error = res.error()};
return {.passEvent = res->passEvent};
}
// helper: resolve window from regex string, or focused if empty/active
static PHLWINDOW windowFromArg(const std::string& arg) {
if (arg.empty() || arg == "active")
return nullptr; // will use xtract(nullopt) -> focused window
return g_pCompositor->getWindowByRegex(arg);
}
// helper: resolve workspace from string and optionally create it
static PHLWORKSPACE resolveWorkspace(const std::string& args) {
const auto& [id, name, isAutoID] = getWorkspaceIDNameFromString(args);
if (id == WORKSPACE_INVALID)
return nullptr;
auto ws = g_pCompositor->getWorkspaceByID(id);
if (!ws) {
const auto PMONITOR = Desktop::focusState()->monitor();
if (PMONITOR)
ws = g_pCompositor->createNewWorkspace(id, PMONITOR->m_id, name, false);
}
return ws;
}
static SDispatchResult exec(const std::string& args) {
const auto PROC = Config::Supplementary::executor()->spawn(args);
if (!PROC.has_value())
return {.success = false, .error = std::format("Failed to start process. No closing bracket in exec rule. {}", args)};
return {.success = PROC.value() > 0, .error = std::format("Failed to start process {}", args)};
}
static SDispatchResult execr(const std::string& args) {
const auto PROC = Config::Supplementary::executor()->spawnRaw(args);
return {.success = PROC && *PROC > 0, .error = std::format("Failed to start process {}", args)};
}
static SDispatchResult killactive(const std::string&) {
return wrap(Actions::closeWindow());
}
static SDispatchResult forcekillactive(const std::string&) {
return wrap(Actions::killWindow());
}
static SDispatchResult closewindow(const std::string& data) {
return wrap(Actions::closeWindow(g_pCompositor->getWindowByRegex(data)));
}
static SDispatchResult killwindow(const std::string& data) {
return wrap(Actions::killWindow(g_pCompositor->getWindowByRegex(data)));
}
static SDispatchResult signalactive(const std::string& args) {
if (!isNumber(args))
return {.success = false, .error = "signalActive: signal has to be int"};
try {
return wrap(Actions::signalWindow(std::stoi(args)));
} catch (...) { return {.success = false, .error = "signalActive: invalid signal format"}; }
}
static SDispatchResult signalwindow(const std::string& args) {
const auto WINDOWREGEX = args.substr(0, args.find_first_of(','));
const auto SIGNAL = args.substr(args.find_first_of(',') + 1);
const auto PWINDOW = g_pCompositor->getWindowByRegex(WINDOWREGEX);
if (!PWINDOW)
return {.success = false, .error = "signalWindow: no window"};
if (!isNumber(SIGNAL))
return {.success = false, .error = "signalWindow: signal has to be int"};
try {
return wrap(Actions::signalWindow(std::stoi(SIGNAL), PWINDOW));
} catch (...) { return {.success = false, .error = "signalWindow: invalid signal format"}; }
}
static SDispatchResult togglefloating(const std::string& args) {
auto w = windowFromArg(args);
return wrap(Actions::floatWindow(TOGGLE_ACTION_TOGGLE, w));
}
static SDispatchResult setfloating(const std::string& args) {
auto w = windowFromArg(args);
return wrap(Actions::floatWindow(TOGGLE_ACTION_ENABLE, w));
}
static SDispatchResult settiled(const std::string& args) {
auto w = windowFromArg(args);
return wrap(Actions::floatWindow(TOGGLE_ACTION_DISABLE, w));
}
static SDispatchResult pseudo(const std::string& args) {
auto w = windowFromArg(args);
return wrap(Actions::pseudoWindow(TOGGLE_ACTION_TOGGLE, w));
}
static SDispatchResult workspace(const std::string& args) {
return wrap(Actions::changeWorkspace(args));
}
static SDispatchResult renameworkspace(const std::string& args) {
try {
const auto FIRSTSPACEPOS = args.find_first_of(' ');
if (FIRSTSPACEPOS != std::string::npos) {
int wsid = std::stoi(args.substr(0, FIRSTSPACEPOS));
std::string name = args.substr(FIRSTSPACEPOS + 1);
const auto PWS = g_pCompositor->getWorkspaceByID(wsid);
if (!PWS)
return {.success = false, .error = "No such workspace"};
return wrap(Actions::renameWorkspace(PWS, name));
} else {
const auto PWS = g_pCompositor->getWorkspaceByID(std::stoi(args));
if (!PWS)
return {.success = false, .error = "No such workspace"};
return wrap(Actions::renameWorkspace(PWS, ""));
}
} catch (std::exception& e) { return {.success = false, .error = std::format("Invalid arg in renameWorkspace: {}", e.what())}; }
}
static SDispatchResult fullscreen(const std::string& args) {
CVarList2 ARGS(args, 2, ' ');
const eFullscreenMode MODE = ARGS.size() > 0 && ARGS[0] == "1" ? FSMODE_MAXIMIZED : FSMODE_FULLSCREEN;
if (ARGS.size() <= 1 || ARGS[1] == "toggle")
return wrap(Actions::fullscreenWindow(MODE));
// "set" means enable, "unset" means disable - but the Action toggles.
// We need to check current state ourselves.
const auto PWINDOW = Desktop::focusState()->window();
if (!PWINDOW)
return {.success = false, .error = "Window not found"};
if (ARGS[1] == "set") {
if (!PWINDOW->isEffectiveInternalFSMode(MODE))
return wrap(Actions::fullscreenWindow(MODE));
return {};
} else if (ARGS[1] == "unset") {
if (PWINDOW->isEffectiveInternalFSMode(MODE))
return wrap(Actions::fullscreenWindow(MODE));
return {};
}
return {};
}
static SDispatchResult fullscreenstate(const std::string& args) {
CVarList2 ARGS(args, 3, ' ');
const auto PWINDOW = Desktop::focusState()->window();
if (!PWINDOW)
return {.success = false, .error = "Window not found"};
int internalMode, clientMode;
try {
internalMode = std::stoi(std::string(ARGS[0]));
} catch (...) { internalMode = -1; }
try {
clientMode = std::stoi(std::string(ARGS[1]));
} catch (...) { clientMode = -1; }
eFullscreenMode im = internalMode != -1 ? sc<eFullscreenMode>(internalMode) : PWINDOW->m_fullscreenState.internal;
eFullscreenMode cm = clientMode != -1 ? sc<eFullscreenMode>(clientMode) : PWINDOW->m_fullscreenState.client;
return wrap(Actions::fullscreenWindow(im, cm));
}
static SDispatchResult movetoworkspace(const std::string& args) {
PHLWINDOW PWINDOW = nullptr;
std::string wsArgs = args;
if (args.contains(',')) {
PWINDOW = g_pCompositor->getWindowByRegex(args.substr(args.find_last_of(',') + 1));
wsArgs = args.substr(0, args.find_last_of(','));
}
auto ws = resolveWorkspace(wsArgs);
if (!ws)
return {.success = false, .error = "Invalid workspace"};
return wrap(Actions::moveToWorkspace(ws, false, PWINDOW));
}
static SDispatchResult movetoworkspacesilent(const std::string& args) {
PHLWINDOW PWINDOW = nullptr;
std::string wsArgs = args;
if (args.contains(',')) {
PWINDOW = g_pCompositor->getWindowByRegex(args.substr(args.find_last_of(',') + 1));
wsArgs = args.substr(0, args.find_last_of(','));
}
auto ws = resolveWorkspace(wsArgs);
if (!ws)
return {.success = false, .error = "Invalid workspace"};
return wrap(Actions::moveToWorkspace(ws, true, PWINDOW));
}
static SDispatchResult movefocus(const std::string& args) {
Math::eDirection dir = Math::fromChar(args[0]);
if (dir == Math::DIRECTION_DEFAULT)
return {.success = false, .error = std::format("Unsupported direction: {}", args[0])};
return wrap(Actions::moveFocus(dir));
}
static SDispatchResult movewindow(const std::string& args) {
// "movewindow" dispatcher handles both "mon:<monitor>" and directional moves.
// For mon: prefix, it delegates to movetoworkspace.
bool silent = args.ends_with(" silent");
auto cleanArgs = silent ? args.substr(0, args.length() - 7) : args;
if (cleanArgs.starts_with("mon:")) {
const auto PNEWMONITOR = g_pCompositor->getMonitorFromString(cleanArgs.substr(4));
if (!PNEWMONITOR)
return {.success = false, .error = std::format("Monitor {} not found", cleanArgs.substr(4))};
auto ws = PNEWMONITOR->m_activeWorkspace;
return wrap(Actions::moveToWorkspace(ws, silent));
}
Math::eDirection dir = Math::fromChar(cleanArgs[0]);
if (dir == Math::DIRECTION_DEFAULT)
return {.success = false, .error = std::format("Unsupported direction: {}", cleanArgs[0])};
return wrap(Actions::moveInDirection(dir));
}
static SDispatchResult swapwindow(const std::string& args) {
if (isDirection(args))
return wrap(Actions::swapInDirection(Math::fromChar(args[0])));
// regex-based swap: resolve window and use swapInDirection? No - the old code used getWindowByRegex + switchTargets.
// The new Actions don't have a "swap with specific window" variant.
// Fall through to the old swapActive logic via getWindowByRegex + layout switchTargets.
const auto PLASTWINDOW = Desktop::focusState()->window();
if (!PLASTWINDOW)
return {.success = false, .error = "Window to swap with not found"};
if (PLASTWINDOW->isFullscreen())
return {.success = false, .error = "Can't swap fullscreen window"};
const auto PWINDOWTOCHANGETO = g_pCompositor->getWindowByRegex(args);
if (!PWINDOWTOCHANGETO || PWINDOWTOCHANGETO == PLASTWINDOW)
return {.success = false, .error = std::format("Can't swap with {}, invalid window", args)};
g_layoutManager->switchTargets(PLASTWINDOW->layoutTarget(), PWINDOWTOCHANGETO->layoutTarget(), true);
PLASTWINDOW->warpCursor();
return {};
}
static SDispatchResult centerwindow(const std::string&) {
return wrap(Actions::center());
}
static SDispatchResult togglegroup(const std::string&) {
return wrap(Actions::toggleGroup());
}
static SDispatchResult changegroupactive(const std::string& args) {
bool forward = !(args == "b" || args == "prev");
// index-based change
if (isNumber(args, false)) {
const auto PWINDOW = Desktop::focusState()->window();
if (!PWINDOW)
return {.success = false, .error = "No window found"};
if (!PWINDOW->m_group)
return {.success = false, .error = "No group"};
if (PWINDOW->m_group->size() == 1)
return {.success = false, .error = "Only one window in group"};
try {
const int INDEX = std::stoi(args);
if (INDEX <= 0)
PWINDOW->m_group->setCurrent(PWINDOW->m_group->size() - 1);
else
PWINDOW->m_group->setCurrent(INDEX - 1);
} catch (...) { return {.success = false, .error = "invalid idx"}; }
return {};
}
return wrap(Actions::changeGroupActive(forward));
}
static SDispatchResult movegroupwindow(const std::string& args) {
return wrap(Actions::moveGroupWindow(!(args == "b" || args == "prev")));
}
static SDispatchResult focusmonitor(const std::string& args) {
const auto PMONITOR = g_pCompositor->getMonitorFromString(args);
if (!PMONITOR)
return {.success = false, .error = "Monitor not found"};
return wrap(Actions::focusMonitor(PMONITOR));
}
static SDispatchResult movecursortocorner(const std::string& args) {
if (!isNumber(args))
return {.success = false, .error = "moveCursorToCorner, arg has to be a number"};
return wrap(Actions::moveCursorToCorner(std::stoi(args)));
}
static SDispatchResult movecursor(const std::string& args) {
size_t i = args.find_first_of(' ');
if (i == std::string::npos)
return {.success = false, .error = "moveCursor takes 2 arguments"};
auto x_str = args.substr(0, i);
auto y_str = args.substr(i + 1);
if (!isNumber(x_str) || !isNumber(y_str))
return {.success = false, .error = "moveCursor arguments must be numbers"};
return wrap(Actions::moveCursor({std::stoi(x_str), std::stoi(y_str)}));
}
static SDispatchResult workspaceopt(const std::string&) {
return {.success = false, .error = "workspaceopt is deprecated"};
}
static SDispatchResult exitHyprland(const std::string&) {
return wrap(Actions::exit());
}
static SDispatchResult movecurrentworkspacetomonitor(const std::string& args) {
const auto PMONITOR = g_pCompositor->getMonitorFromString(args);
if (!PMONITOR)
return {.success = false, .error = "Monitor not found"};
const auto PCURRENTWORKSPACE = Desktop::focusState()->monitor()->m_activeWorkspace;
if (!PCURRENTWORKSPACE)
return {.success = false, .error = "Invalid workspace"};
return wrap(Actions::moveToMonitor(PCURRENTWORKSPACE, PMONITOR));
}
static SDispatchResult moveworkspacetomonitor(const std::string& args) {
if (!args.contains(' '))
return {.success = false, .error = "Expected: workspace monitor"};
std::string wsStr = args.substr(0, args.find_first_of(' '));
std::string monStr = args.substr(args.find_first_of(' ') + 1);
const auto PMONITOR = g_pCompositor->getMonitorFromString(monStr);
if (!PMONITOR)
return {.success = false, .error = "Monitor not found"};
const auto WORKSPACEID = getWorkspaceIDNameFromString(wsStr).id;
if (WORKSPACEID == WORKSPACE_INVALID)
return {.success = false, .error = "Invalid workspace"};
const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(WORKSPACEID);
if (!PWORKSPACE)
return {.success = false, .error = "Workspace not found"};
return wrap(Actions::moveToMonitor(PWORKSPACE, PMONITOR));
}
static SDispatchResult focusworkspaceoncurrentmonitor(const std::string& args) {
auto ws = resolveWorkspace(args);
if (!ws)
return {.success = false, .error = "Invalid workspace"};
return wrap(Actions::changeWorkspaceOnCurrentMonitor(ws));
}
static SDispatchResult togglespecialworkspace(const std::string& args) {
const auto& [workspaceID, workspaceName, isAutoID] = getWorkspaceIDNameFromString("special:" + args);
if (workspaceID == WORKSPACE_INVALID || !g_pCompositor->isWorkspaceSpecial(workspaceID))
return {.success = false, .error = "Invalid special workspace"};
auto ws = g_pCompositor->getWorkspaceByID(workspaceID);
if (!ws) {
const auto PMONITOR = Desktop::focusState()->monitor();
if (PMONITOR)
ws = g_pCompositor->createNewWorkspace(workspaceID, PMONITOR->m_id, workspaceName);
}
if (!ws)
return {.success = false, .error = "Could not resolve special workspace"};
return wrap(Actions::toggleSpecial(ws));
}
static SDispatchResult forcerendererreload(const std::string&) {
return wrap(Actions::forceRendererReload());
}
static SDispatchResult resizeactive(const std::string& args) {
const auto PWINDOW = Desktop::focusState()->window();
if (!PWINDOW)
return {.success = false, .error = "No window found"};
const auto SIZ = g_pCompositor->parseWindowVectorArgsRelative(args, PWINDOW->m_realSize->goal());
if (SIZ.x < 1 || SIZ.y < 1)
return {.success = false, .error = "Invalid size"};
return wrap(Actions::resize(SIZ));
}
static SDispatchResult moveactive(const std::string& args) {
const auto PWINDOW = Desktop::focusState()->window();
if (!PWINDOW)
return {.success = false, .error = "No window found"};
const auto POS = g_pCompositor->parseWindowVectorArgsRelative(args, PWINDOW->m_realPosition->goal());
return wrap(Actions::move(POS));
}
static SDispatchResult movewindowpixel(const std::string& args) {
const auto WINDOWREGEX = args.substr(args.find_first_of(',') + 1);
const auto MOVECMD = args.substr(0, args.find_first_of(','));
const auto PWINDOW = g_pCompositor->getWindowByRegex(WINDOWREGEX);
if (!PWINDOW)
return {.success = false, .error = "moveWindow: no window"};
const auto POS = g_pCompositor->parseWindowVectorArgsRelative(MOVECMD, PWINDOW->m_realPosition->goal());
return wrap(Actions::move(POS, false, PWINDOW));
}
static SDispatchResult resizewindowpixel(const std::string& args) {
const auto WINDOWREGEX = args.substr(args.find_first_of(',') + 1);
const auto MOVECMD = args.substr(0, args.find_first_of(','));
const auto PWINDOW = g_pCompositor->getWindowByRegex(WINDOWREGEX);
if (!PWINDOW)
return {.success = false, .error = "resizeWindow: no window"};
const auto SIZ = g_pCompositor->parseWindowVectorArgsRelative(MOVECMD, PWINDOW->m_realSize->goal());
if (SIZ.x < 1 || SIZ.y < 1)
return {.success = false, .error = "Invalid size"};
return wrap(Actions::resize(SIZ, false, PWINDOW));
}
static SDispatchResult cyclenext(const std::string& arg) {
CVarList2 args(arg, 0, 's', true);
const bool PREV = args.contains("prev") || args.contains("p") || args.contains("last") || args.contains("l");
const bool NEXT = args.contains("next") || args.contains("n");
std::optional<bool> onlyTiled = {};
std::optional<bool> onlyFloating = {};
if (args.contains("tile") || args.contains("tiled"))
onlyTiled = true;
if (args.contains("float") || args.contains("floating"))
onlyFloating = true;
// "hist" and "visible" modes are not mapped to the new API - they remain niche.
// The new cycleNext uses a simple next/prev boolean.
// PREV is default in classic alt+tab, NEXT overrides it.
return wrap(Actions::cycleNext(NEXT || !PREV, onlyTiled, onlyFloating));
}
static SDispatchResult focuswindow(const std::string& regexp) {
const auto PWINDOW = g_pCompositor->getWindowByRegex(regexp);
if (!PWINDOW)
return {.success = false, .error = "No such window found"};
return wrap(Actions::focus(PWINDOW));
}
static SDispatchResult tagwindow(const std::string& args) {
CVarList2 vars(args, 0, 's', true);
PHLWINDOW PWINDOW = nullptr;
if (vars.size() == 1)
; // use focused (nullptr)
else if (vars.size() == 2)
PWINDOW = g_pCompositor->getWindowByRegex(std::string(vars[1]));
else
return {.success = false, .error = "Invalid number of arguments, expected 1 or 2"};
return wrap(Actions::tag(std::string(vars[0]), PWINDOW));
}
static SDispatchResult toggleswallow(const std::string&) {
return wrap(Actions::toggleSwallow());
}
static SDispatchResult setsubmap(const std::string& submap) {
return wrap(Actions::setSubmap(submap));
}
static SDispatchResult passDispatcher(const std::string& regexp) {
const auto PWINDOW = g_pCompositor->getWindowByRegex(regexp);
if (!PWINDOW)
return {.success = false, .error = "pass: window not found"};
return wrap(Actions::pass(PWINDOW));
}
static SDispatchResult sendshortcut(const std::string& args) {
CVarList2 ARGS(args, 3);
if (ARGS.size() != 3)
return {.success = false, .error = "sendshortcut: invalid args"};
const auto MOD = g_pKeybindManager->stringToModMask(std::string(ARGS[0]));
const auto KEY = std::string(ARGS[1]);
uint32_t keycode = 0;
if (isNumber(KEY) && std::stoi(KEY) > 9)
keycode = std::stoi(KEY);
else if (KEY.starts_with("code:") && isNumber(KEY.substr(5)))
keycode = std::stoi(KEY.substr(5));
else if (KEY.starts_with("mouse:") && isNumber(KEY.substr(6))) {
keycode = std::stoi(KEY.substr(6));
if (keycode < 272)
return {.success = false, .error = "sendshortcut: invalid mouse button"};
} else {
// resolve keycode from key name via xkb
const auto KEYSYM = xkb_keysym_from_name(KEY.c_str(), XKB_KEYSYM_CASE_INSENSITIVE);
keycode = 0;
const auto KB = g_pSeatManager->m_keyboard;
if (!KB)
return {.success = false, .error = "sendshortcut: no kb"};
const auto KEYPAIRSTRING = std::format("{}{}", rc<uintptr_t>(KB.get()), KEY);
if (!g_pKeybindManager->m_keyToCodeCache.contains(KEYPAIRSTRING)) {
xkb_keymap* km = KB->m_xkbKeymap;
xkb_state* ks = KB->m_xkbState;
xkb_keycode_t keycode_min = xkb_keymap_min_keycode(km);
xkb_keycode_t keycode_max = xkb_keymap_max_keycode(km);
for (xkb_keycode_t kc = keycode_min; kc <= keycode_max; ++kc) {
xkb_keysym_t sym = xkb_state_key_get_one_sym(ks, kc);
if (sym == KEYSYM) {
keycode = kc;
g_pKeybindManager->m_keyToCodeCache[KEYPAIRSTRING] = keycode;
}
}
if (!keycode)
return {.success = false, .error = "sendshortcut: key not found"};
} else
keycode = g_pKeybindManager->m_keyToCodeCache[KEYPAIRSTRING];
}
if (!keycode)
return {.success = false, .error = "sendshortcut: invalid key"};
const std::string regexp = std::string(ARGS[2]);
PHLWINDOW PWINDOW = regexp.empty() ? nullptr : g_pCompositor->getWindowByRegex(regexp);
if (!regexp.empty() && !PWINDOW)
return {.success = false, .error = "sendshortcut: window not found"};
return wrap(Actions::pass(MOD, keycode, PWINDOW));
}
static SDispatchResult sendkeystate(const std::string& args) {
CVarList2 ARGS(args, 4);
if (ARGS.size() != 4)
return {.success = false, .error = "sendkeystate: invalid args"};
const auto STATE = ARGS[2];
if (STATE != "down" && STATE != "repeat" && STATE != "up")
return {.success = false, .error = "sendkeystate: invalid state, must be 'down', 'repeat', or 'up'"};
uint32_t keyState = 0;
if (STATE == "down")
keyState = 1;
else if (STATE == "repeat")
keyState = 2;
// Reuse sendshortcut for keycode resolution, but wrap with state
std::string modifiedArgs = std::string(ARGS[0]) + "," + std::string(ARGS[1]) + "," + std::string(ARGS[3]);
// We need to resolve the keycode first, so delegate to sendshortcut parsing.
// But sendkeystate overrides m_passPressed. Let's just call through sendshortcut
// with the proper state set.
const int oldPassPressed = Config::Actions::state()->m_passPressed;
if (keyState == 1 || keyState == 2)
Config::Actions::state()->m_passPressed = 1;
else
Config::Actions::state()->m_passPressed = 0;
auto result = sendshortcut(modifiedArgs);
if (keyState == 2 && result.success)
result = sendshortcut(modifiedArgs);
Config::Actions::state()->m_passPressed = oldPassPressed;
return result;
}
static SDispatchResult layoutmsg(const std::string& msg) {
return wrap(Actions::layoutMessage(msg));
}
static SDispatchResult dpmsDispatcher(const std::string& arg) {
eTogglableAction action;
if (arg.starts_with("on"))
action = TOGGLE_ACTION_ENABLE;
else if (arg.starts_with("toggle"))
action = TOGGLE_ACTION_TOGGLE;
else
action = TOGGLE_ACTION_DISABLE;
std::optional<PHLMONITOR> mon = std::nullopt;
if (arg.find_first_of(' ') != std::string::npos) {
auto port = arg.substr(arg.find_first_of(' ') + 1);
auto pMon = g_pCompositor->getMonitorFromString(port);
if (pMon)
mon = pMon;
}
return wrap(Actions::dpms(action, mon));
}
static SDispatchResult swapnext(const std::string& arg) {
return wrap(Actions::swapNext(arg != "l" && arg != "last" && arg != "prev" && arg != "b" && arg != "back"));
}
static SDispatchResult swapactiveworkspaces(const std::string& args) {
const auto MON1 = args.substr(0, args.find_first_of(' '));
const auto MON2 = args.substr(args.find_first_of(' ') + 1);
const auto PMON1 = g_pCompositor->getMonitorFromString(MON1);
const auto PMON2 = g_pCompositor->getMonitorFromString(MON2);
if (!PMON1 || !PMON2)
return {.success = false, .error = "Monitor not found"};
return wrap(Actions::swapActiveWorkspaces(PMON1, PMON2));
}
static SDispatchResult pin(const std::string& args) {
auto w = windowFromArg(args);
return wrap(Actions::pinWindow(TOGGLE_ACTION_TOGGLE, w));
}
static SDispatchResult mouseDispatcher(const std::string& args) {
return wrap(Actions::mouse(args.substr(1)));
}
static SDispatchResult bringactivetotop(const std::string&) {
return wrap(Actions::alterZOrder("top"));
}
static SDispatchResult alterzorder(const std::string& args) {
const auto WINDOWREGEX = args.substr(args.find_first_of(',') + 1);
const auto POSITION = args.substr(0, args.find_first_of(','));
auto PWINDOW = g_pCompositor->getWindowByRegex(WINDOWREGEX);
if (!PWINDOW && Desktop::focusState()->window() && Desktop::focusState()->window()->m_isFloating)
PWINDOW = Desktop::focusState()->window();
return wrap(Actions::alterZOrder(POSITION, PWINDOW));
}
static SDispatchResult focusurgentorlast(const std::string&) {
return wrap(Actions::focusUrgentOrLast());
}
static SDispatchResult focuscurrentorlast(const std::string&) {
return wrap(Actions::focusCurrentOrLast());
}
static SDispatchResult lockgroups(const std::string& args) {
eTogglableAction action;
if (args == "toggle")
action = TOGGLE_ACTION_TOGGLE;
else if (args == "lock" || args.empty() || args == "lockgroups")
action = TOGGLE_ACTION_ENABLE;
else
action = TOGGLE_ACTION_DISABLE;
return wrap(Actions::lockGroups(action));
}
static SDispatchResult lockactivegroup(const std::string& args) {
eTogglableAction action;
if (args == "toggle")
action = TOGGLE_ACTION_TOGGLE;
else if (args == "lock")
action = TOGGLE_ACTION_ENABLE;
else
action = TOGGLE_ACTION_DISABLE;
return wrap(Actions::lockActiveGroup(action));
}
static SDispatchResult moveintogroup(const std::string& args) {
Math::eDirection dir = Math::fromChar(args[0]);
if (dir == Math::DIRECTION_DEFAULT)
return {.success = false, .error = std::format("Unsupported direction: {}", args[0])};
return wrap(Actions::moveIntoGroup(dir));
}
static SDispatchResult moveintoorcreategroup(const std::string& args) {
Math::eDirection dir = Math::fromChar(args[0]);
if (dir == Math::DIRECTION_DEFAULT)
return {.success = false, .error = std::format("Unsupported direction: {}", args[0])};
return wrap(Actions::moveIntoOrCreateGroup(dir));
}
static SDispatchResult moveoutofgroup(const std::string& args) {
if (args != "active" && args.length() > 1) {
auto PWINDOW = g_pCompositor->getWindowByRegex(args);
return wrap(Actions::moveOutOfGroup(Math::DIRECTION_DEFAULT, PWINDOW));
}
return wrap(Actions::moveOutOfGroup(Math::DIRECTION_DEFAULT));
}
static SDispatchResult movewindoworgroup(const std::string& args) {
Math::eDirection dir = Math::fromChar(args[0]);
if (dir == Math::DIRECTION_DEFAULT)
return {.success = false, .error = std::format("Unsupported direction: {}", args[0])};
return wrap(Actions::moveWindowOrGroup(dir));
}
static SDispatchResult denywindowfromgroup(const std::string& args) {
eTogglableAction action;
if (args == "toggle")
action = TOGGLE_ACTION_TOGGLE;
else if (args == "on")
action = TOGGLE_ACTION_ENABLE;
else
action = TOGGLE_ACTION_DISABLE;
return wrap(Actions::denyWindowFromGroup(action));
}
static SDispatchResult eventDispatcher(const std::string& args) {
return wrap(Actions::event(args));
}
static SDispatchResult globalDispatcher(const std::string& args) {
return wrap(Actions::global(args));
}
static SDispatchResult setprop(const std::string& args) {
CVarList2 vars(args, 3, ' ');
if (vars.size() < 3)
return {.success = false, .error = "Not enough args"};
const auto PWINDOW = g_pCompositor->getWindowByRegex(std::string(vars[0]));
if (!PWINDOW)
return {.success = false, .error = "Window not found"};
// Reconstruct val from remaining args (for multi-arg values like colors)
return wrap(Actions::setProp(std::string(vars[1]), vars.join(" ", 2), PWINDOW));
}
static SDispatchResult forceidle(const std::string& args) {
std::optional<float> duration = getPlusMinusKeywordResult(args, 0);
if (!duration.has_value())
return {.success = false, .error = "Duration invalid in forceIdle"};
return wrap(Actions::forceIdle(duration.value()));
}
CDispatcherTranslator::CDispatcherTranslator() {
m_dispMap["exec"] = ::exec;
m_dispMap["execr"] = ::execr;
m_dispMap["killactive"] = ::killactive;
m_dispMap["forcekillactive"] = ::forcekillactive;
m_dispMap["closewindow"] = ::closewindow;
m_dispMap["killwindow"] = ::killwindow;
m_dispMap["signal"] = ::signalactive;
m_dispMap["signalwindow"] = ::signalwindow;
m_dispMap["togglefloating"] = ::togglefloating;
m_dispMap["setfloating"] = ::setfloating;
m_dispMap["settiled"] = ::settiled;
m_dispMap["workspace"] = ::workspace;
m_dispMap["renameworkspace"] = ::renameworkspace;
m_dispMap["fullscreen"] = ::fullscreen;
m_dispMap["fullscreenstate"] = ::fullscreenstate;
m_dispMap["movetoworkspace"] = ::movetoworkspace;
m_dispMap["movetoworkspacesilent"] = ::movetoworkspacesilent;
m_dispMap["pseudo"] = ::pseudo;
m_dispMap["movefocus"] = ::movefocus;
m_dispMap["movewindow"] = ::movewindow;
m_dispMap["swapwindow"] = ::swapwindow;
m_dispMap["centerwindow"] = ::centerwindow;
m_dispMap["togglegroup"] = ::togglegroup;
m_dispMap["changegroupactive"] = ::changegroupactive;
m_dispMap["movegroupwindow"] = ::movegroupwindow;
m_dispMap["focusmonitor"] = ::focusmonitor;
m_dispMap["movecursortocorner"] = ::movecursortocorner;
m_dispMap["movecursor"] = ::movecursor;
m_dispMap["workspaceopt"] = ::workspaceopt;
m_dispMap["exit"] = ::exitHyprland;
m_dispMap["movecurrentworkspacetomonitor"] = ::movecurrentworkspacetomonitor;
m_dispMap["focusworkspaceoncurrentmonitor"] = ::focusworkspaceoncurrentmonitor;
m_dispMap["moveworkspacetomonitor"] = ::moveworkspacetomonitor;
m_dispMap["togglespecialworkspace"] = ::togglespecialworkspace;
m_dispMap["forcerendererreload"] = ::forcerendererreload;
m_dispMap["resizeactive"] = ::resizeactive;
m_dispMap["moveactive"] = ::moveactive;
m_dispMap["cyclenext"] = ::cyclenext;
m_dispMap["focuswindowbyclass"] = ::focuswindow;
m_dispMap["focuswindow"] = ::focuswindow;
m_dispMap["tagwindow"] = ::tagwindow;
m_dispMap["toggleswallow"] = ::toggleswallow;
m_dispMap["submap"] = ::setsubmap;
m_dispMap["pass"] = ::passDispatcher;
m_dispMap["sendshortcut"] = ::sendshortcut;
m_dispMap["sendkeystate"] = ::sendkeystate;
m_dispMap["layoutmsg"] = ::layoutmsg;
m_dispMap["dpms"] = ::dpmsDispatcher;
m_dispMap["movewindowpixel"] = ::movewindowpixel;
m_dispMap["resizewindowpixel"] = ::resizewindowpixel;
m_dispMap["swapnext"] = ::swapnext;
m_dispMap["swapactiveworkspaces"] = ::swapactiveworkspaces;
m_dispMap["pin"] = ::pin;
m_dispMap["mouse"] = ::mouseDispatcher;
m_dispMap["bringactivetotop"] = ::bringactivetotop;
m_dispMap["alterzorder"] = ::alterzorder;
m_dispMap["focusurgentorlast"] = ::focusurgentorlast;
m_dispMap["focuscurrentorlast"] = ::focuscurrentorlast;
m_dispMap["lockgroups"] = ::lockgroups;
m_dispMap["lockactivegroup"] = ::lockactivegroup;
m_dispMap["moveintogroup"] = ::moveintogroup;
m_dispMap["moveintoorcreategroup"] = ::moveintoorcreategroup;
m_dispMap["moveoutofgroup"] = ::moveoutofgroup;
m_dispMap["movewindoworgroup"] = ::movewindoworgroup;
m_dispMap["setignoregrouplock"] = [](const std::string&) -> SDispatchResult { return {}; }; // deprecated
m_dispMap["denywindowfromgroup"] = ::denywindowfromgroup;
m_dispMap["event"] = ::eventDispatcher;
m_dispMap["global"] = ::globalDispatcher;
m_dispMap["setprop"] = ::setprop;
m_dispMap["forceidle"] = ::forceidle;
}
@@ -0,0 +1,23 @@
#pragma once
#include "../../SharedDefs.hpp"
#include "../../helpers/memory/Memory.hpp"
#include <functional>
#include <unordered_map>
#include <string>
namespace Config::Legacy {
class CDispatcherTranslator {
public:
CDispatcherTranslator();
~CDispatcherTranslator() = default;
SDispatchResult run(const std::string& dispatcher, const std::string& data);
private:
std::unordered_map<std::string, std::function<SDispatchResult(const std::string&)>> m_dispMap;
};
UP<CDispatcherTranslator>& translator();
}
File diff suppressed because it is too large Load Diff
+186
View File
@@ -0,0 +1,186 @@
#pragma once
#include <hyprutils/animation/AnimationConfig.hpp>
#include <vector>
#include <string>
#include <optional>
#include <chrono>
#include <string_view>
#include <unordered_map>
#include "../../helpers/memory/Memory.hpp"
#include "../ConfigManager.hpp"
#include "../../managers/eventLoop/EventLoopTimer.hpp"
#include "./types/LuaConfigValue.hpp"
#include "./LuaEventHandler.hpp"
#include "../../desktop/rule/windowRule/WindowRule.hpp"
#include "../../desktop/rule/layerRule/LayerRule.hpp"
#include "../../SharedDefs.hpp"
#include "../../managers/KeybindManager.hpp"
extern "C" {
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}
namespace Config::Supplementary {
struct SConfigOptionDescription;
};
namespace Config::Lua {
class CConfigManager;
class CConfigManagerPluginLuaTestAccessor;
}
namespace Config::Lua::Bindings {
void registerBindings(lua_State* L, CConfigManager* mgr);
}
namespace Config::Lua {
class CConfigManager : public Config::IConfigManager {
public:
CConfigManager();
virtual eConfigManagerType type() override;
virtual void init() override;
virtual void reload() override;
virtual std::string verify() override;
virtual int getDeviceInt(const std::string&, const std::string&, const std::string& fallback = "") override;
virtual float getDeviceFloat(const std::string&, const std::string&, const std::string& fallback = "") override;
virtual Vector2D getDeviceVec(const std::string&, const std::string&, const std::string& fallback = "") override;
virtual std::string getDeviceString(const std::string&, const std::string&, const std::string& fallback = "") override;
virtual bool deviceConfigExplicitlySet(const std::string&, const std::string&) override;
virtual bool deviceConfigExists(const std::string&) override;
virtual SConfigOptionReply getConfigValue(const std::string&) override;
virtual std::string getMainConfigPath() override;
virtual std::string getErrors() override;
virtual std::string getConfigString() override;
virtual std::string currentConfigPath() override;
virtual const std::vector<std::string>& getConfigPaths() override;
virtual std::expected<void, std::string> generateDefaultConfig(const std::filesystem::path&, bool safeMode) override;
virtual void handlePluginLoads() override;
virtual bool configVerifPassed() override;
virtual std::expected<void, std::string> registerPluginValue(void* handle, SP<Config::Values::IValue> value) override;
virtual void onPluginUnload(void* handle) override;
int invokePluginLuaFunctionByID(uint64_t id, lua_State* L);
std::expected<void, std::string> registerPluginLuaFunction(void* handle, const std::string& namespace_, const std::string& name, PLUGIN_LUA_FN fn);
std::expected<void, std::string> unregisterPluginLuaFunction(void* handle, const std::string& namespace_, const std::string& name);
void addError(std::string&& str);
void registerLuaRef(int ref);
void callLuaFn(int ref);
// execute an arbitrary lua string on the current state.
std::optional<std::string> eval(const std::string& code);
int guardedPCall(int nargs, int nresults, int errfunc, int timeoutMs, std::string_view context);
static CConfigManager* fromLuaState(lua_State* L);
static constexpr int LUA_WATCHDOG_INSTRUCTION_INTERVAL = 10000;
static constexpr int LUA_TIMEOUT_CONFIG_RELOAD_MS = 1500;
static constexpr int LUA_TIMEOUT_EVENT_CALLBACK_MS = 50;
static constexpr int LUA_TIMEOUT_KEYBIND_CALLBACK_MS = 100;
static constexpr int LUA_TIMEOUT_TIMER_CALLBACK_MS = 50;
static constexpr int LUA_TIMEOUT_EVAL_MS = 250;
static constexpr int LUA_TIMEOUT_DISPATCH_MS = 100;
bool isFirstLaunch() const;
bool isDynamicParse() const;
std::string m_currentSubmap;
std::string m_currentSubmapReset;
UP<CLuaEventHandler> m_eventHandler;
struct SLuaTimer {
SP<CEventLoopTimer> timer;
int luaRef = LUA_NOREF; // registry ref to the lua callback
};
std::vector<SLuaTimer> m_luaTimers;
std::vector<std::string> m_registeredPlugins;
std::unordered_map<std::string, UP<ILuaConfigValue>> m_configValues;
struct SDeviceConfig {
SDeviceConfig();
std::unordered_map<std::string, UP<ILuaConfigValue>> values;
};
std::unordered_map<std::string, SDeviceConfig> m_deviceConfigs;
std::vector<std::string> m_errors, m_configPaths;
// named window/layer rules for merge-on-redeclaration
std::unordered_map<std::string, SP<Desktop::Rule::CWindowRule>> m_luaWindowRules;
std::unordered_map<std::string, SP<Desktop::Rule::CLayerRule>> m_luaLayerRules;
private:
void reinitLuaState();
void postConfigReload();
void registerValue(const char* name, ILuaConfigValue* val);
void cleanTimers();
void clearHeldLuaRefs();
std::string luaConfigValueName(const std::string& s);
std::expected<void, std::string> registerPluginLuaFunctionInState(uint64_t id, const std::string& namespace_, const std::string& name);
std::expected<void, std::string> unregisterPluginLuaFunctionInState(const std::string& namespace_, const std::string& name);
void erasePluginLuaFunction(uint64_t id);
void reregisterLuaPluginFns();
static void watchdogHook(lua_State* L, lua_Debug* ar);
lua_State* m_lua = nullptr;
bool m_lastConfigVerificationWasSuccessful = true;
bool m_isFirstLaunch = true;
bool m_manualCrashInitiated = false;
bool m_watchdogActive = false;
bool m_isParsingConfig = false;
bool m_isEvaluating = false;
std::chrono::steady_clock::time_point m_watchdogDeadline;
std::string m_watchdogContext;
std::string m_mainConfigPath;
std::vector<int> m_heldLuaRefs;
// this is here for legacy reasons.
std::unordered_map<std::string, const void*> m_configPtrMap;
// this is here for plugin reasons.
std::unordered_map<void* /* HANDLE */, std::vector<std::string>> m_pluginValues;
struct SPluginLuaFunction {
uint64_t id = 0;
void* handle = nullptr;
std::string namespace_;
std::string name;
Config::PLUGIN_LUA_FN fn = nullptr;
};
std::vector<SPluginLuaFunction> m_pluginLuaFunctions;
ILuaConfigValue* findDeviceValue(const std::string& dev, const std::string& field);
friend class CConfigManagerPluginLuaTestAccessor;
};
WP<CConfigManager> mgr();
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <string_view>
inline constexpr std::string_view AUTOGENERATED_PREFIX = R"#(
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
-- AUTOGENERATED HYPRLAND CONFIG. --
-- EDIT THIS CONFIG ACCORDING TO THE WIKI INSTRUCTIONS. --
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
hl.config({ autogenerated = true }) -- remove this line to remove the warning
)#";
inline constexpr char EXAMPLE_CONFIG_BYTES[] = {
#embed "../../../example/hyprland.lua"
};
inline constexpr std::string_view EXAMPLE_CONFIG = {EXAMPLE_CONFIG_BYTES, sizeof(EXAMPLE_CONFIG_BYTES)};
+47
View File
@@ -0,0 +1,47 @@
#pragma once
namespace Config::Lua {
constexpr const char* EMERGENCY_PCALL = R"#(
local function shell_ok(cmd)
local a, b, c = os.execute(cmd)
if type(a) == "number" then
return a == 0
end
return a == true and b == "exit" and c == 0
end
local function first_installed(candidates)
for _, bin in ipairs(candidates) do
if shell_ok(("command -v %q >/dev/null 2>&1"):format(bin)) then
return bin
end
end
return nil
end
local function launch_first_installed(candidates)
local term = first_installed(candidates)
if not term then
return false
end
hl.dispatch(hl.dsp.exec_cmd(term))
return true
end
hl.bind("SUPER + Q", function()
launch_first_installed({ "kitty", "alacritty", "foot", "wezterm", "gnome-terminal", "xterm" })
end)
hl.bind("SUPER + R", hl.dsp.exec_cmd("hyprland-run"))
hl.bind("SUPER + M", hl.dsp.exit())
hl.window_rule({
name = "move-hyprland-run",
match = { class = "hyprland-run" },
move = "20 monitor_h-120",
float = true,
})
)#";
}
+7
View File
@@ -0,0 +1,7 @@
#include "LuaBindings.hpp"
#include "bindings/LuaBindingsInternal.hpp"
void Config::Lua::Bindings::registerBindings(lua_State* L, CConfigManager* mgr) {
Internal::registerBindingsImpl(L, mgr);
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
extern "C" {
#include <lua.h>
}
namespace Config::Lua {
class CConfigManager;
}
namespace Config::Lua::Bindings {
void registerBindings(lua_State* L, CConfigManager* mgr);
}
+233
View File
@@ -0,0 +1,233 @@
#include "LuaEventHandler.hpp"
#include "ConfigManager.hpp"
#include "objects/LuaWindow.hpp"
#include "objects/LuaWorkspace.hpp"
#include "objects/LuaMonitor.hpp"
#include "objects/LuaLayerSurface.hpp"
#include "../../defines.hpp"
#include "../../event/EventBus.hpp"
#include "../../desktop/state/FocusState.hpp"
extern "C" {
#include <lauxlib.h>
}
#include <format>
#include <algorithm>
using namespace Config::Lua;
using namespace Config::Lua::Objects;
void CLuaEventHandler::dispatch(const std::string& name, int nargs, const std::function<void()>& pushArgs) {
auto it = m_callbacks.find(name);
if (it == m_callbacks.end() || it->second.empty())
return;
if (m_dispatchDepth >= MAX_DISPATCH_DEPTH) {
Log::logger->log(Log::WARN, "[LuaEvents] max dispatch depth ({}) reached while handling '{}'", MAX_DISPATCH_DEPTH, name);
return;
}
const auto handles = it->second;
for (const auto handle : handles) {
const auto sub = m_subscriptions.find(handle);
if (sub == m_subscriptions.end())
continue;
if (m_activeHandles.contains(handle)) {
if (m_reentrancyWarnedHandles.emplace(handle).second)
Log::logger->log(Log::WARN, "[LuaEvents] suppressed recursive hl.on(\"{}\") callback invocation", name);
continue;
}
struct SDispatchScope {
CLuaEventHandler* self = nullptr;
uint64_t handle = 0;
~SDispatchScope() {
if (!self)
return;
self->m_activeHandles.erase(handle);
if (self->m_dispatchDepth > 0)
--self->m_dispatchDepth;
}
} dispatchScope{.self = this, .handle = handle};
m_activeHandles.emplace(handle);
++m_dispatchDepth;
lua_rawgeti(m_lua, LUA_REGISTRYINDEX, sub->second.luaRef);
pushArgs();
int status = LUA_OK;
if (auto* mgr = CConfigManager::fromLuaState(m_lua); mgr)
status = mgr->guardedPCall(nargs, 0, 0, CConfigManager::LUA_TIMEOUT_EVENT_CALLBACK_MS, std::format("hl.on(\"{}\") callback", name));
else
status = lua_pcall(m_lua, nargs, 0, 0);
if (status != LUA_OK) {
const char* err = lua_tostring(m_lua, -1);
Config::Lua::mgr()->addError(std::format("hl.on(\"{}\") callback: {}", name, err ? err : "(unknown)"));
lua_pop(m_lua, 1);
}
}
}
CLuaEventHandler::CLuaEventHandler(lua_State* L) : m_lua(L) {
CLuaWindow{}.setup(L);
CLuaWorkspace{}.setup(L);
CLuaMonitor{}.setup(L);
CLuaLayerSurface{}.setup(L);
using namespace Event;
m_listeners.push_back(bus()->m_events.window.open.listen([this](PHLWINDOW w) { dispatch("window.open", 1, [&] { CLuaWindow::push(m_lua, w); }); }));
m_listeners.push_back(bus()->m_events.window.openEarly.listen([this](PHLWINDOW w) { dispatch("window.open_early", 1, [&] { CLuaWindow::push(m_lua, w); }); }));
m_listeners.push_back(bus()->m_events.window.close.listen([this](PHLWINDOW w) { dispatch("window.close", 1, [&] { CLuaWindow::push(m_lua, w); }); }));
m_listeners.push_back(bus()->m_events.window.destroy.listen([this](PHLWINDOW w) { dispatch("window.destroy", 1, [&] { CLuaWindow::push(m_lua, w); }); }));
m_listeners.push_back(bus()->m_events.window.kill.listen([this](PHLWINDOW w) { dispatch("window.kill", 1, [&] { CLuaWindow::push(m_lua, w); }); }));
m_listeners.push_back(bus()->m_events.window.active.listen([this](PHLWINDOW w, Desktop::eFocusReason r) {
dispatch("window.active", 2, [&] {
CLuaWindow::push(m_lua, w);
lua_pushinteger(m_lua, sc<lua_Integer>(r));
});
}));
m_listeners.push_back(bus()->m_events.window.urgent.listen([this](PHLWINDOW w) { dispatch("window.urgent", 1, [&] { CLuaWindow::push(m_lua, w); }); }));
m_listeners.push_back(bus()->m_events.window.title.listen([this](PHLWINDOW w) { dispatch("window.title", 1, [&] { CLuaWindow::push(m_lua, w); }); }));
m_listeners.push_back(bus()->m_events.window.class_.listen([this](PHLWINDOW w) { dispatch("window.class", 1, [&] { CLuaWindow::push(m_lua, w); }); }));
m_listeners.push_back(bus()->m_events.window.pin.listen([this](PHLWINDOW w) { dispatch("window.pin", 1, [&] { CLuaWindow::push(m_lua, w); }); }));
m_listeners.push_back(bus()->m_events.window.fullscreen.listen([this](PHLWINDOW w) { dispatch("window.fullscreen", 1, [&] { CLuaWindow::push(m_lua, w); }); }));
m_listeners.push_back(bus()->m_events.window.updateRules.listen([this](PHLWINDOW w) { dispatch("window.update_rules", 1, [&] { CLuaWindow::push(m_lua, w); }); }));
m_listeners.push_back(bus()->m_events.window.moveToWorkspace.listen([this](PHLWINDOW w, PHLWORKSPACE ws) {
dispatch("window.move_to_workspace", 2, [&] {
CLuaWindow::push(m_lua, w);
CLuaWorkspace::push(m_lua, ws);
});
}));
m_listeners.push_back(bus()->m_events.layer.opened.listen([this](PHLLS ls) { dispatch("layer.opened", 1, [&] { CLuaLayerSurface::push(m_lua, ls); }); }));
m_listeners.push_back(bus()->m_events.layer.closed.listen([this](PHLLS ls) { dispatch("layer.closed", 1, [&] { CLuaLayerSurface::push(m_lua, ls); }); }));
m_listeners.push_back(bus()->m_events.monitor.added.listen([this](PHLMONITOR mon) { dispatch("monitor.added", 1, [&] { CLuaMonitor::push(m_lua, mon); }); }));
m_listeners.push_back(bus()->m_events.monitor.removed.listen([this](PHLMONITOR mon) { dispatch("monitor.removed", 1, [&] { CLuaMonitor::push(m_lua, mon); }); }));
m_listeners.push_back(bus()->m_events.monitor.focused.listen([this](PHLMONITOR mon) { dispatch("monitor.focused", 1, [&] { CLuaMonitor::push(m_lua, mon); }); }));
m_listeners.push_back(bus()->m_events.monitor.layoutChanged.listen([this] { dispatch("monitor.layout_changed", 0, [] {}); }));
m_listeners.push_back(bus()->m_events.workspace.active.listen([this](PHLWORKSPACE ws) { dispatch("workspace.active", 1, [&] { CLuaWorkspace::push(m_lua, ws); }); }));
m_listeners.push_back(bus()->m_events.workspace.created.listen([this](PHLWORKSPACEREF wsRef) {
const auto ws = wsRef.lock();
if (!ws)
return;
dispatch("workspace.created", 1, [&] { CLuaWorkspace::push(m_lua, ws); });
}));
m_listeners.push_back(bus()->m_events.workspace.removed.listen([this](PHLWORKSPACEREF wsRef) {
const auto ws = wsRef.lock();
if (!ws)
return;
dispatch("workspace.removed", 1, [&] { CLuaWorkspace::push(m_lua, ws); });
}));
m_listeners.push_back(bus()->m_events.workspace.moveToMonitor.listen([this](PHLWORKSPACE ws, PHLMONITOR mon) {
dispatch("workspace.move_to_monitor", 2, [&] {
CLuaWorkspace::push(m_lua, ws);
CLuaMonitor::push(m_lua, mon);
});
}));
m_listeners.push_back(bus()->m_events.config.reloaded.listen([this] { dispatch("config.reloaded", 0, [] {}); }));
m_listeners.push_back(
bus()->m_events.keybinds.submap.listen([this](const std::string& submap) { dispatch("keybinds.submap", 1, [&] { lua_pushstring(m_lua, submap.c_str()); }); }));
m_listeners.push_back(bus()->m_events.screenshare.state.listen([this](bool state, uint8_t type, const std::string& name) {
dispatch("screenshare.state", 3, [&] {
lua_pushboolean(m_lua, state);
lua_pushinteger(m_lua, sc<lua_Integer>(type));
lua_pushstring(m_lua, name.c_str());
});
}));
m_listeners.push_back(bus()->m_events.start.listen([this]() { dispatch("hyprland.start", 0, [] {}); }));
m_listeners.push_back(bus()->m_events.exit.listen([this]() { dispatch("hyprland.shutdown", 0, [] {}); }));
}
CLuaEventHandler::~CLuaEventHandler() {
clearEvents();
}
std::optional<uint64_t> CLuaEventHandler::registerEvent(const std::string& name, int luaRef) {
if (!knownEvents().contains(name))
return std::nullopt;
const auto handle = m_nextHandle++;
m_callbacks[name].push_back(handle);
m_subscriptions[handle] = {.eventName = name, .luaRef = luaRef};
return handle;
}
bool CLuaEventHandler::unregisterEvent(uint64_t handle) {
const auto it = m_subscriptions.find(handle);
if (it == m_subscriptions.end())
return false;
luaL_unref(m_lua, LUA_REGISTRYINDEX, it->second.luaRef);
auto callbacksIt = m_callbacks.find(it->second.eventName);
if (callbacksIt != m_callbacks.end()) {
std::erase(callbacksIt->second, handle);
if (callbacksIt->second.empty())
m_callbacks.erase(callbacksIt);
}
m_subscriptions.erase(it);
m_activeHandles.erase(handle);
m_reentrancyWarnedHandles.erase(handle);
return true;
}
void CLuaEventHandler::clearEvents() {
for (const auto& s : m_subscriptions) {
luaL_unref(m_lua, LUA_REGISTRYINDEX, s.second.luaRef);
}
m_subscriptions.clear();
m_activeHandles.clear();
m_reentrancyWarnedHandles.clear();
m_callbacks.clear();
}
const std::unordered_set<std::string>& CLuaEventHandler::knownEvents() {
static const std::unordered_set<std::string> EVENTS = {
"window.open",
"window.open_early",
"window.close",
"window.destroy",
"window.kill",
"window.active",
"window.urgent",
"window.title",
"window.class",
"window.pin",
"window.fullscreen",
"window.update_rules",
"window.move_to_workspace",
"layer.opened",
"layer.closed",
"monitor.added",
"monitor.removed",
"monitor.focused",
"monitor.layout_changed",
"workspace.active",
"workspace.created",
"workspace.removed",
"workspace.move_to_monitor",
"config.reloaded",
"keybinds.submap",
"screenshare.state",
"hyprland.start",
"hyprland.shutdown",
};
return EVENTS;
}
+61
View File
@@ -0,0 +1,61 @@
#pragma once
#include <functional>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <optional>
#include <cstdint>
#include "../../helpers/memory/Memory.hpp"
#include "../../helpers/signal/Signal.hpp"
#include "../../desktop/DesktopTypes.hpp"
extern "C" {
#include <lua.h>
}
namespace Config::Lua {
// Manages hl.on() event subscriptions for a single Lua state lifetime.
// Destroyed (and recreated) on every config reload so callbacks never double-up.
//
// Hyprland objects (window, workspace, monitor, layer surface) are exposed to Lua
// as typed userdata holding a weak pointer. Field accesses read live C++ state via
// __index; accessing a field on a destroyed object raises a Lua error.
class CLuaEventHandler {
public:
explicit CLuaEventHandler(lua_State* L);
~CLuaEventHandler();
// Store a Lua function (as a registry ref) to be called when `name` fires.
// Returns a subscription handle, or std::nullopt if the event name is unknown.
std::optional<uint64_t> registerEvent(const std::string& name, int luaRef);
bool unregisterEvent(uint64_t handle);
void clearEvents();
static const std::unordered_set<std::string>& knownEvents();
private:
struct SSubscription {
std::string eventName;
int luaRef = -1;
};
lua_State* m_lua = nullptr;
std::unordered_map<std::string, std::vector<uint64_t>> m_callbacks;
std::unordered_map<uint64_t, SSubscription> m_subscriptions;
std::unordered_set<uint64_t> m_activeHandles;
std::unordered_set<uint64_t> m_reentrancyWarnedHandles;
uint64_t m_nextHandle = 1;
size_t m_dispatchDepth = 0;
std::vector<CHyprSignalListener> m_listeners;
static constexpr size_t MAX_DISPATCH_DEPTH = 32;
void dispatch(const std::string& name, int nargs, const std::function<void()>& pushArgs);
};
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,502 @@
#include "LuaBindingsInternal.hpp"
using namespace Config;
using namespace Config::Lua;
using namespace Config::Lua::Bindings;
namespace CA = Config::Actions;
namespace {
constexpr const char* LUA_WINDOW_MT = "HL.Window";
constexpr const char* LUA_WORKSPACE_MT = "HL.Workspace";
constexpr const char* LUA_MONITOR_MT = "HL.Monitor";
}
std::string Internal::argStr(lua_State* L, int idx) {
if (lua_type(L, idx) == LUA_TNUMBER)
return std::to_string((long long)lua_tonumber(L, idx));
size_t n = 0;
const char* s = luaL_checklstring(L, idx, &n);
return {s, n};
}
std::optional<std::string> Internal::tableOptStr(lua_State* L, int idx, const char* field) {
lua_getfield(L, idx, field);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return std::nullopt;
}
const char* s = lua_tostring(L, -1);
lua_pop(L, 1);
return s ? std::optional(std::string(s)) : std::nullopt;
}
std::optional<double> Internal::tableOptNum(lua_State* L, int idx, const char* field) {
lua_getfield(L, idx, field);
if (lua_isnil(L, -1) || !lua_isnumber(L, -1)) {
lua_pop(L, 1);
return std::nullopt;
}
const double v = lua_tonumber(L, -1);
lua_pop(L, 1);
return v;
}
std::optional<bool> Internal::tableOptBool(lua_State* L, int idx, const char* field) {
lua_getfield(L, idx, field);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return std::nullopt;
}
const bool v = lua_toboolean(L, -1);
lua_pop(L, 1);
return v;
}
PHLMONITOR Internal::monitorFromLuaSelectorOrObject(lua_State* L, int idx, const char* fnName) {
idx = lua_absindex(L, idx);
if (lua_isnil(L, idx))
return nullptr;
if (auto* ref = sc<PHLMONITORREF*>(luaL_testudata(L, idx, LUA_MONITOR_MT)); ref)
return ref->lock();
if (lua_isstring(L, idx) || lua_isnumber(L, idx))
return g_pCompositor->getMonitorFromString(argStr(L, idx));
Internal::configError(L, "{}: expected a monitor object or selector", fnName);
return nullptr;
}
PHLWORKSPACE Internal::workspaceFromLuaSelectorOrObject(lua_State* L, int idx, const char* fnName) {
idx = lua_absindex(L, idx);
if (lua_isnil(L, idx))
return nullptr;
if (auto* ref = sc<PHLWORKSPACEREF*>(luaL_testudata(L, idx, LUA_WORKSPACE_MT)); ref) {
auto ws = ref->lock();
if (!ws || ws->inert())
return nullptr;
return ws;
}
if (lua_isstring(L, idx) || lua_isnumber(L, idx))
return g_pCompositor->getWorkspaceByString(argStr(L, idx));
Internal::configError(L, "{}: expected a workspace object or selector", fnName);
return nullptr;
}
PHLWINDOW Internal::windowFromLuaSelectorOrObject(lua_State* L, int idx, const char* fnName) {
idx = lua_absindex(L, idx);
if (lua_isnil(L, idx))
return nullptr;
if (auto* ref = sc<PHLWINDOWREF*>(luaL_testudata(L, idx, LUA_WINDOW_MT)); ref)
return ref->lock();
if (lua_isstring(L, idx) || lua_isnumber(L, idx))
return g_pCompositor->getWindowByRegex(argStr(L, idx));
Internal::configError(L, "{}: expected a window object or selector", fnName);
return nullptr;
}
std::optional<PHLMONITOR> Internal::tableOptMonitor(lua_State* L, int idx, const char* field, const char* fnName) {
idx = lua_absindex(L, idx);
lua_getfield(L, idx, field);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return std::nullopt;
}
auto mon = monitorFromLuaSelectorOrObject(L, -1, fnName);
lua_pop(L, 1);
return mon;
}
std::optional<PHLWORKSPACE> Internal::tableOptWorkspace(lua_State* L, int idx, const char* field, const char* fnName) {
idx = lua_absindex(L, idx);
lua_getfield(L, idx, field);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return std::nullopt;
}
auto ws = workspaceFromLuaSelectorOrObject(L, -1, fnName);
lua_pop(L, 1);
return ws;
}
std::optional<PHLWINDOW> Internal::tableOptWindow(lua_State* L, int idx, const char* field, const char* fnName) {
idx = lua_absindex(L, idx);
lua_getfield(L, idx, field);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return std::nullopt;
}
auto window = windowFromLuaSelectorOrObject(L, -1, fnName);
lua_pop(L, 1);
return window;
}
std::optional<std::string> Internal::monitorSelectorFromLuaSelectorOrObject(lua_State* L, int idx, const char* fnName) {
idx = lua_absindex(L, idx);
if (lua_isnil(L, idx))
return std::nullopt;
if (auto* ref = sc<PHLMONITORREF*>(luaL_testudata(L, idx, LUA_MONITOR_MT)); ref) {
const auto mon = ref->lock();
if (!mon) {
Internal::configError(L, "{}: monitor object is expired", fnName);
return std::nullopt;
}
return mon->m_name;
}
if (lua_isstring(L, idx) || lua_isnumber(L, idx))
return argStr(L, idx);
Internal::configError(L, "{}: expected a monitor object or selector", fnName);
return std::nullopt;
}
std::optional<std::string> Internal::workspaceSelectorFromLuaSelectorOrObject(lua_State* L, int idx, const char* fnName) {
idx = lua_absindex(L, idx);
if (lua_isnil(L, idx))
return std::nullopt;
if (auto* ref = sc<PHLWORKSPACEREF*>(luaL_testudata(L, idx, LUA_WORKSPACE_MT)); ref) {
const auto ws = ref->lock();
if (!ws || ws->inert()) {
Internal::configError(L, "{}: workspace object is expired", fnName);
return std::nullopt;
}
return std::to_string(ws->m_id);
}
if (lua_isstring(L, idx) || lua_isnumber(L, idx))
return argStr(L, idx);
Internal::configError(L, "{}: expected a workspace object or selector", fnName);
return std::nullopt;
}
std::optional<std::string> Internal::windowSelectorFromLuaSelectorOrObject(lua_State* L, int idx, const char* fnName) {
idx = lua_absindex(L, idx);
if (lua_isnil(L, idx))
return std::nullopt;
if (auto* ref = sc<PHLWINDOWREF*>(luaL_testudata(L, idx, LUA_WINDOW_MT)); ref) {
const auto w = ref->lock();
if (!w) {
Internal::configError(L, "{}: window object is expired", fnName);
return std::nullopt;
}
return std::format("0x{:x}", reinterpret_cast<uintptr_t>(w.get()));
}
if (lua_isstring(L, idx) || lua_isnumber(L, idx))
return argStr(L, idx);
Internal::configError(L, "{}: expected a window object or selector", fnName);
return std::nullopt;
}
std::optional<std::string> Internal::tableOptMonitorSelector(lua_State* L, int idx, const char* field, const char* fnName) {
idx = lua_absindex(L, idx);
lua_getfield(L, idx, field);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return std::nullopt;
}
auto selector = monitorSelectorFromLuaSelectorOrObject(L, -1, fnName);
lua_pop(L, 1);
return selector;
}
std::optional<std::string> Internal::tableOptWorkspaceSelector(lua_State* L, int idx, const char* field, const char* fnName) {
idx = lua_absindex(L, idx);
lua_getfield(L, idx, field);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return std::nullopt;
}
auto selector = workspaceSelectorFromLuaSelectorOrObject(L, -1, fnName);
lua_pop(L, 1);
return selector;
}
std::optional<std::string> Internal::tableOptWindowSelector(lua_State* L, int idx, const char* field, const char* fnName) {
idx = lua_absindex(L, idx);
lua_getfield(L, idx, field);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return std::nullopt;
}
auto selector = windowSelectorFromLuaSelectorOrObject(L, -1, fnName);
lua_pop(L, 1);
return selector;
}
std::string Internal::requireTableFieldMonitorSelector(lua_State* L, int idx, const char* field, const char* fnName) {
auto selector = tableOptMonitorSelector(L, idx, field, fnName);
if (!selector) {
Internal::configError(L, "{}: '{}' is required", fnName, field);
return "";
}
return *selector;
}
std::string Internal::requireTableFieldWorkspaceSelector(lua_State* L, int idx, const char* field, const char* fnName) {
auto selector = tableOptWorkspaceSelector(L, idx, field, fnName);
if (!selector) {
Internal::configError(L, "{}: '{}' is required", fnName, field);
return "";
}
return *selector;
}
std::string Internal::requireTableFieldWindowSelector(lua_State* L, int idx, const char* field, const char* fnName) {
auto selector = tableOptWindowSelector(L, idx, field, fnName);
if (!selector) {
Internal::configError(L, "{}: '{}' is required", fnName, field);
return "";
}
return *selector;
}
Math::eDirection Internal::parseDirectionStr(const std::string& str) {
if (str == "left" || str == "l")
return Math::DIRECTION_LEFT;
if (str == "right" || str == "r")
return Math::DIRECTION_RIGHT;
if (str == "up" || str == "u" || str == "t")
return Math::DIRECTION_UP;
if (str == "down" || str == "d" || str == "b")
return Math::DIRECTION_DOWN;
return Math::DIRECTION_DEFAULT;
}
CA::eTogglableAction Internal::parseToggleStr(const std::string& str) {
if (str.empty() || str == "toggle")
return CA::TOGGLE_ACTION_TOGGLE;
if (str == "enable" || str == "on")
return CA::TOGGLE_ACTION_ENABLE;
if (str == "disable" || str == "off")
return CA::TOGGLE_ACTION_DISABLE;
return CA::TOGGLE_ACTION_TOGGLE;
}
std::optional<PHLWINDOW> Internal::windowFromUpval(lua_State* L, int idx) {
if (lua_isnil(L, lua_upvalueindex(idx)))
return std::nullopt;
return g_pCompositor->getWindowByRegex(lua_tostring(L, lua_upvalueindex(idx)));
}
void Internal::pushWindowUpval(lua_State* L, int tableIdx) {
if (lua_istable(L, tableIdx)) {
auto selector = tableOptWindowSelector(L, tableIdx, "window", "window selector");
if (!selector)
lua_pushnil(L);
else
lua_pushstring(L, selector->c_str());
} else
lua_pushnil(L);
}
void Internal::checkResult(lua_State* L, const CA::ActionResult& r) {
if (!r)
Internal::configError(L, "{}", r.error());
}
PHLWORKSPACE Internal::resolveWorkspaceStr(const std::string& args) {
const auto& [id, name, isAutoID] = getWorkspaceIDNameFromString(args);
if (id == WORKSPACE_INVALID)
return nullptr;
auto ws = g_pCompositor->getWorkspaceByID(id);
if (!ws) {
const auto PMONITOR = Desktop::focusState()->monitor();
if (PMONITOR)
ws = g_pCompositor->createNewWorkspace(id, PMONITOR->m_id, name, false);
}
return ws;
}
PHLMONITOR Internal::resolveMonitorStr(const std::string& args) {
auto mon = g_pCompositor->getMonitorFromString(args);
return mon;
}
std::string Internal::getSourceInfo(lua_State* L, int stackLevel) {
lua_Debug ar = {};
std::string sourceInfo = "?:?";
if (lua_getstack(L, stackLevel, &ar) && lua_getinfo(L, "Sl", &ar)) {
const char* src = ar.source;
if (src && src[0] == '@')
src++;
sourceInfo = std::format("{}:{}", src ? src : "?", ar.currentline);
}
return sourceInfo;
}
std::string Internal::requireTableFieldStr(lua_State* L, int idx, const char* field, const char* fnName) {
auto value = tableOptStr(L, idx, field);
if (!value) {
Internal::configError(L, "{}: '{}' is required", fnName, field);
return "";
}
return *value;
}
double Internal::requireTableFieldNum(lua_State* L, int idx, const char* field, const char* fnName) {
auto value = tableOptNum(L, idx, field);
if (!value) {
Internal::configError(L, "{}: '{}' is required", fnName, field);
return 0;
}
return *value;
}
CA::eTogglableAction Internal::tableToggleAction(lua_State* L, int idx, const char* field) {
if (!lua_istable(L, idx))
return CA::TOGGLE_ACTION_TOGGLE;
if (const auto action = tableOptStr(L, idx, field); action.has_value())
return parseToggleStr(*action);
return CA::TOGGLE_ACTION_TOGGLE;
}
void Internal::setFn(lua_State* L, const char* name, lua_CFunction fn) {
lua_pushcfunction(L, fn);
lua_setfield(L, -2, name);
}
void Internal::setMgrFn(lua_State* L, CConfigManager* mgr, const char* name, lua_CFunction fn) {
lua_pushlightuserdata(L, mgr);
lua_pushcclosure(L, fn, 1);
lua_setfield(L, -2, name);
}
int Internal::configError(lua_State* L, std::string s, int stackLevel) {
s = std::format("{}: {}", getSourceInfo(L, stackLevel), std::move(s));
Log::logger->log(Log::ERR, "Error in lua: {}", std::string_view{s});
Config::Lua::mgr()->addError(std::move(s));
return 0;
}
std::expected<std::string, std::string> Internal::ruleValueToString(lua_State* L) {
if (lua_type(L, -1) == LUA_TBOOLEAN)
return lua_toboolean(L, -1) ? "true" : "false";
if (lua_isinteger(L, -1))
return std::to_string(lua_tointeger(L, -1));
if (lua_isnumber(L, -1))
return std::to_string(lua_tonumber(L, -1));
if (lua_isstring(L, -1))
return std::string(lua_tostring(L, -1));
return std::unexpected("value must be a string, bool, or number");
}
std::expected<SP<Desktop::Rule::CWindowRule>, int> Internal::buildRuleFromTable(lua_State* L, int idx) {
SP<Desktop::Rule::CWindowRule> rule;
if (!lua_isnoneornil(L, idx)) {
if (!lua_istable(L, idx))
return std::unexpected(Internal::configError(L, "buildRuleFromTable: failed to build table for exec rules from argument"));
int optsIdx = lua_absindex(L, idx);
bool hasRuleEffects = false;
rule = makeShared<Desktop::Rule::CWindowRule>();
lua_pushnil(L);
while (lua_next(L, optsIdx) != 0) {
if (lua_type(L, -2) != LUA_TSTRING) {
lua_pop(L, 1);
return std::unexpected(Internal::configError(L, "buildRuleFromTable: effect key must be a string"));
}
std::string key = lua_tostring(L, -2);
if (key == "floating")
key = "float";
const auto* desc = Internal::findDescByName(Internal::WINDOW_RULE_EFFECT_DESCS, key);
if (!desc) {
const auto dynamicEffect = Desktop::Rule::windowEffects()->get(key);
if (!dynamicEffect.has_value()) {
lua_pop(L, 1);
return std::unexpected(Internal::configError(L, "buildRuleFromTable: unknown effect '{}'", key));
}
auto val = ruleValueToString(L);
if (!val) {
lua_pop(L, 1);
return std::unexpected(Internal::configError(L, "buildRuleFromTable: effect '{}': {}", key, val.error()));
}
rule->addEffect(*dynamicEffect, *val);
hasRuleEffects = true;
lua_pop(L, 1);
continue;
}
auto val = UP<ILuaConfigValue>(desc->factory());
auto err = val->parse(L);
if (err.errorCode != PARSE_ERROR_OK) {
const bool allowLegacyString = (key == "max_size" || key == "min_size" || key == "border_color") && lua_isstring(L, -1);
if (allowLegacyString)
rule->addEffect(desc->effect, lua_tostring(L, -1));
else {
lua_pop(L, 1);
return std::unexpected(Internal::configError(L, "buildRuleFromTable: effect '{}': {}", key, err.message));
}
} else
rule->addEffect(desc->effect, val->toString());
hasRuleEffects = true;
lua_pop(L, 1);
}
if (!hasRuleEffects)
return nullptr;
}
return rule;
}
@@ -0,0 +1,188 @@
#pragma once
#include "../LuaBindings.hpp"
#include "../ConfigManager.hpp"
#include "../types/LuaConfigValue.hpp"
#include "../types/LuaConfigBool.hpp"
#include "../types/LuaConfigFloat.hpp"
#include "../types/LuaConfigGradient.hpp"
#include "../types/LuaConfigInt.hpp"
#include "../types/LuaConfigString.hpp"
#include "../types/LuaConfigVec2.hpp"
#include "../../../Compositor.hpp"
#include "../../../helpers/MiscFunctions.hpp"
#include "../../../helpers/Monitor.hpp"
#include "../../../desktop/state/FocusState.hpp"
#include "../../../desktop/rule/windowRule/WindowRuleEffectContainer.hpp"
#include "../../../managers/KeybindManager.hpp"
#include "../../shared/actions/ConfigActions.hpp"
#include <functional>
#include <optional>
#include <format>
#include <string>
#include <string_view>
#include <utility>
extern "C" {
#include <lua.h>
#include <lauxlib.h>
}
namespace Config::Lua::Bindings::Internal {
struct SWindowRuleEffectDesc {
const char* name;
std::function<ILuaConfigValue*()> factory;
uint16_t effect;
};
using WE = Desktop::Rule::eWindowRuleEffect;
inline const SWindowRuleEffectDesc WINDOW_RULE_EFFECT_DESCS[] = {
{"float", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_FLOAT},
{"tile", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_TILE},
{"fullscreen", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_FULLSCREEN},
{"maximize", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_MAXIMIZE},
{"center", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_CENTER},
{"pseudo", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_PSEUDO},
{"no_initial_focus", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_NOINITIALFOCUS},
{"pin", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_PIN},
{"fullscreen_state", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_FULLSCREENSTATE},
{"move", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_MOVE},
{"size", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_SIZE},
{"monitor", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_MONITOR},
{"workspace", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_WORKSPACE},
{"group", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_GROUP},
{"suppress_event", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_SUPPRESSEVENT},
{"content", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_CONTENT},
{"no_close_for", []() -> ILuaConfigValue* { return new CLuaConfigInt(0); }, WE::WINDOW_RULE_EFFECT_NOCLOSEFOR},
{"scrolling_width", []() -> ILuaConfigValue* { return new CLuaConfigFloat(0.F); }, WE::WINDOW_RULE_EFFECT_SCROLLING_WIDTH},
{"rounding", []() -> ILuaConfigValue* { return new CLuaConfigInt(0, 0, 20); }, WE::WINDOW_RULE_EFFECT_ROUNDING},
{"border_size", []() -> ILuaConfigValue* { return new CLuaConfigInt(0); }, WE::WINDOW_RULE_EFFECT_BORDER_SIZE},
{"rounding_power", []() -> ILuaConfigValue* { return new CLuaConfigFloat(2.F, 1.F, 10.F); }, WE::WINDOW_RULE_EFFECT_ROUNDING_POWER},
{"scroll_mouse", []() -> ILuaConfigValue* { return new CLuaConfigFloat(1.F, 0.01F, 10.F); }, WE::WINDOW_RULE_EFFECT_SCROLL_MOUSE},
{"scroll_touchpad", []() -> ILuaConfigValue* { return new CLuaConfigFloat(1.F, 0.01F, 10.F); }, WE::WINDOW_RULE_EFFECT_SCROLL_TOUCHPAD},
{"animation", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_ANIMATION},
{"idle_inhibit", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_IDLE_INHIBIT},
{"opacity", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_OPACITY},
{"tag", []() -> ILuaConfigValue* { return new CLuaConfigString(STRVAL_EMPTY); }, WE::WINDOW_RULE_EFFECT_TAG},
{"max_size", []() -> ILuaConfigValue* { return new CLuaConfigVec2({0, 0}); }, WE::WINDOW_RULE_EFFECT_MAX_SIZE},
{"min_size", []() -> ILuaConfigValue* { return new CLuaConfigVec2({0, 0}); }, WE::WINDOW_RULE_EFFECT_MIN_SIZE},
{"border_color", []() -> ILuaConfigValue* { return new CLuaConfigGradient(CHyprColor(0xFF000000)); }, WE::WINDOW_RULE_EFFECT_BORDER_COLOR},
{"persistent_size", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_PERSISTENT_SIZE},
{"allows_input", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_ALLOWS_INPUT},
{"dim_around", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_DIM_AROUND},
{"decorate", []() -> ILuaConfigValue* { return new CLuaConfigBool(true); }, WE::WINDOW_RULE_EFFECT_DECORATE},
{"focus_on_activate", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_FOCUS_ON_ACTIVATE},
{"keep_aspect_ratio", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_KEEP_ASPECT_RATIO},
{"nearest_neighbor", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_NEAREST_NEIGHBOR},
{"no_anim", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_NO_ANIM},
{"no_blur", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_NO_BLUR},
{"no_dim", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_NO_DIM},
{"no_focus", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_NO_FOCUS},
{"no_follow_mouse", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_NO_FOLLOW_MOUSE},
{"no_max_size", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_NO_MAX_SIZE},
{"no_shadow", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_NO_SHADOW},
{"no_shortcuts_inhibit", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_NO_SHORTCUTS_INHIBIT},
{"opaque", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_OPAQUE},
{"force_rgbx", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_FORCE_RGBX},
{"sync_fullscreen", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_SYNC_FULLSCREEN},
{"immediate", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_IMMEDIATE},
{"xray", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_XRAY},
{"render_unfocused", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_RENDER_UNFOCUSED},
{"no_screen_share", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_NO_SCREEN_SHARE},
{"no_vrr", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_NO_VRR},
{"stay_focused", []() -> ILuaConfigValue* { return new CLuaConfigBool(false); }, WE::WINDOW_RULE_EFFECT_STAY_FOCUSED},
};
std::string argStr(lua_State* L, int idx);
std::optional<std::string> tableOptStr(lua_State* L, int idx, const char* field);
std::optional<double> tableOptNum(lua_State* L, int idx, const char* field);
std::optional<bool> tableOptBool(lua_State* L, int idx, const char* field);
PHLMONITOR monitorFromLuaSelectorOrObject(lua_State* L, int idx, const char* fnName);
PHLWORKSPACE workspaceFromLuaSelectorOrObject(lua_State* L, int idx, const char* fnName);
PHLWINDOW windowFromLuaSelectorOrObject(lua_State* L, int idx, const char* fnName);
std::optional<PHLMONITOR> tableOptMonitor(lua_State* L, int idx, const char* field, const char* fnName);
std::optional<PHLWORKSPACE> tableOptWorkspace(lua_State* L, int idx, const char* field, const char* fnName);
std::optional<PHLWINDOW> tableOptWindow(lua_State* L, int idx, const char* field, const char* fnName);
std::optional<std::string> monitorSelectorFromLuaSelectorOrObject(lua_State* L, int idx, const char* fnName);
std::optional<std::string> workspaceSelectorFromLuaSelectorOrObject(lua_State* L, int idx, const char* fnName);
std::optional<std::string> windowSelectorFromLuaSelectorOrObject(lua_State* L, int idx, const char* fnName);
std::optional<std::string> tableOptMonitorSelector(lua_State* L, int idx, const char* field, const char* fnName);
std::optional<std::string> tableOptWorkspaceSelector(lua_State* L, int idx, const char* field, const char* fnName);
std::optional<std::string> tableOptWindowSelector(lua_State* L, int idx, const char* field, const char* fnName);
std::string requireTableFieldMonitorSelector(lua_State* L, int idx, const char* field, const char* fnName);
std::string requireTableFieldWorkspaceSelector(lua_State* L, int idx, const char* field, const char* fnName);
std::string requireTableFieldWindowSelector(lua_State* L, int idx, const char* field, const char* fnName);
Math::eDirection parseDirectionStr(const std::string& str);
Config::Actions::eTogglableAction parseToggleStr(const std::string& str);
std::optional<PHLWINDOW> windowFromUpval(lua_State* L, int idx);
void pushWindowUpval(lua_State* L, int tableIdx);
void checkResult(lua_State* L, const Config::Actions::ActionResult& r);
PHLWORKSPACE resolveWorkspaceStr(const std::string& args);
PHLMONITOR resolveMonitorStr(const std::string& args);
std::string getSourceInfo(lua_State* L, int stackLevel = 1);
std::string requireTableFieldStr(lua_State* L, int idx, const char* field, const char* fnName);
double requireTableFieldNum(lua_State* L, int idx, const char* field, const char* fnName);
Config::Actions::eTogglableAction tableToggleAction(lua_State* L, int idx, const char* field = "action");
std::expected<std::string, std::string> ruleValueToString(lua_State* L);
std::expected<SP<Desktop::Rule::CWindowRule>, int> buildRuleFromTable(lua_State* L, int idx);
int configError(lua_State* L, std::string s, int stackLevel = 1);
template <typename... Args>
int configError(lua_State* L, std::format_string<Args...> fmt, Args&&... args) {
return configError(L, std::format(fmt, std::forward<Args>(args)...));
}
template <typename... Args>
int configError(lua_State* L, int stackLevel, std::format_string<Args...> fmt, Args&&... args) {
return configError(L, std::format(fmt, std::forward<Args>(args)...), stackLevel);
}
template <typename T, size_t N>
const T* findDescByName(const T (&descs)[N], std::string_view key) {
for (const auto& desc : descs) {
if (key == desc.name)
return &desc;
}
return nullptr;
}
void setFn(lua_State* L, const char* name, lua_CFunction fn);
void setMgrFn(lua_State* L, CConfigManager* mgr, const char* name, lua_CFunction fn);
template <typename T>
SParseError parseTableField(lua_State* L, int tableIdx, const char* field, T& parser) {
lua_getfield(L, tableIdx, field);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return {.errorCode = PARSE_ERROR_BAD_VALUE, .message = std::format("missing required field \"{}\"", field)};
}
auto err = parser.parse(L);
lua_pop(L, 1);
if (err.errorCode != PARSE_ERROR_OK)
err.message = std::format("field \"{}\": {}", field, err.message);
return err;
}
void registerToplevelBindings(lua_State* L, CConfigManager* mgr);
void registerQueryBindings(lua_State* L);
void registerNotificationBindings(lua_State* L);
void registerConfigRuleBindings(lua_State* L, CConfigManager* mgr);
void registerBindingsImpl(lua_State* L, CConfigManager* mgr);
void registerDispatcherBindings(lua_State* L);
}
@@ -0,0 +1,135 @@
#include "LuaBindingsInternal.hpp"
#include "../objects/LuaNotification.hpp"
#include "../../../helpers/MiscFunctions.hpp"
#include "../../../notification/NotificationOverlay.hpp"
#include <algorithm>
#include <array>
#include <cctype>
#include <optional>
#include <string>
using namespace Config;
using namespace Config::Lua;
using namespace Config::Lua::Bindings;
static std::optional<eIcons> iconFromString(std::string iconName) {
std::ranges::transform(iconName, iconName.begin(), [](const unsigned char c) { return std::tolower(c); });
static constexpr std::array<std::pair<const char*, eIcons>, 10> ICON_NAMES = {
std::pair{"warning", ICON_WARNING}, std::pair{"warn", ICON_WARNING}, std::pair{"info", ICON_INFO}, std::pair{"hint", ICON_HINT},
std::pair{"error", ICON_ERROR}, std::pair{"err", ICON_ERROR}, std::pair{"confused", ICON_CONFUSED}, std::pair{"question", ICON_CONFUSED},
std::pair{"ok", ICON_OK}, std::pair{"none", ICON_NONE},
};
for (const auto& [name, icon] : ICON_NAMES) {
if (name == iconName)
return icon;
}
return std::nullopt;
}
static std::optional<eIcons> parseIconArg(lua_State* L, int idx) {
if (lua_isnumber(L, idx)) {
const auto raw = sc<int>(lua_tonumber(L, idx));
if (raw >= ICON_WARNING && raw <= ICON_NONE)
return sc<eIcons>(raw);
return std::nullopt;
}
if (lua_isstring(L, idx))
return iconFromString(lua_tostring(L, idx));
return std::nullopt;
}
static std::optional<CHyprColor> parseColorArg(lua_State* L, int idx) {
if (lua_isnumber(L, idx))
return CHyprColor(sc<uint64_t>(lua_tonumber(L, idx)));
if (lua_isstring(L, idx)) {
auto parsed = configStringToInt(lua_tostring(L, idx));
if (!parsed)
return std::nullopt;
return CHyprColor(sc<uint64_t>(*parsed));
}
return std::nullopt;
}
static int hlNotificationCreate(lua_State* L) {
if (!lua_istable(L, 1))
return Internal::configError(L, "hl.notification.create: expected a table { text, duration, icon?, color?, font_size? }");
const auto text = Internal::requireTableFieldStr(L, 1, "text", "hl.notification.create");
std::optional<double> duration = Internal::tableOptNum(L, 1, "duration");
if (!duration)
duration = Internal::tableOptNum(L, 1, "timeout");
if (!duration)
duration = Internal::tableOptNum(L, 1, "time");
if (!duration)
return Internal::configError(L, "hl.notification.create: 'duration' (or 'timeout' / 'time') is required");
if (*duration < 0)
return Internal::configError(L, "hl.notification.create: duration must be >= 0");
eIcons icon = ICON_NONE;
lua_getfield(L, 1, "icon");
if (!lua_isnil(L, -1)) {
const auto parsedIcon = parseIconArg(L, -1);
if (!parsedIcon)
return Internal::configError(L, "hl.notification.create: invalid 'icon' (expected icon name or number)");
icon = *parsedIcon;
}
lua_pop(L, 1);
CHyprColor color = CHyprColor(0);
lua_getfield(L, 1, "color");
if (!lua_isnil(L, -1)) {
const auto parsedColor = parseColorArg(L, -1);
if (!parsedColor)
return Internal::configError(L, "hl.notification.create: invalid 'color' (expected color string or number)");
color = *parsedColor;
}
lua_pop(L, 1);
float fontSize = 13.F;
if (const auto parsedFontSize = Internal::tableOptNum(L, 1, "font_size"); parsedFontSize.has_value()) {
if (*parsedFontSize <= 0)
return Internal::configError(L, "hl.notification.create: 'font_size' must be > 0");
fontSize = sc<float>(*parsedFontSize);
}
auto notification = Notification::overlay()->addNotification(text, color, sc<float>(*duration), icon, fontSize);
Objects::CLuaNotification::push(L, notification);
return 1;
}
static int hlGetNotifications(lua_State* L) {
lua_newtable(L);
const auto notifications = Notification::overlay()->getNotifications();
int i = 1;
for (const auto& notification : notifications) {
Objects::CLuaNotification::push(L, notification);
lua_rawseti(L, -2, i++);
}
return 1;
}
void Internal::registerNotificationBindings(lua_State* L) {
lua_newtable(L);
Internal::setFn(L, "create", hlNotificationCreate);
Internal::setFn(L, "get", hlGetNotifications);
lua_setfield(L, -2, "notification");
}
@@ -0,0 +1,400 @@
#include "LuaBindingsInternal.hpp"
#include "../objects/LuaLayerSurface.hpp"
#include "../objects/LuaMonitor.hpp"
#include "../objects/LuaWindow.hpp"
#include "../objects/LuaWorkspace.hpp"
#include "../../../desktop/history/WindowHistoryTracker.hpp"
#include "../../../desktop/history/WorkspaceHistoryTracker.hpp"
#include "../../../desktop/rule/windowRule/WindowRuleEffectContainer.hpp"
#include "../../../desktop/view/LayerSurface.hpp"
#include "../../../desktop/view/Window.hpp"
#include "../../../managers/input/InputManager.hpp"
using namespace Config;
using namespace Config::Lua;
using namespace Config::Lua::Bindings;
namespace {
struct SWindowQuery {
std::optional<PHLMONITOR> monitor;
std::optional<PHLWORKSPACE> workspace;
std::optional<bool> floating;
std::optional<bool> mapped;
std::optional<std::string> className;
std::optional<std::string> title;
std::optional<std::string> tag;
};
struct SLayerQuery {
std::optional<PHLMONITOR> monitor;
std::optional<std::string> namespace_;
};
}
static bool windowMatchesQuery(const PHLWINDOW& w, const SWindowQuery& query) {
if (query.monitor) {
const auto mon = w->m_monitor.lock();
if (mon != *query.monitor)
return false;
}
if (query.workspace && w->m_workspace != *query.workspace)
return false;
if (query.floating && w->m_isFloating != *query.floating)
return false;
if (query.mapped && w->m_isMapped != *query.mapped)
return false;
if (query.className && w->m_class != *query.className)
return false;
if (query.title && w->m_title != *query.title)
return false;
if (query.tag) {
if (!w->m_ruleApplicator || !w->m_ruleApplicator->m_tagKeeper.isTagged(*query.tag, true))
return false;
}
return true;
}
static void pushWindowsMatchingQuery(lua_State* L, const SWindowQuery& query) {
lua_newtable(L);
int i = 1;
for (const auto& w : g_pCompositor->m_windows) {
if (!windowMatchesQuery(w, query))
continue;
Objects::CLuaWindow::push(L, w);
lua_rawseti(L, -2, i++);
}
}
static void parseWindowQueryFromTable(lua_State* L, int idx, const char* fnName, SWindowQuery& query) {
query.monitor = Internal::tableOptMonitor(L, idx, "monitor", fnName);
query.workspace = Internal::tableOptWorkspace(L, idx, "workspace", fnName);
if (const auto floating = Internal::tableOptBool(L, idx, "floating"); floating.has_value())
query.floating = floating;
if (const auto mapped = Internal::tableOptBool(L, idx, "mapped"); mapped.has_value())
query.mapped = mapped;
query.className = Internal::tableOptStr(L, idx, "class");
query.title = Internal::tableOptStr(L, idx, "title");
query.tag = Internal::tableOptStr(L, idx, "tag");
}
static void parseLayerQueryFromTable(lua_State* L, int idx, const char* fnName, SLayerQuery& query) {
query.monitor = Internal::tableOptMonitor(L, idx, "monitor", fnName);
query.namespace_ = Internal::tableOptStr(L, idx, "namespace");
}
static PHLMONITOR monitorFromOptionalArg(lua_State* L, int idx, const char* fnName) {
if (lua_gettop(L) < idx || lua_isnil(L, idx))
return Desktop::focusState()->monitor();
return Internal::monitorFromLuaSelectorOrObject(L, idx, fnName);
}
static int hlGetWindows(lua_State* L) {
SWindowQuery query;
query.mapped = true;
if (lua_gettop(L) >= 1 && !lua_isnil(L, 1)) {
if (!lua_istable(L, 1))
return Internal::configError(L, "hl.get_windows: expected no args or a table of filters");
parseWindowQueryFromTable(L, 1, "hl.get_windows", query);
}
pushWindowsMatchingQuery(L, query);
return 1;
}
static int hlGetActiveWindow(lua_State* L) {
auto PWINDOW = Desktop::focusState()->window();
if (!PWINDOW) {
lua_pushnil(L);
return 1;
}
Config::Lua::Objects::CLuaWindow::push(L, PWINDOW);
return 1;
}
static int hlGetWindow(lua_State* L) {
const auto PWINDOW = Internal::windowFromLuaSelectorOrObject(L, 1, "hl.get_window");
if (!PWINDOW) {
lua_pushnil(L);
return 1;
}
Objects::CLuaWindow::push(L, PWINDOW);
return 1;
}
static int hlGetUrgentWindow(lua_State* L) {
const auto PWINDOW = g_pCompositor->getUrgentWindow();
if (!PWINDOW) {
lua_pushnil(L);
return 1;
}
Objects::CLuaWindow::push(L, PWINDOW);
return 1;
}
static int hlGetWorkspaces(lua_State* L) {
lua_newtable(L);
int i = 1;
for (const auto& wsRef : g_pCompositor->getWorkspaces()) {
const auto ws = wsRef.lock();
if (!ws || ws->inert())
continue;
Objects::CLuaWorkspace::push(L, ws);
lua_rawseti(L, -2, i++);
}
return 1;
}
static int hlGetWorkspace(lua_State* L) {
const auto PWORKSPACE = Internal::workspaceFromLuaSelectorOrObject(L, 1, "hl.get_workspace");
if (!PWORKSPACE) {
lua_pushnil(L);
return 1;
}
Objects::CLuaWorkspace::push(L, PWORKSPACE);
return 1;
}
static int hlGetActiveWorkspace(lua_State* L) {
const auto PMONITOR = monitorFromOptionalArg(L, 1, "hl.get_active_workspace");
if (!PMONITOR || !PMONITOR->m_activeWorkspace) {
lua_pushnil(L);
return 1;
}
Objects::CLuaWorkspace::push(L, PMONITOR->m_activeWorkspace);
return 1;
}
static int hlGetActiveSpecialWorkspace(lua_State* L) {
const auto PMONITOR = monitorFromOptionalArg(L, 1, "hl.get_active_special_workspace");
if (!PMONITOR || !PMONITOR->m_activeSpecialWorkspace) {
lua_pushnil(L);
return 1;
}
Objects::CLuaWorkspace::push(L, PMONITOR->m_activeSpecialWorkspace);
return 1;
}
static int hlGetMonitors(lua_State* L) {
lua_newtable(L);
int i = 1;
for (const auto& mon : g_pCompositor->m_monitors) {
Objects::CLuaMonitor::push(L, mon);
lua_rawseti(L, -2, i++);
}
return 1;
}
static int hlGetMonitor(lua_State* L) {
const auto PMONITOR = Internal::monitorFromLuaSelectorOrObject(L, 1, "hl.get_monitor");
if (!PMONITOR) {
lua_pushnil(L);
return 1;
}
Objects::CLuaMonitor::push(L, PMONITOR);
return 1;
}
static int hlGetActiveMonitor(lua_State* L) {
const auto PMONITOR = Desktop::focusState()->monitor();
if (!PMONITOR) {
lua_pushnil(L);
return 1;
}
Objects::CLuaMonitor::push(L, PMONITOR);
return 1;
}
static int hlGetMonitorAt(lua_State* L) {
double x = 0;
double y = 0;
if (lua_istable(L, 1)) {
const auto tx = Internal::tableOptNum(L, 1, "x");
const auto ty = Internal::tableOptNum(L, 1, "y");
if (!tx || !ty)
return Internal::configError(L, "hl.get_monitor_at: expected a table { x, y }");
x = *tx;
y = *ty;
} else {
x = luaL_checknumber(L, 1);
y = luaL_checknumber(L, 2);
}
const auto PMONITOR = g_pCompositor->getMonitorFromVector(Vector2D{x, y});
if (!PMONITOR) {
lua_pushnil(L);
return 1;
}
Objects::CLuaMonitor::push(L, PMONITOR);
return 1;
}
static int hlGetMonitorAtCursor(lua_State* L) {
const auto PMONITOR = g_pCompositor->getMonitorFromCursor();
if (!PMONITOR) {
lua_pushnil(L);
return 1;
}
Objects::CLuaMonitor::push(L, PMONITOR);
return 1;
}
static int hlGetCursorPos(lua_State* L) {
if (!g_pInputManager) {
lua_pushnil(L);
return 1;
}
const auto pos = g_pInputManager->getMouseCoordsInternal();
lua_newtable(L);
lua_pushnumber(L, pos.x);
lua_setfield(L, -2, "x");
lua_pushnumber(L, pos.y);
lua_setfield(L, -2, "y");
return 1;
}
static int hlGetLastWindow(lua_State* L) {
const auto current = Desktop::focusState()->window();
const auto& fullHistory = Desktop::History::windowTracker()->fullHistory();
for (auto it = fullHistory.rbegin(); it != fullHistory.rend(); ++it) {
const auto candidate = it->lock();
if (!candidate || !candidate->m_isMapped)
continue;
if (current && candidate == current)
continue;
Objects::CLuaWindow::push(L, candidate);
return 1;
}
lua_pushnil(L);
return 1;
}
static int hlGetLastWorkspace(lua_State* L) {
const bool hadMonitorArg = lua_gettop(L) >= 1 && !lua_isnil(L, 1);
const auto PMONITOR = hadMonitorArg ? Internal::monitorFromLuaSelectorOrObject(L, 1, "hl.get_last_workspace") : Desktop::focusState()->monitor();
if (!PMONITOR || !PMONITOR->m_activeWorkspace) {
lua_pushnil(L);
return 1;
}
const auto current = PMONITOR->m_activeWorkspace;
auto previous = hadMonitorArg ? Desktop::History::workspaceTracker()->previousWorkspace(current, PMONITOR) : Desktop::History::workspaceTracker()->previousWorkspace(current);
auto ws = previous.workspace.lock();
if ((!ws || ws->inert()) && previous.id != WORKSPACE_INVALID)
ws = g_pCompositor->getWorkspaceByID(previous.id);
if (!ws || ws->inert()) {
lua_pushnil(L);
return 1;
}
Objects::CLuaWorkspace::push(L, ws);
return 1;
}
static int hlGetLayers(lua_State* L) {
SLayerQuery query;
if (lua_gettop(L) >= 1 && !lua_isnil(L, 1)) {
if (!lua_istable(L, 1))
return Internal::configError(L, "hl.get_layers: expected no args or a table of filters");
parseLayerQueryFromTable(L, 1, "hl.get_layers", query);
}
lua_newtable(L);
int i = 1;
for (const auto& mon : g_pCompositor->m_monitors) {
if (query.monitor && mon != *query.monitor)
continue;
for (const auto& level : mon->m_layerSurfaceLayers) {
for (const auto& lsRef : level) {
const auto ls = lsRef.lock();
if (!ls)
continue;
if (query.namespace_ && ls->m_namespace != *query.namespace_)
continue;
Objects::CLuaLayerSurface::push(L, ls);
lua_rawseti(L, -2, i++);
}
}
}
return 1;
}
static int hlGetWorkspaceWindows(lua_State* L) {
const auto ws = Internal::workspaceFromLuaSelectorOrObject(L, 1, "hl.get_workspace_windows");
SWindowQuery query;
query.workspace = ws;
query.mapped = true;
pushWindowsMatchingQuery(L, query);
return 1;
}
static int hlGetCurrentSubmap(lua_State* L) {
lua_pushstring(L, Config::Actions::state()->m_currentSubmap.c_str());
return 1;
}
void Internal::registerQueryBindings(lua_State* L) {
Internal::setFn(L, "get_windows", hlGetWindows);
Internal::setFn(L, "get_window", hlGetWindow);
Internal::setFn(L, "get_active_window", hlGetActiveWindow);
Internal::setFn(L, "get_urgent_window", hlGetUrgentWindow);
Internal::setFn(L, "get_workspaces", hlGetWorkspaces);
Internal::setFn(L, "get_workspace", hlGetWorkspace);
Internal::setFn(L, "get_active_workspace", hlGetActiveWorkspace);
Internal::setFn(L, "get_active_special_workspace", hlGetActiveSpecialWorkspace);
Internal::setFn(L, "get_monitors", hlGetMonitors);
Internal::setFn(L, "get_monitor", hlGetMonitor);
Internal::setFn(L, "get_active_monitor", hlGetActiveMonitor);
Internal::setFn(L, "get_monitor_at", hlGetMonitorAt);
Internal::setFn(L, "get_monitor_at_cursor", hlGetMonitorAtCursor);
Internal::setFn(L, "get_layers", hlGetLayers);
Internal::setFn(L, "get_workspace_windows", hlGetWorkspaceWindows);
Internal::setFn(L, "get_cursor_pos", hlGetCursorPos);
Internal::setFn(L, "get_last_window", hlGetLastWindow);
Internal::setFn(L, "get_last_workspace", hlGetLastWorkspace);
Internal::setFn(L, "get_current_submap", hlGetCurrentSubmap);
}
@@ -0,0 +1,66 @@
#include "LuaBindingsInternal.hpp"
#include "../objects/LuaEventSubscription.hpp"
#include "../objects/LuaKeybind.hpp"
#include "../objects/LuaLayerRule.hpp"
#include "../objects/LuaNotification.hpp"
#include "../objects/LuaTimer.hpp"
#include "../objects/LuaWindowRule.hpp"
using namespace Config;
using namespace Config::Lua;
using namespace Config::Lua::Bindings;
static int hlPrint(lua_State* L) {
const int n = lua_gettop(L);
std::string out;
for (int i = 1; i <= n; i++) {
size_t len = 0;
const char* s = luaL_tolstring(L, i, &len);
if (i > 1)
out += '\t';
out.append(s, len);
lua_pop(L, 1);
}
Log::logger->log(Log::INFO, "[Lua] {}", out);
return 0;
}
void Internal::registerBindingsImpl(lua_State* L, CConfigManager* mgr) {
Objects::CLuaTimer{}.setup(L);
Objects::CLuaEventSubscription{}.setup(L);
Objects::CLuaWindowRule{}.setup(L);
Objects::CLuaLayerRule{}.setup(L);
Objects::CLuaKeybind{}.setup(L);
Objects::CLuaNotification{}.setup(L);
g_pKeybindManager->m_dispatchers["__lua"] = [L](std::string arg) -> SDispatchResult {
int ref = std::stoi(arg);
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
int status = LUA_OK;
if (auto* mgr = CConfigManager::fromLuaState(L); mgr)
status = mgr->guardedPCall(0, 0, 0, CConfigManager::LUA_TIMEOUT_KEYBIND_CALLBACK_MS, "keybind callback");
else
status = lua_pcall(L, 0, 0, 0);
if (status != LUA_OK) {
Config::Lua::mgr()->addError(std::format("error in keybind lambda: {}", lua_tostring(L, -1)));
lua_pop(L, 1);
}
return {};
};
lua_newtable(L);
Internal::registerConfigRuleBindings(L, mgr);
Internal::registerToplevelBindings(L, mgr);
Internal::registerQueryBindings(L);
Internal::registerDispatcherBindings(L);
Internal::registerNotificationBindings(L);
lua_setglobal(L, "hl");
lua_pushcfunction(L, hlPrint);
lua_setglobal(L, "print");
}
@@ -0,0 +1,435 @@
#include "LuaBindingsInternal.hpp"
#include "../objects/LuaEventSubscription.hpp"
#include "../objects/LuaKeybind.hpp"
#include "../objects/LuaTimer.hpp"
#include "../../supplementary/executor/Executor.hpp"
#include "../../../devices/IKeyboard.hpp"
#include "../../../managers/eventLoop/EventLoopManager.hpp"
#include <hyprutils/string/String.hpp>
#include <hyprutils/string/VarList.hpp>
using namespace Config;
using namespace Config::Lua;
using namespace Config::Lua::Bindings;
using namespace Hyprutils::String;
static std::optional<eKeyboardModifiers> modFromSv(std::string_view sv) {
if (sv == "SHIFT")
return HL_MODIFIER_SHIFT;
if (sv == "CAPS")
return HL_MODIFIER_CAPS;
if (sv == "CTRL" || sv == "CONTROL")
return HL_MODIFIER_CTRL;
if (sv == "ALT" || sv == "MOD1")
return HL_MODIFIER_ALT;
if (sv == "MOD2")
return HL_MODIFIER_MOD2;
if (sv == "MOD3")
return HL_MODIFIER_MOD3;
if (sv == "SUPER" || sv == "WIN" || sv == "LOGO" || sv == "MOD4" || sv == "META")
return HL_MODIFIER_META;
if (sv == "MOD5")
return HL_MODIFIER_MOD5;
return std::nullopt;
}
static bool isSymSpecial(std::string_view sv) {
if (sv == "mouse_down" || sv == "mouse_up" || sv == "mouse_left" || sv == "mouse_right")
return true;
return sv.starts_with("switch:") || sv.starts_with("mouse:");
}
static std::expected<void, std::string> parseKeyString(SKeybind& kb, std::string_view sv) {
bool modsEnded = false, specialSym = false;
CVarList2 vl(sv, 0, '+', true);
uint32_t modMask = 0;
std::vector<xkb_keysym_t> keysyms;
std::string lastKeyArg;
if (sv == "catchall") {
kb.catchAll = true;
return {};
}
for (const auto& a : vl) {
auto arg = Hyprutils::String::trim(a);
auto mask = modFromSv(arg);
if (!mask)
modsEnded = true;
if (modsEnded && mask)
return std::unexpected("Modifiers must come first in the list");
if (mask) {
modMask |= *mask;
continue;
}
if (specialSym)
return std::unexpected("Cannot combine special syms (e.g. mouse_down + Q)");
if (isSymSpecial(arg)) {
if (!keysyms.empty())
return std::unexpected("Cannot combine special syms (e.g. mouse_down + Q)");
specialSym = true;
kb.key = arg;
continue;
}
auto sym = xkb_keysym_from_name(std::string{arg}.c_str(), XKB_KEYSYM_CASE_INSENSITIVE);
if (sym == XKB_KEY_NoSymbol) {
if (arg.contains(' '))
return std::unexpected(std::format("Unknown keysym: \"{}\", did you forget a +?", arg));
if (arg == "Enter")
return std::unexpected(std::format(R"(Unknown keysym: "{}", did you mean "Return"?)", arg));
return std::unexpected(std::format("Unknown keysym: \"{}\"", arg));
}
lastKeyArg = arg;
keysyms.emplace_back(sym);
}
kb.modmask = modMask;
kb.sMkKeys = std::move(keysyms);
if (!specialSym && !lastKeyArg.empty())
kb.key = lastKeyArg;
return {};
}
static int hlBind(lua_State* L) {
auto* mgr = sc<CConfigManager*>(lua_touserdata(L, lua_upvalueindex(1)));
std::string_view keys = luaL_checkstring(L, 1);
SKeybind kb;
kb.submap.name = mgr->m_currentSubmap;
kb.submap.reset = mgr->m_currentSubmapReset;
if (auto res = parseKeyString(kb, keys); !res)
return Internal::configError(L, std::format("hl.bind: failed to parse key string: {}", res.error()));
if (!lua_isfunction(L, 2))
return Internal::configError(L, "hl.bind: dispatcher must be a dispatcher (e.g. hl.dsp.window.close()) or a lua function");
if (kb.catchAll && mgr->m_currentSubmap.empty())
return Internal::configError(L, "hl.bind: catchall keybinds are only allowed in submaps.");
lua_pushvalue(L, 2);
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
kb.handler = "__lua";
kb.arg = std::to_string(ref);
kb.displayKey = keys;
int optsIdx = 3;
if (lua_istable(L, optsIdx)) {
auto getBool = [&](const char* field) -> bool {
lua_getfield(L, optsIdx, field);
const bool v = lua_toboolean(L, -1);
lua_pop(L, 1);
return v;
};
auto readOptString = [&](const char* field) -> std::optional<std::string> {
lua_getfield(L, optsIdx, field);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return std::nullopt;
}
if (!lua_isstring(L, -1)) {
lua_pop(L, 1);
Internal::configError(L, "hl.bind: opts.{} must be a string", field);
return std::nullopt;
}
std::string result = lua_tostring(L, -1);
lua_pop(L, 1);
return result;
};
kb.repeat = getBool("repeating");
kb.locked = getBool("locked");
kb.release = getBool("release");
kb.nonConsuming = getBool("non_consuming");
kb.transparent = getBool("transparent");
kb.ignoreMods = getBool("ignore_mods");
kb.dontInhibit = getBool("dont_inhibit");
kb.longPress = getBool("long_press");
kb.submapUniversal = getBool("submap_universal");
if (auto description = readOptString("description"); description.has_value()) {
kb.description = *description;
kb.hasDescription = true;
} else if (auto desc = readOptString("desc"); desc.has_value()) {
kb.description = *desc;
kb.hasDescription = true;
}
bool click = false;
bool drag = false;
if (getBool("click")) {
click = true;
kb.release = true;
}
if (getBool("drag")) {
drag = true;
kb.release = true;
}
if (click && drag)
return Internal::configError(L, "hl.bind: click and drag are exclusive");
if ((kb.longPress || kb.release) && kb.repeat)
return Internal::configError(L, "hl.bind: long_press / release is incompatible with repeat");
if (kb.mouse && (kb.repeat || kb.release || kb.locked))
return Internal::configError(L, "hl.bind: mouse is exclusive");
kb.click = click;
kb.drag = drag;
lua_getfield(L, optsIdx, "device");
if (lua_istable(L, -1)) {
lua_getfield(L, -1, "inclusive");
kb.deviceInclusive = lua_isnil(L, -1) ? true : lua_toboolean(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "list");
if (lua_istable(L, -1)) {
lua_pushnil(L);
while (lua_next(L, -2)) {
if (lua_isstring(L, -1))
kb.devices.emplace(lua_tostring(L, -1));
lua_pop(L, 1);
}
}
lua_pop(L, 1);
}
lua_pop(L, 1);
}
const auto BIND = g_pKeybindManager->addKeybind(kb);
Objects::CLuaKeybind::push(L, BIND);
return 1;
}
static int hlDefineSubmap(lua_State* L) {
auto* mgr = sc<CConfigManager*>(lua_touserdata(L, lua_upvalueindex(1)));
const char* name = luaL_checkstring(L, 1);
std::string reset;
int fnIdx = 2;
if (lua_gettop(L) >= 3 && lua_isstring(L, 2)) {
reset = lua_tostring(L, 2);
fnIdx = 3;
}
luaL_checktype(L, fnIdx, LUA_TFUNCTION);
std::string prev = mgr->m_currentSubmap;
std::string prevReset = mgr->m_currentSubmapReset;
mgr->m_currentSubmap = name;
mgr->m_currentSubmapReset = reset;
lua_pushvalue(L, fnIdx);
if (mgr->guardedPCall(0, 0, 0, CConfigManager::LUA_TIMEOUT_DISPATCH_MS, std::format("hl.define_submap(\"{}\")", name)) != LUA_OK) {
mgr->addError(std::format("hl.define_submap: error in submap \"{}\": {}", name, lua_tostring(L, -1)));
lua_pop(L, 1);
}
mgr->m_currentSubmap = prev;
mgr->m_currentSubmapReset = prevReset;
return 0;
}
static int hlVersion(lua_State* L) {
lua_pushstring(L, HYPRLAND_VERSION);
return 1;
}
static int hlExecCmd(lua_State* L) {
auto cmd = Internal::argStr(L, 1);
if (cmd.empty())
return Internal::configError(L, "hl.exec_cmd: expected command as first argument");
auto rule = Internal::buildRuleFromTable(L, 2);
if (!rule)
return rule.error();
Config::Supplementary::executor()->spawn(Supplementary::SExecRequest{cmd, !*rule, *rule});
return 0;
}
static int hlDispatch(lua_State* L) {
if (!lua_isfunction(L, 1))
return Internal::configError(L, "hl.dispatch: expected a dispatcher function (e.g. hl.dsp.window.close())");
lua_pushvalue(L, 1);
int status = LUA_OK;
if (auto* mgr = CConfigManager::fromLuaState(L); mgr)
status = mgr->guardedPCall(0, 0, 0, CConfigManager::LUA_TIMEOUT_DISPATCH_MS, "hl.dispatch");
else
status = lua_pcall(L, 0, 0, 0);
if (status != LUA_OK) {
const char* err = lua_tostring(L, -1);
lua_pop(L, 1);
return Internal::configError(L, "hl.dispatch: {}", err ? err : "unknown error");
}
return 0;
}
static int hlOn(lua_State* L) {
auto* mgr = sc<CConfigManager*>(lua_touserdata(L, lua_upvalueindex(1)));
const char* eventName = luaL_checkstring(L, 1);
luaL_checktype(L, 2, LUA_TFUNCTION);
lua_pushvalue(L, 2);
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
const auto handle = mgr->m_eventHandler->registerEvent(eventName, ref);
if (!handle.has_value()) {
luaL_unref(L, LUA_REGISTRYINDEX, ref);
const auto& known = CLuaEventHandler::knownEvents();
std::string list;
for (const auto& e : known) {
list += e + ", ";
}
list.pop_back();
list.pop_back();
return Internal::configError(L, "hl.on: unknown event \"{}\". Known events:{}", eventName, list);
}
Objects::CLuaEventSubscription::push(L, mgr->m_eventHandler.get(), *handle);
return 1;
}
static int hlUnbind(lua_State* L) {
if (lua_isstring(L, 1) && std::string_view(lua_tostring(L, 1)) == "all" && lua_gettop(L) == 1) {
g_pKeybindManager->clearKeybinds();
return 0;
}
const char* mods = luaL_checkstring(L, 1);
const char* keyStr = luaL_checkstring(L, 2);
uint32_t mod = g_pKeybindManager->stringToModMask(mods);
SParsedKey key;
std::string k = keyStr;
if (Hyprutils::String::isNumber(k) && std::stoi(k) > 9)
key = {.keycode = (uint32_t)std::stoi(k)};
else if (k.starts_with("code:") && Hyprutils::String::isNumber(k.substr(5)))
key = {.keycode = (uint32_t)std::stoi(k.substr(5))};
else if (k == "catchall")
key = {.catchAll = true};
else
key = {.key = k};
g_pKeybindManager->removeKeybind(mod, key);
return 0;
}
static int hlTimer(lua_State* L) {
auto* mgr = sc<CConfigManager*>(lua_touserdata(L, lua_upvalueindex(1)));
luaL_checktype(L, 1, LUA_TFUNCTION);
luaL_checktype(L, 2, LUA_TTABLE);
lua_getfield(L, 2, "timeout");
if (!lua_isnumber(L, -1))
return Internal::configError(L, "hl.timer: opts.timeout must be a number (ms)");
int timeoutMs = (int)lua_tonumber(L, -1);
lua_pop(L, 1);
if (timeoutMs <= 0)
return Internal::configError(L, "hl.timer: opts.timeout must be > 0");
lua_getfield(L, 2, "type");
if (!lua_isstring(L, -1))
return Internal::configError(L, "hl.timer: opts.type must be \"repeat\" or \"oneshot\"");
std::string type = lua_tostring(L, -1);
lua_pop(L, 1);
bool repeat = false;
if (type == "repeat")
repeat = true;
else if (type != "oneshot")
return Internal::configError(L, "hl.timer: opts.type must be \"repeat\" or \"oneshot\"");
lua_pushvalue(L, 1);
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
auto timer = makeShared<CEventLoopTimer>(
std::chrono::milliseconds(timeoutMs),
[L, ref, repeat, timeoutMs, mgr](SP<CEventLoopTimer> self, void* data) {
// update repeat already so that if we call set_timeout inside
// our timer it doesn't get overwritten
if (repeat)
self->updateTimeout(std::chrono::milliseconds(timeoutMs));
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
int status = LUA_OK;
if (mgr)
status = mgr->guardedPCall(0, 0, 0, CConfigManager::LUA_TIMEOUT_TIMER_CALLBACK_MS, "hl.timer callback");
else
status = lua_pcall(L, 0, 0, 0);
if (status != LUA_OK) {
Log::logger->log(Log::ERR, "[Lua] error in timer callback: {}", lua_tostring(L, -1));
lua_pop(L, 1);
}
if (!repeat) {
const auto HAS = std::ranges::find_if(mgr->m_luaTimers, [&self](const auto& lt) { return lt.timer == self; }) != mgr->m_luaTimers.end();
// avoid double-unref if this timer triggered a reload
if (HAS) {
luaL_unref(L, LUA_REGISTRYINDEX, ref);
std::erase_if(mgr->m_luaTimers, [&self](const auto& lt) { return lt.timer == self; });
}
}
},
nullptr);
mgr->m_luaTimers.emplace_back(CConfigManager::SLuaTimer{timer, ref});
if (g_pEventLoopManager)
g_pEventLoopManager->addTimer(timer);
Objects::CLuaTimer::push(L, timer, timeoutMs);
return 1;
}
void Internal::registerToplevelBindings(lua_State* L, CConfigManager* mgr) {
Internal::setMgrFn(L, mgr, "on", hlOn);
Internal::setMgrFn(L, mgr, "bind", hlBind);
Internal::setMgrFn(L, mgr, "define_submap", hlDefineSubmap);
Internal::setMgrFn(L, mgr, "timer", hlTimer);
Internal::setFn(L, "dispatch", hlDispatch);
Internal::setFn(L, "version", hlVersion);
Internal::setFn(L, "exec_cmd", hlExecCmd);
Internal::setFn(L, "unbind", hlUnbind);
}
@@ -0,0 +1,74 @@
#include "LuaEventSubscription.hpp"
#include <format>
#include <string_view>
using namespace Config::Lua;
static constexpr const char* MT = "HL.EventSubscription";
namespace {
struct SEventSubscriptionRef {
CLuaEventHandler* handler = nullptr;
uint64_t handle = 0;
bool active = false;
};
}
static int eventSubscriptionEq(lua_State* L) {
const auto* lhs = sc<SEventSubscriptionRef*>(luaL_checkudata(L, 1, MT));
const auto* rhs = sc<SEventSubscriptionRef*>(luaL_checkudata(L, 2, MT));
lua_pushboolean(L, lhs->handler == rhs->handler && lhs->handle == rhs->handle);
return 1;
}
static int eventSubscriptionToString(lua_State* L) {
const auto* ref = sc<SEventSubscriptionRef*>(luaL_checkudata(L, 1, MT));
const auto str = std::format("HL.EventSubscription({},{})", ref->handle, ref->active ? "active" : "inactive");
lua_pushstring(L, str.c_str());
return 1;
}
static int eventSubscriptionRemove(lua_State* L) {
auto* ref = sc<SEventSubscriptionRef*>(luaL_checkudata(L, 1, MT));
if (!ref->active || !ref->handler)
return 0;
ref->handler->unregisterEvent(ref->handle);
ref->active = false;
return 0;
}
static int eventSubscriptionIsActive(lua_State* L) {
auto* ref = sc<SEventSubscriptionRef*>(luaL_checkudata(L, 1, MT));
lua_pushboolean(L, ref->active);
return 1;
}
static int eventSubscriptionIndex(lua_State* L) {
luaL_checkudata(L, 1, MT);
const std::string_view key = luaL_checkstring(L, 2);
if (key == "remove")
lua_pushcfunction(L, eventSubscriptionRemove);
else if (key == "is_active")
lua_pushcfunction(L, eventSubscriptionIsActive);
else
lua_pushnil(L);
return 1;
}
void Objects::CLuaEventSubscription::setup(lua_State* L) {
registerMetatable(L, MT, eventSubscriptionIndex, gcRef<SEventSubscriptionRef>, eventSubscriptionEq, eventSubscriptionToString);
}
void Objects::CLuaEventSubscription::push(lua_State* L, CLuaEventHandler* handler, uint64_t handle) {
new (lua_newuserdata(L, sizeof(SEventSubscriptionRef))) SEventSubscriptionRef{.handler = handler, .handle = handle, .active = true};
luaL_getmetatable(L, MT);
lua_setmetatable(L, -2);
}
@@ -0,0 +1,12 @@
#pragma once
#include "LuaObjectHelpers.hpp"
#include "../LuaEventHandler.hpp"
namespace Config::Lua::Objects {
class CLuaEventSubscription : public ILuaObjectWrapper {
public:
void setup(lua_State* L) override;
static void push(lua_State* L, CLuaEventHandler* handler, uint64_t handle);
};
}
+180
View File
@@ -0,0 +1,180 @@
#include "LuaKeybind.hpp"
#include <optional>
#include <string_view>
using namespace Config::Lua;
static constexpr const char* MT = "HL.Keybind";
namespace {
std::optional<SP<SKeybind>> getKeybindFromUserdata(lua_State* L) {
auto* ref = sc<WP<SKeybind>*>(luaL_checkudata(L, 1, MT));
return ref->lock();
}
void pushDeviceList(lua_State* L, const SKeybind& keybind) {
lua_newtable(L);
int i = 1;
for (const auto& device : keybind.devices) {
lua_pushstring(L, device.c_str());
lua_rawseti(L, -2, i++);
}
}
}
static int keybindEq(lua_State* L) {
const auto* lhs = sc<WP<SKeybind>*>(luaL_checkudata(L, 1, MT));
const auto* rhs = sc<WP<SKeybind>*>(luaL_checkudata(L, 2, MT));
lua_pushboolean(L, lhs->lock() == rhs->lock());
return 1;
}
static int keybindToString(lua_State* L) {
const auto* ref = sc<WP<SKeybind>*>(luaL_checkudata(L, 1, MT));
const auto keybind = ref->lock();
if (!keybind)
lua_pushstring(L, "HL.Keybind(expired)");
else
lua_pushfstring(L, "HL.Keybind(%p)", keybind.get());
return 1;
}
static int keybindSetEnabled(lua_State* L) {
luaL_checktype(L, 2, LUA_TBOOLEAN);
const auto keybind = getKeybindFromUserdata(L);
if (!keybind)
return 0;
(*keybind)->enabled = lua_toboolean(L, 2);
return 0;
}
static int keybindIsEnabled(lua_State* L) {
const auto keybind = getKeybindFromUserdata(L);
if (!keybind) {
lua_pushnil(L);
return 1;
}
lua_pushboolean(L, (*keybind)->enabled);
return 1;
}
static int keybindRemove(lua_State* L) {
const auto keybind = getKeybindFromUserdata(L);
if (!keybind || !g_pKeybindManager)
return 0;
if ((*keybind)->handler == "__lua") {
try {
const int ref = std::stoi((*keybind)->arg);
if (ref > 0)
luaL_unref(L, LUA_REGISTRYINDEX, ref);
} catch (...) {
// invalid ref, ignore
}
(*keybind)->arg = std::to_string(LUA_NOREF);
}
g_pKeybindManager->removeKeybind((*keybind)->modmask, SParsedKey{.key = (*keybind)->key, .keycode = (*keybind)->keycode, .catchAll = (*keybind)->catchAll});
return 0;
}
static int keybindGetDescription(lua_State* L) {
const auto keybind = getKeybindFromUserdata(L);
if (!keybind) {
lua_pushnil(L);
return 1;
}
if (!(*keybind)->hasDescription)
lua_pushnil(L);
else
lua_pushstring(L, (*keybind)->description.c_str());
return 1;
}
static int keybindIndex(lua_State* L) {
const auto keybind = getKeybindFromUserdata(L);
const std::string_view key = luaL_checkstring(L, 2);
if (key == "set_enabled")
lua_pushcfunction(L, keybindSetEnabled);
else if (key == "is_enabled")
lua_pushcfunction(L, keybindIsEnabled);
else if (key == "remove" || key == "unbind")
lua_pushcfunction(L, keybindRemove);
else if (!keybind)
lua_pushnil(L);
else if (key == "enabled")
lua_pushboolean(L, (*keybind)->enabled);
else if (key == "has_description")
lua_pushboolean(L, (*keybind)->hasDescription);
else if (key == "description")
return keybindGetDescription(L);
else if (key == "display_key")
lua_pushstring(L, (*keybind)->displayKey.c_str());
else if (key == "submap")
lua_pushstring(L, (*keybind)->submap.name.c_str());
else if (key == "handler")
lua_pushstring(L, (*keybind)->handler.c_str());
else if (key == "arg")
lua_pushstring(L, (*keybind)->arg.c_str());
else if (key == "modmask")
lua_pushinteger(L, sc<lua_Integer>((*keybind)->modmask));
else if (key == "key")
lua_pushstring(L, (*keybind)->key.c_str());
else if (key == "keycode")
lua_pushinteger(L, sc<lua_Integer>((*keybind)->keycode));
else if (key == "catchall")
lua_pushboolean(L, (*keybind)->catchAll);
else if (key == "repeating")
lua_pushboolean(L, (*keybind)->repeat);
else if (key == "locked")
lua_pushboolean(L, (*keybind)->locked);
else if (key == "release")
lua_pushboolean(L, (*keybind)->release);
else if (key == "non_consuming")
lua_pushboolean(L, (*keybind)->nonConsuming);
else if (key == "transparent")
lua_pushboolean(L, (*keybind)->transparent);
else if (key == "ignore_mods")
lua_pushboolean(L, (*keybind)->ignoreMods);
else if (key == "long_press")
lua_pushboolean(L, (*keybind)->longPress);
else if (key == "dont_inhibit")
lua_pushboolean(L, (*keybind)->dontInhibit);
else if (key == "click")
lua_pushboolean(L, (*keybind)->click);
else if (key == "drag")
lua_pushboolean(L, (*keybind)->drag);
else if (key == "submap_universal")
lua_pushboolean(L, (*keybind)->submapUniversal);
else if (key == "mouse")
lua_pushboolean(L, (*keybind)->mouse);
else if (key == "device_inclusive")
lua_pushboolean(L, (*keybind)->deviceInclusive);
else if (key == "devices")
pushDeviceList(L, **keybind);
else
lua_pushnil(L);
return 1;
}
void Objects::CLuaKeybind::setup(lua_State* L) {
registerMetatable(L, MT, keybindIndex, gcRef<WP<SKeybind>>, keybindEq, keybindToString);
}
void Objects::CLuaKeybind::push(lua_State* L, const SP<SKeybind>& keybind) {
new (lua_newuserdata(L, sizeof(WP<SKeybind>))) WP<SKeybind>(keybind);
luaL_getmetatable(L, MT);
lua_setmetatable(L, -2);
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "LuaObjectHelpers.hpp"
#include "../../../managers/KeybindManager.hpp"
namespace Config::Lua::Objects {
class CLuaKeybind : public ILuaObjectWrapper {
public:
void setup(lua_State* L) override;
static void push(lua_State* L, const SP<SKeybind>& keybind);
};
}
+80
View File
@@ -0,0 +1,80 @@
#include "LuaLayerRule.hpp"
#include "../../../desktop/rule/Engine.hpp"
#include <string_view>
using namespace Config::Lua;
static constexpr const char* MT = "HL.LayerRule";
//
static int layerRuleEq(lua_State* L) {
const auto* lhs = sc<WP<Desktop::Rule::CLayerRule>*>(luaL_checkudata(L, 1, MT));
const auto* rhs = sc<WP<Desktop::Rule::CLayerRule>*>(luaL_checkudata(L, 2, MT));
lua_pushboolean(L, lhs->lock() == rhs->lock());
return 1;
}
static int layerRuleToString(lua_State* L) {
const auto* ref = sc<WP<Desktop::Rule::CLayerRule>*>(luaL_checkudata(L, 1, MT));
const auto rule = ref->lock();
if (!rule)
lua_pushstring(L, "HL.LayerRule(expired)");
else
lua_pushfstring(L, "HL.LayerRule(%p)", rule.get());
return 1;
}
static int layerRuleSetEnabled(lua_State* L) {
auto* ref = sc<WP<Desktop::Rule::CLayerRule>*>(luaL_checkudata(L, 1, MT));
luaL_checktype(L, 2, LUA_TBOOLEAN);
const auto rule = ref->lock();
if (!rule)
return 0;
rule->setEnabled(lua_toboolean(L, 2));
Desktop::Rule::ruleEngine()->updateAllRules();
return 0;
}
static int layerRuleIsEnabled(lua_State* L) {
auto* ref = sc<WP<Desktop::Rule::CLayerRule>*>(luaL_checkudata(L, 1, MT));
const auto rule = ref->lock();
if (!rule) {
lua_pushnil(L);
return 1;
}
lua_pushboolean(L, rule->isEnabled());
return 1;
}
static int layerRuleIndex(lua_State* L) {
luaL_checkudata(L, 1, MT);
const std::string_view key = luaL_checkstring(L, 2);
if (key == "set_enabled")
lua_pushcfunction(L, layerRuleSetEnabled);
else if (key == "is_enabled")
lua_pushcfunction(L, layerRuleIsEnabled);
else
lua_pushnil(L);
return 1;
}
void Objects::CLuaLayerRule::setup(lua_State* L) {
registerMetatable(L, MT, layerRuleIndex, gcRef<WP<Desktop::Rule::CLayerRule>>, layerRuleEq, layerRuleToString);
}
void Objects::CLuaLayerRule::push(lua_State* L, const SP<Desktop::Rule::CLayerRule>& rule) {
new (lua_newuserdata(L, sizeof(WP<Desktop::Rule::CLayerRule>))) WP<Desktop::Rule::CLayerRule>(rule);
luaL_getmetatable(L, MT);
lua_setmetatable(L, -2);
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "LuaObjectHelpers.hpp"
#include "../../../desktop/rule/layerRule/LayerRule.hpp"
namespace Config::Lua::Objects {
class CLuaLayerRule : public ILuaObjectWrapper {
public:
void setup(lua_State* L) override;
static void push(lua_State* L, const SP<Desktop::Rule::CLayerRule>& rule);
};
}
@@ -0,0 +1,88 @@
#include "LuaLayerSurface.hpp"
#include "LuaMonitor.hpp"
#include "LuaObjectHelpers.hpp"
#include "../../../desktop/view/LayerSurface.hpp"
#include <format>
#include <string_view>
using namespace Config::Lua;
static constexpr const char* MT = "HL.LayerSurface";
//
static int layerSurfaceEq(lua_State* L) {
const auto* lhs = sc<PHLLSREF*>(luaL_checkudata(L, 1, MT));
const auto* rhs = sc<PHLLSREF*>(luaL_checkudata(L, 2, MT));
lua_pushboolean(L, lhs->lock() == rhs->lock());
return 1;
}
static int layerSurfaceToString(lua_State* L) {
const auto* ref = sc<PHLLSREF*>(luaL_checkudata(L, 1, MT));
const auto ls = ref->lock();
if (!ls)
lua_pushstring(L, "HL.LayerSurface(expired)");
else
lua_pushfstring(L, "HL.LayerSurface(%p)", ls.get());
return 1;
}
static int layerSurfaceIndex(lua_State* L) {
auto* ref = sc<PHLLSREF*>(luaL_checkudata(L, 1, MT));
const auto ls = ref->lock();
if (!ls) {
Log::logger->log(Log::DEBUG, "[lua] Tried to access an expired object");
lua_pushnil(L);
return 1;
}
const std::string_view key = luaL_checkstring(L, 2);
if (key == "address")
lua_pushstring(L, std::format("0x{:x}", reinterpret_cast<uintptr_t>(ls.get())).c_str());
else if (key == "x")
lua_pushinteger(L, ls->m_geometry.x);
else if (key == "y")
lua_pushinteger(L, ls->m_geometry.y);
else if (key == "w")
lua_pushinteger(L, ls->m_geometry.width);
else if (key == "h")
lua_pushinteger(L, ls->m_geometry.height);
else if (key == "namespace")
lua_pushstring(L, ls->m_namespace.c_str());
else if (key == "pid")
lua_pushinteger(L, sc<lua_Integer>(ls->getPID()));
else if (key == "monitor") {
const auto mon = ls->m_monitor.lock();
if (mon)
Objects::CLuaMonitor::push(L, mon);
else
lua_pushnil(L);
} else if (key == "mapped")
lua_pushboolean(L, ls->m_mapped);
else if (key == "layer")
lua_pushinteger(L, sc<lua_Integer>(ls->m_layer));
else if (key == "interactivity")
lua_pushinteger(L, sc<lua_Integer>(ls->m_interactivity));
else if (key == "above_fullscreen")
lua_pushboolean(L, ls->m_aboveFullscreen);
else
lua_pushnil(L);
return 1;
}
void Objects::CLuaLayerSurface::setup(lua_State* L) {
registerMetatable(L, MT, layerSurfaceIndex, gcRef<PHLLSREF>, layerSurfaceEq, layerSurfaceToString);
}
void Objects::CLuaLayerSurface::push(lua_State* L, PHLLS ls) {
new (lua_newuserdata(L, sizeof(PHLLSREF))) PHLLSREF(ls ? ls->m_self : nullptr);
luaL_getmetatable(L, MT);
lua_setmetatable(L, -2);
}
@@ -0,0 +1,12 @@
#pragma once
#include "LuaObjectHelpers.hpp"
#include "../../../desktop/DesktopTypes.hpp"
namespace Config::Lua::Objects {
class CLuaLayerSurface : public ILuaObjectWrapper {
public:
void setup(lua_State* L) override;
static void push(lua_State* L, PHLLS ls);
};
}
+122
View File
@@ -0,0 +1,122 @@
#include "LuaMonitor.hpp"
#include "LuaWorkspace.hpp"
#include "LuaObjectHelpers.hpp"
#include "../../../helpers/Monitor.hpp"
#include "../../../desktop/state/FocusState.hpp"
#include <string_view>
using namespace Config::Lua;
static constexpr const char* MT = "HL.Monitor";
//
static int monitorEq(lua_State* L) {
const auto* lhs = sc<PHLMONITORREF*>(luaL_checkudata(L, 1, MT));
const auto* rhs = sc<PHLMONITORREF*>(luaL_checkudata(L, 2, MT));
lua_pushboolean(L, lhs->lock() == rhs->lock());
return 1;
}
static int monitorToString(lua_State* L) {
const auto* ref = sc<PHLMONITORREF*>(luaL_checkudata(L, 1, MT));
const auto mon = ref->lock();
if (!mon)
lua_pushstring(L, "HL.Monitor(expired)");
else
lua_pushfstring(L, "HL.Monitor(%d:%s)", mon->m_id, mon->m_name.c_str());
return 1;
}
static int monitorIndex(lua_State* L) {
auto* ref = sc<PHLMONITORREF*>(luaL_checkudata(L, 1, MT));
const auto mon = ref->lock();
if (!mon) {
Log::logger->log(Log::DEBUG, "[lua] Tried to access an expired object");
lua_pushnil(L);
return 1;
}
const std::string_view key = luaL_checkstring(L, 2);
if (key == "id")
lua_pushinteger(L, sc<lua_Integer>(mon->m_id));
else if (key == "name")
lua_pushstring(L, mon->m_name.c_str());
else if (key == "description")
lua_pushstring(L, mon->m_shortDescription.c_str());
else if (key == "width")
lua_pushinteger(L, sc<int>(mon->m_pixelSize.x));
else if (key == "height")
lua_pushinteger(L, sc<int>(mon->m_pixelSize.y));
else if (key == "refresh_rate")
lua_pushnumber(L, mon->m_refreshRate);
else if (key == "x")
lua_pushinteger(L, sc<int>(mon->m_position.x));
else if (key == "y")
lua_pushinteger(L, sc<int>(mon->m_position.y));
else if (key == "active_workspace") {
if (mon->m_activeWorkspace)
Objects::CLuaWorkspace::push(L, mon->m_activeWorkspace);
else
lua_pushnil(L);
} else if (key == "active_special_workspace") {
if (mon->m_activeSpecialWorkspace)
Objects::CLuaWorkspace::push(L, mon->m_activeSpecialWorkspace);
else
lua_pushnil(L);
} else if (key == "position") {
lua_newtable(L);
lua_pushinteger(L, sc<int>(mon->m_position.x));
lua_setfield(L, -2, "x");
lua_pushinteger(L, sc<int>(mon->m_position.y));
lua_setfield(L, -2, "y");
} else if (key == "size") {
lua_newtable(L);
lua_pushinteger(L, sc<int>(mon->m_pixelSize.x));
lua_setfield(L, -2, "width");
lua_pushinteger(L, sc<int>(mon->m_pixelSize.y));
lua_setfield(L, -2, "height");
} else if (key == "scale")
lua_pushnumber(L, mon->m_scale);
else if (key == "transform")
lua_pushinteger(L, sc<int>(mon->m_transform));
else if (key == "dpms_status")
lua_pushboolean(L, mon->m_dpmsStatus);
else if (key == "vrr_active")
lua_pushboolean(L, mon->m_vrrActive);
else if (key == "is_mirror")
lua_pushboolean(L, mon->isMirror());
else if (key == "mirrors") {
lua_newtable(L);
int i = 1;
for (const auto& mirrorRef : mon->m_mirrors) {
const auto mirror = mirrorRef.lock();
if (!mirror)
continue;
Objects::CLuaMonitor::push(L, mirror);
lua_rawseti(L, -2, i++);
}
} else if (key == "focused")
lua_pushboolean(L, mon == Desktop::focusState()->monitor());
else
lua_pushnil(L);
return 1;
}
void Objects::CLuaMonitor::setup(lua_State* L) {
registerMetatable(L, MT, monitorIndex, gcRef<PHLMONITORREF>, monitorEq, monitorToString);
}
void Objects::CLuaMonitor::push(lua_State* L, PHLMONITOR mon) {
new (lua_newuserdata(L, sizeof(PHLMONITORREF))) PHLMONITORREF(mon ? mon->m_self : nullptr);
luaL_getmetatable(L, MT);
lua_setmetatable(L, -2);
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "LuaObjectHelpers.hpp"
#include "../../../desktop/DesktopTypes.hpp"
namespace Config::Lua::Objects {
class CLuaMonitor : public ILuaObjectWrapper {
public:
void setup(lua_State* L) override;
static void push(lua_State* L, PHLMONITOR mon);
};
}
+377
View File
@@ -0,0 +1,377 @@
#include "LuaNotification.hpp"
#include "../../../helpers/MiscFunctions.hpp"
#include <array>
#include <algorithm>
#include <cctype>
#include <optional>
#include <string_view>
using namespace Config::Lua;
static constexpr const char* MT = "HL.Notification";
namespace {
struct SNotificationRef {
WP<Notification::CNotification> notification;
bool paused = false;
};
std::optional<CHyprColor> parseColor(lua_State* L, int idx) {
if (lua_isnumber(L, idx))
return CHyprColor(sc<uint64_t>(lua_tonumber(L, idx)));
if (lua_isstring(L, idx)) {
auto parsed = configStringToInt(lua_tostring(L, idx));
if (!parsed)
return std::nullopt;
return CHyprColor(sc<uint64_t>(*parsed));
}
return std::nullopt;
}
std::optional<eIcons> iconFromString(std::string iconName) {
std::ranges::transform(iconName, iconName.begin(), [](const unsigned char c) { return std::tolower(c); });
static constexpr std::array<std::pair<const char*, eIcons>, 10> ICON_NAMES = {
std::pair{"warning", ICON_WARNING}, std::pair{"warn", ICON_WARNING}, std::pair{"info", ICON_INFO}, std::pair{"hint", ICON_HINT},
std::pair{"error", ICON_ERROR}, std::pair{"err", ICON_ERROR}, std::pair{"confused", ICON_CONFUSED}, std::pair{"question", ICON_CONFUSED},
std::pair{"ok", ICON_OK}, std::pair{"none", ICON_NONE},
};
for (const auto& [name, icon] : ICON_NAMES) {
if (name == iconName)
return icon;
}
return std::nullopt;
}
std::optional<eIcons> parseIcon(lua_State* L, int idx) {
if (lua_isnumber(L, idx)) {
const auto raw = sc<int>(lua_tonumber(L, idx));
if (raw >= ICON_WARNING && raw <= ICON_NONE)
return sc<eIcons>(raw);
return std::nullopt;
}
if (lua_isstring(L, idx))
return iconFromString(lua_tostring(L, idx));
return std::nullopt;
}
}
static int notificationEq(lua_State* L) {
const auto* lhs = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
const auto* rhs = sc<SNotificationRef*>(luaL_checkudata(L, 2, MT));
lua_pushboolean(L, lhs->notification.lock() == rhs->notification.lock());
return 1;
}
static int notificationToString(lua_State* L) {
const auto* ref = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
const auto notification = ref->notification.lock();
if (!notification)
lua_pushstring(L, "HL.Notification(expired)");
else
lua_pushfstring(L, "HL.Notification(%p)", notification.get());
return 1;
}
static int notificationGC(lua_State* L) {
auto* ref = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
if (ref->paused) {
if (const auto notification = ref->notification.lock(); notification)
notification->unlock();
}
ref->~SNotificationRef();
return 0;
}
static int notificationPause(lua_State* L) {
auto* ref = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
const auto notification = ref->notification.lock();
if (!notification)
return 0;
if (ref->paused)
return 0;
notification->lock();
ref->paused = true;
return 0;
}
static int notificationResume(lua_State* L) {
auto* ref = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
const auto notification = ref->notification.lock();
if (!notification)
return 0;
if (!ref->paused)
return 0;
notification->unlock();
ref->paused = false;
return 0;
}
static int notificationSetPaused(lua_State* L) {
luaL_checkudata(L, 1, MT);
luaL_checktype(L, 2, LUA_TBOOLEAN);
if (lua_toboolean(L, 2))
return notificationPause(L);
return notificationResume(L);
}
static int notificationIsPaused(lua_State* L) {
auto* ref = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
const auto notification = ref->notification.lock();
if (!notification) {
lua_pushnil(L);
return 1;
}
lua_pushboolean(L, notification->isLocked());
return 1;
}
static int notificationSetText(lua_State* L) {
auto* ref = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
const auto* text = luaL_checkstring(L, 2);
if (const auto notification = ref->notification.lock(); notification)
notification->setText(text);
return 0;
}
static int notificationSetTimeout(lua_State* L) {
auto* ref = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
const auto timeoutMs = sc<float>(luaL_checknumber(L, 2));
if (timeoutMs < 0.F)
return Config::Lua::Bindings::Internal::configError(L, "HL.Notification:set_timeout: timeout must be >= 0");
if (const auto notification = ref->notification.lock(); notification)
notification->resetTimeout(timeoutMs);
return 0;
}
static int notificationSetColor(lua_State* L) {
auto* ref = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
const auto color = parseColor(L, 2);
if (!color)
return Config::Lua::Bindings::Internal::configError(L, "HL.Notification:set_color: expected a color string or number");
if (const auto notification = ref->notification.lock(); notification)
notification->setColor(*color);
return 0;
}
static int notificationSetIcon(lua_State* L) {
auto* ref = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
const auto icon = parseIcon(L, 2);
if (!icon)
return Config::Lua::Bindings::Internal::configError(L, "HL.Notification:set_icon: expected an icon name or number");
if (const auto notification = ref->notification.lock(); notification)
notification->setIcon(*icon);
return 0;
}
static int notificationSetFontSize(lua_State* L) {
auto* ref = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
const auto fontSize = sc<float>(luaL_checknumber(L, 2));
if (fontSize <= 0.F)
return Config::Lua::Bindings::Internal::configError(L, "HL.Notification:set_font_size: font size must be > 0");
if (const auto notification = ref->notification.lock(); notification)
notification->setFontSize(fontSize);
return 0;
}
static int notificationDismiss(lua_State* L) {
auto* ref = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
if (const auto notification = ref->notification.lock(); notification)
Notification::overlay()->dismissNotification(notification);
return 0;
}
static int notificationGetText(lua_State* L) {
auto* ref = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
const auto notification = ref->notification.lock();
if (!notification) {
lua_pushnil(L);
return 1;
}
lua_pushstring(L, notification->text().c_str());
return 1;
}
static int notificationGetTimeout(lua_State* L) {
auto* ref = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
const auto notification = ref->notification.lock();
if (!notification) {
lua_pushnil(L);
return 1;
}
lua_pushnumber(L, notification->timeMs());
return 1;
}
static int notificationGetColor(lua_State* L) {
auto* ref = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
const auto notification = ref->notification.lock();
if (!notification) {
lua_pushnil(L);
return 1;
}
lua_pushinteger(L, sc<lua_Integer>(notification->color().getAsHex()));
return 1;
}
static int notificationGetIcon(lua_State* L) {
auto* ref = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
const auto notification = ref->notification.lock();
if (!notification) {
lua_pushnil(L);
return 1;
}
lua_pushinteger(L, sc<lua_Integer>(notification->icon()));
return 1;
}
static int notificationGetFontSize(lua_State* L) {
auto* ref = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
const auto notification = ref->notification.lock();
if (!notification) {
lua_pushnil(L);
return 1;
}
lua_pushnumber(L, notification->fontSize());
return 1;
}
static int notificationGetElapsed(lua_State* L) {
auto* ref = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
const auto notification = ref->notification.lock();
if (!notification) {
lua_pushnil(L);
return 1;
}
lua_pushnumber(L, notification->timeElapsedMs());
return 1;
}
static int notificationGetElapsedSinceCreation(lua_State* L) {
auto* ref = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
const auto notification = ref->notification.lock();
if (!notification) {
lua_pushnil(L);
return 1;
}
lua_pushnumber(L, notification->timeElapsedSinceCreationMs());
return 1;
}
static int notificationIsAlive(lua_State* L) {
auto* ref = sc<SNotificationRef*>(luaL_checkudata(L, 1, MT));
lua_pushboolean(L, ref->notification.lock().get() != nullptr);
return 1;
}
static int notificationIndex(lua_State* L) {
luaL_checkudata(L, 1, MT);
const std::string_view key = luaL_checkstring(L, 2);
if (key == "pause")
lua_pushcfunction(L, notificationPause);
else if (key == "resume")
lua_pushcfunction(L, notificationResume);
else if (key == "set_paused")
lua_pushcfunction(L, notificationSetPaused);
else if (key == "is_paused")
lua_pushcfunction(L, notificationIsPaused);
else if (key == "set_text")
lua_pushcfunction(L, notificationSetText);
else if (key == "set_timeout")
lua_pushcfunction(L, notificationSetTimeout);
else if (key == "set_color")
lua_pushcfunction(L, notificationSetColor);
else if (key == "set_icon")
lua_pushcfunction(L, notificationSetIcon);
else if (key == "set_font_size")
lua_pushcfunction(L, notificationSetFontSize);
else if (key == "dismiss")
lua_pushcfunction(L, notificationDismiss);
else if (key == "get_text")
lua_pushcfunction(L, notificationGetText);
else if (key == "get_timeout")
lua_pushcfunction(L, notificationGetTimeout);
else if (key == "get_color")
lua_pushcfunction(L, notificationGetColor);
else if (key == "get_icon")
lua_pushcfunction(L, notificationGetIcon);
else if (key == "get_font_size")
lua_pushcfunction(L, notificationGetFontSize);
else if (key == "get_elapsed")
lua_pushcfunction(L, notificationGetElapsed);
else if (key == "get_elapsed_since_creation")
lua_pushcfunction(L, notificationGetElapsedSinceCreation);
else if (key == "is_alive")
lua_pushcfunction(L, notificationIsAlive);
else
lua_pushnil(L);
return 1;
}
void Objects::CLuaNotification::setup(lua_State* L) {
registerMetatable(L, MT, notificationIndex, notificationGC, notificationEq, notificationToString);
}
void Objects::CLuaNotification::push(lua_State* L, const SP<Notification::CNotification>& notification) {
new (lua_newuserdata(L, sizeof(SNotificationRef))) SNotificationRef{.notification = WP<Notification::CNotification>(notification)};
luaL_getmetatable(L, MT);
lua_setmetatable(L, -2);
}
@@ -0,0 +1,12 @@
#pragma once
#include "LuaObjectHelpers.hpp"
#include "../../../notification/NotificationOverlay.hpp"
namespace Config::Lua::Objects {
class CLuaNotification : public ILuaObjectWrapper {
public:
void setup(lua_State* L) override;
static void push(lua_State* L, const SP<Notification::CNotification>& notification);
};
}
@@ -0,0 +1,51 @@
#pragma once
#include "../bindings/LuaBindingsInternal.hpp"
#include "../../../helpers/memory/Memory.hpp"
extern "C" {
#include <lauxlib.h>
}
namespace Config::Lua::Objects {
template <typename T>
inline int gcRef(lua_State* L) {
sc<T*>(lua_touserdata(L, 1))->~T();
return 0;
}
inline int readOnlyNewIndex(lua_State* L) {
return Config::Lua::Bindings::Internal::configError(L, "attempt to modify read-only hl object");
}
inline void registerMetatable(lua_State* L, const char* name, lua_CFunction indexFn, lua_CFunction gcFn, lua_CFunction eqFn = nullptr, lua_CFunction toStringFn = nullptr) {
luaL_newmetatable(L, name);
lua_pushcfunction(L, indexFn);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, readOnlyNewIndex);
lua_setfield(L, -2, "__newindex");
lua_pushcfunction(L, gcFn);
lua_setfield(L, -2, "__gc");
if (eqFn) {
lua_pushcfunction(L, eqFn);
lua_setfield(L, -2, "__eq");
}
if (toStringFn) {
lua_pushcfunction(L, toStringFn);
lua_setfield(L, -2, "__tostring");
}
lua_pop(L, 1);
}
class ILuaObjectWrapper {
public:
virtual ~ILuaObjectWrapper() = default;
virtual void setup(lua_State* L) = 0;
};
}
+109
View File
@@ -0,0 +1,109 @@
#include "LuaTimer.hpp"
#include <algorithm>
#include <string_view>
using namespace Config::Lua;
static constexpr const char* MT = "HL.Timer";
namespace {
struct STimerRef {
WP<CEventLoopTimer> timer;
int timeoutMs = 0;
};
}
static int timerEq(lua_State* L) {
const auto* lhs = sc<STimerRef*>(luaL_checkudata(L, 1, MT));
const auto* rhs = sc<STimerRef*>(luaL_checkudata(L, 2, MT));
lua_pushboolean(L, lhs->timer.lock() == rhs->timer.lock());
return 1;
}
static int timerToString(lua_State* L) {
const auto* ref = sc<STimerRef*>(luaL_checkudata(L, 1, MT));
const auto timer = ref->timer.lock();
if (!timer)
lua_pushstring(L, "HL.Timer(expired)");
else
lua_pushfstring(L, "HL.Timer(%p)", timer.get());
return 1;
}
static int timerSetEnabled(lua_State* L) {
auto* ref = sc<STimerRef*>(luaL_checkudata(L, 1, MT));
luaL_checktype(L, 2, LUA_TBOOLEAN);
const auto timer = ref->timer.lock();
if (!timer)
return 0;
if (lua_toboolean(L, 2))
timer->updateTimeout(std::chrono::milliseconds(std::max(ref->timeoutMs, 1)));
else
timer->updateTimeout(std::nullopt);
return 0;
}
static int timerSetTimeout(lua_State* L) {
auto* ref = sc<STimerRef*>(luaL_checkudata(L, 1, MT));
luaL_checktype(L, 2, LUA_TNUMBER);
const auto timer = ref->timer.lock();
if (!timer)
return 0;
uint64_t x = lua_tointeger(L, 2);
if (x < 1)
return Config::Lua::Bindings::Internal::configError(L, "hl.timer:set_timeout: timeout must be greater or equal to 1ms");
timer->updateTimeout(std::chrono::milliseconds(x));
ref->timeoutMs = x;
return 0;
}
static int timerIsEnabled(lua_State* L) {
auto* ref = sc<STimerRef*>(luaL_checkudata(L, 1, MT));
const auto timer = ref->timer.lock();
if (!timer) {
lua_pushnil(L);
return 1;
}
lua_pushboolean(L, timer->armed());
return 1;
}
static int timerIndex(lua_State* L) {
luaL_checkudata(L, 1, MT);
const std::string_view key = luaL_checkstring(L, 2);
if (key == "set_enabled")
lua_pushcfunction(L, timerSetEnabled);
else if (key == "is_enabled")
lua_pushcfunction(L, timerIsEnabled);
else if (key == "set_timeout")
lua_pushcfunction(L, timerSetTimeout);
else
lua_pushnil(L);
return 1;
}
void Objects::CLuaTimer::setup(lua_State* L) {
registerMetatable(L, MT, timerIndex, gcRef<STimerRef>, timerEq, timerToString);
}
void Objects::CLuaTimer::push(lua_State* L, const SP<CEventLoopTimer>& timer, int timeoutMs) {
new (lua_newuserdata(L, sizeof(STimerRef))) STimerRef{WP<CEventLoopTimer>(timer), timeoutMs};
luaL_getmetatable(L, MT);
lua_setmetatable(L, -2);
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "LuaObjectHelpers.hpp"
#include "../../../managers/eventLoop/EventLoopTimer.hpp"
namespace Config::Lua::Objects {
class CLuaTimer : public ILuaObjectWrapper {
public:
void setup(lua_State* L) override;
static void push(lua_State* L, const SP<CEventLoopTimer>& timer, int timeoutMs);
};
}
+277
View File
@@ -0,0 +1,277 @@
#include "LuaWindow.hpp"
#include "LuaWorkspace.hpp"
#include "LuaMonitor.hpp"
#include "LuaObjectHelpers.hpp"
#include "../../../desktop/view/Window.hpp"
#include "../../../desktop/view/Group.hpp"
#include "../../../desktop/Workspace.hpp"
#include "../../../desktop/state/FocusState.hpp"
#include "../../../desktop/history/WindowHistoryTracker.hpp"
#include "../../../layout/algorithm/Algorithm.hpp"
#include "../../../layout/algorithm/tiled/master/MasterAlgorithm.hpp"
#include "../../../layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp"
#include "../../../layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp"
#include "../../../layout/space/Space.hpp"
#include "../../../layout/supplementary/WorkspaceAlgoMatcher.hpp"
#include "../../../managers/input/InputManager.hpp"
#include <format>
#include <string_view>
using namespace Config::Lua;
static constexpr const char* MT = "HL.Window";
//
static int getFocusHistoryID(PHLWINDOW wnd) {
const auto& history = Desktop::History::windowTracker()->fullHistory();
for (size_t i = 0; i < history.size(); ++i) {
if (history[i].lock() == wnd)
return sc<int>(history.size() - i - 1); // reverse order for backwards compat
}
return -1;
}
static int windowEq(lua_State* L) {
const auto* lhs = sc<PHLWINDOWREF*>(luaL_checkudata(L, 1, MT));
const auto* rhs = sc<PHLWINDOWREF*>(luaL_checkudata(L, 2, MT));
lua_pushboolean(L, lhs->lock() == rhs->lock());
return 1;
}
static int windowToString(lua_State* L) {
const auto* ref = sc<PHLWINDOWREF*>(luaL_checkudata(L, 1, MT));
const auto w = ref->lock();
if (!w)
lua_pushstring(L, "HL.Window(expired)");
else
lua_pushfstring(L, "HL.Window(%p)", w.get());
return 1;
}
static int windowIndex(lua_State* L) {
auto* ref = sc<PHLWINDOWREF*>(luaL_checkudata(L, 1, MT));
const auto w = ref->lock();
if (!w) {
Log::logger->log(Log::DEBUG, "[lua] Tried to access an expired object");
lua_pushnil(L);
return 1;
}
const std::string_view key = luaL_checkstring(L, 2);
if (key == "address")
lua_pushstring(L, std::format("0x{:x}", reinterpret_cast<uintptr_t>(w.get())).c_str());
else if (key == "mapped")
lua_pushboolean(L, w->m_isMapped);
else if (key == "hidden")
lua_pushboolean(L, w->isHidden());
else if (key == "at") {
lua_newtable(L);
lua_pushinteger(L, sc<int>(w->m_realPosition->goal().x));
lua_setfield(L, -2, "x");
lua_pushinteger(L, sc<int>(w->m_realPosition->goal().y));
lua_setfield(L, -2, "y");
} else if (key == "size") {
lua_newtable(L);
lua_pushinteger(L, sc<int>(w->m_realSize->goal().x));
lua_setfield(L, -2, "x");
lua_pushinteger(L, sc<int>(w->m_realSize->goal().y));
lua_setfield(L, -2, "y");
} else if (key == "workspace") {
if (w->m_workspace)
Objects::CLuaWorkspace::push(L, w->m_workspace);
else
lua_pushnil(L);
} else if (key == "floating")
lua_pushboolean(L, w->m_isFloating);
else if (key == "monitor") {
const auto mon = w->m_monitor.lock();
if (mon)
Objects::CLuaMonitor::push(L, mon);
else
lua_pushnil(L);
} else if (key == "class")
lua_pushstring(L, w->m_class.c_str());
else if (key == "title")
lua_pushstring(L, w->m_title.c_str());
else if (key == "initial_class")
lua_pushstring(L, w->m_initialClass.c_str());
else if (key == "initial_title")
lua_pushstring(L, w->m_initialTitle.c_str());
else if (key == "pid")
lua_pushinteger(L, sc<lua_Integer>(w->getPID()));
else if (key == "xwayland")
lua_pushboolean(L, w->m_isX11);
else if (key == "pinned")
lua_pushboolean(L, w->m_pinned);
else if (key == "fullscreen")
lua_pushinteger(L, sc<lua_Integer>(sc<uint8_t>(w->m_fullscreenState.internal)));
else if (key == "fullscreen_client")
lua_pushinteger(L, sc<lua_Integer>(sc<uint8_t>(w->m_fullscreenState.client)));
else if (key == "over_fullscreen")
lua_pushboolean(L, w->m_createdOverFullscreen);
else if (key == "group") {
if (!w->m_group) {
lua_pushnil(L);
return 1;
}
lua_newtable(L);
lua_pushboolean(L, w->m_group->locked());
lua_setfield(L, -2, "locked");
lua_pushboolean(L, w->m_group->denied());
lua_setfield(L, -2, "denied");
lua_pushinteger(L, sc<lua_Integer>(w->m_group->size()));
lua_setfield(L, -2, "size");
lua_pushinteger(L, sc<lua_Integer>(w->m_group->getCurrentIdx()) + 1);
lua_setfield(L, -2, "current_index");
const auto current = w->m_group->current();
if (current)
Objects::CLuaWindow::push(L, current);
else
lua_pushnil(L);
lua_setfield(L, -2, "current");
lua_newtable(L);
int i = 1;
for (const auto& grouped : w->m_group->windows()) {
const auto groupedWindow = grouped.lock();
if (!groupedWindow)
continue;
Objects::CLuaWindow::push(L, groupedWindow);
lua_rawseti(L, -2, i++);
}
lua_setfield(L, -2, "members");
} else if (key == "tags") {
lua_newtable(L);
int i = 1;
for (const auto& tag : w->m_ruleApplicator->m_tagKeeper.getTags()) {
lua_pushstring(L, tag.c_str());
lua_rawseti(L, -2, i++);
}
} else if (key == "swallowing") {
const auto swallowed = w->m_swallowed.lock();
if (swallowed)
Objects::CLuaWindow::push(L, swallowed);
else
lua_pushnil(L);
} else if (key == "focus_history_id")
lua_pushinteger(L, sc<lua_Integer>(getFocusHistoryID(w)));
else if (key == "inhibiting_idle")
lua_pushboolean(L, g_pInputManager && g_pInputManager->isWindowInhibiting(w, false));
else if (key == "xdg_tag") {
const auto xdgTag = w->xdgTag();
if (xdgTag)
lua_pushstring(L, xdgTag->c_str());
else
lua_pushnil(L);
} else if (key == "xdg_description") {
const auto xdgDescription = w->xdgDescription();
if (xdgDescription)
lua_pushstring(L, xdgDescription->c_str());
else
lua_pushnil(L);
} else if (key == "content_type")
lua_pushstring(L, NContentType::toString(w->getContentType()).c_str());
else if (key == "stable_id")
lua_pushinteger(L, sc<lua_Integer>(w->m_stableID));
else if (key == "layout") {
const auto target = w->layoutTarget();
if (!target || target->floating() || !w->m_workspace || !w->m_workspace->m_space) {
lua_pushnil(L);
return 1;
}
const auto& algo = w->m_workspace->m_space->algorithm();
if (!algo || !algo->tiledAlgo()) {
lua_pushnil(L);
return 1;
}
const auto& tiledAlgo = algo->tiledAlgo();
const std::string name = Layout::Supplementary::algoMatcher()->getNameForTiledAlgo(&typeid(*tiledAlgo.get()));
lua_newtable(L);
lua_pushstring(L, name.c_str());
lua_setfield(L, -2, "name");
if (const auto* master = dynamic_cast<Layout::Tiled::CMasterAlgorithm*>(tiledAlgo.get())) {
const auto node = master->getNodeFromTarget(target);
if (node) {
lua_pushboolean(L, node->isMaster);
lua_setfield(L, -2, "is_master");
lua_pushnumber(L, node->percMaster);
lua_setfield(L, -2, "perc_master");
lua_pushnumber(L, node->percSize);
lua_setfield(L, -2, "perc_size");
}
} else if (auto* scrolling = dynamic_cast<Layout::Tiled::CScrollingAlgorithm*>(tiledAlgo.get())) {
const auto data = scrolling->dataFor(target);
if (data) {
const auto col = data->column.lock();
if (col) {
const auto scrollingData = col->scrollingData.lock();
lua_newtable(L);
if (scrollingData) {
lua_pushinteger(L, sc<lua_Integer>(scrollingData->idx(col)));
lua_setfield(L, -2, "index");
}
lua_pushnumber(L, col->getColumnWidth());
lua_setfield(L, -2, "width");
lua_newtable(L);
int i = 1;
for (const auto& td : col->targetDatas) {
const auto t = td->target.lock();
if (t) {
const auto win = t->window();
if (win) {
Objects::CLuaWindow::push(L, win);
lua_rawseti(L, -2, i++);
}
}
}
lua_setfield(L, -2, "windows");
lua_setfield(L, -2, "column");
lua_pushinteger(L, sc<lua_Integer>(col->idx(target)));
lua_setfield(L, -2, "index_in_column");
}
}
}
} else if (key == "active") {
lua_pushboolean(L, w == Desktop::focusState()->window());
} else
lua_pushnil(L);
return 1;
}
void Objects::CLuaWindow::setup(lua_State* L) {
registerMetatable(L, MT, windowIndex, gcRef<PHLWINDOWREF>, windowEq, windowToString);
}
void Objects::CLuaWindow::push(lua_State* L, PHLWINDOW w) {
new (lua_newuserdata(L, sizeof(PHLWINDOWREF))) PHLWINDOWREF(w ? w->m_self : nullptr);
luaL_getmetatable(L, MT);
lua_setmetatable(L, -2);
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "LuaObjectHelpers.hpp"
#include "../../../desktop/DesktopTypes.hpp"
namespace Config::Lua::Objects {
class CLuaWindow : public ILuaObjectWrapper {
public:
void setup(lua_State* L) override;
static void push(lua_State* L, PHLWINDOW w);
};
}
+80
View File
@@ -0,0 +1,80 @@
#include "LuaWindowRule.hpp"
#include "../../../desktop/rule/Engine.hpp"
#include <string_view>
using namespace Config::Lua;
static constexpr const char* MT = "HL.WindowRule";
//
static int windowRuleEq(lua_State* L) {
const auto* lhs = sc<WP<Desktop::Rule::CWindowRule>*>(luaL_checkudata(L, 1, MT));
const auto* rhs = sc<WP<Desktop::Rule::CWindowRule>*>(luaL_checkudata(L, 2, MT));
lua_pushboolean(L, lhs->lock() == rhs->lock());
return 1;
}
static int windowRuleToString(lua_State* L) {
const auto* ref = sc<WP<Desktop::Rule::CWindowRule>*>(luaL_checkudata(L, 1, MT));
const auto rule = ref->lock();
if (!rule)
lua_pushstring(L, "HL.WindowRule(expired)");
else
lua_pushfstring(L, "HL.WindowRule(%p)", rule.get());
return 1;
}
static int windowRuleSetEnabled(lua_State* L) {
auto* ref = sc<WP<Desktop::Rule::CWindowRule>*>(luaL_checkudata(L, 1, MT));
luaL_checktype(L, 2, LUA_TBOOLEAN);
const auto rule = ref->lock();
if (!rule)
return 0;
rule->setEnabled(lua_toboolean(L, 2));
Desktop::Rule::ruleEngine()->updateAllRules();
return 0;
}
static int windowRuleIsEnabled(lua_State* L) {
auto* ref = sc<WP<Desktop::Rule::CWindowRule>*>(luaL_checkudata(L, 1, MT));
const auto rule = ref->lock();
if (!rule) {
lua_pushnil(L);
return 1;
}
lua_pushboolean(L, rule->isEnabled());
return 1;
}
static int windowRuleIndex(lua_State* L) {
luaL_checkudata(L, 1, MT);
const std::string_view key = luaL_checkstring(L, 2);
if (key == "set_enabled")
lua_pushcfunction(L, windowRuleSetEnabled);
else if (key == "is_enabled")
lua_pushcfunction(L, windowRuleIsEnabled);
else
lua_pushnil(L);
return 1;
}
void Objects::CLuaWindowRule::setup(lua_State* L) {
registerMetatable(L, MT, windowRuleIndex, gcRef<WP<Desktop::Rule::CWindowRule>>, windowRuleEq, windowRuleToString);
}
void Objects::CLuaWindowRule::push(lua_State* L, const SP<Desktop::Rule::CWindowRule>& rule) {
new (lua_newuserdata(L, sizeof(WP<Desktop::Rule::CWindowRule>))) WP<Desktop::Rule::CWindowRule>(rule);
luaL_getmetatable(L, MT);
lua_setmetatable(L, -2);
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "LuaObjectHelpers.hpp"
#include "../../../desktop/rule/windowRule/WindowRule.hpp"
namespace Config::Lua::Objects {
class CLuaWindowRule : public ILuaObjectWrapper {
public:
void setup(lua_State* L) override;
static void push(lua_State* L, const SP<Desktop::Rule::CWindowRule>& rule);
};
}
+87
View File
@@ -0,0 +1,87 @@
#include "LuaWorkspace.hpp"
#include "LuaMonitor.hpp"
#include "LuaObjectHelpers.hpp"
#include "../../../desktop/Workspace.hpp"
#include "../../../helpers/Monitor.hpp"
#include <string_view>
using namespace Config::Lua;
static constexpr const char* MT = "HL.Workspace";
//
static int workspaceEq(lua_State* L) {
const auto* lhs = sc<PHLWORKSPACEREF*>(luaL_checkudata(L, 1, MT));
const auto* rhs = sc<PHLWORKSPACEREF*>(luaL_checkudata(L, 2, MT));
lua_pushboolean(L, lhs->lock() == rhs->lock());
return 1;
}
static int workspaceToString(lua_State* L) {
const auto* ref = sc<PHLWORKSPACEREF*>(luaL_checkudata(L, 1, MT));
const auto ws = ref->lock();
if (!ws || ws->inert())
lua_pushstring(L, "HL.Workspace(expired)");
else
lua_pushfstring(L, "HL.Workspace(%d:%s)", ws->m_id, ws->m_name.c_str());
return 1;
}
static int workspaceIndex(lua_State* L) {
auto* ref = sc<PHLWORKSPACEREF*>(luaL_checkudata(L, 1, MT));
const auto ws = ref->lock();
if (!ws || ws->inert()) {
Log::logger->log(Log::DEBUG, "[lua] Tried to access an expired object");
lua_pushnil(L);
return 1;
}
const std::string_view key = luaL_checkstring(L, 2);
if (key == "id")
lua_pushinteger(L, sc<lua_Integer>(ws->m_id));
else if (key == "name")
lua_pushstring(L, ws->m_name.c_str());
else if (key == "monitor") {
const auto mon = ws->m_monitor.lock();
if (mon)
Objects::CLuaMonitor::push(L, mon);
else
lua_pushnil(L);
} else if (key == "windows")
lua_pushinteger(L, sc<lua_Integer>(ws->getWindows()));
else if (key == "visible")
lua_pushboolean(L, ws->isVisible());
else if (key == "special")
lua_pushboolean(L, ws->m_isSpecialWorkspace);
else if (key == "active") {
const auto mon = ws->m_monitor.lock();
lua_pushboolean(L, mon && (mon->m_activeWorkspace == ws || mon->m_activeSpecialWorkspace == ws));
} else if (key == "has_urgent")
lua_pushboolean(L, ws->hasUrgentWindow());
else if (key == "fullscreen_mode")
lua_pushinteger(L, sc<lua_Integer>(ws->m_fullscreenMode));
else if (key == "has_fullscreen")
lua_pushboolean(L, ws->m_hasFullscreenWindow);
else if (key == "is_persistent")
lua_pushboolean(L, ws->isPersistent());
else
lua_pushnil(L);
return 1;
}
void Objects::CLuaWorkspace::setup(lua_State* L) {
registerMetatable(L, MT, workspaceIndex, gcRef<PHLWORKSPACEREF>, workspaceEq, workspaceToString);
}
void Objects::CLuaWorkspace::push(lua_State* L, PHLWORKSPACE ws) {
new (lua_newuserdata(L, sizeof(PHLWORKSPACEREF))) PHLWORKSPACEREF(ws ? ws->m_self : nullptr);
luaL_getmetatable(L, MT);
lua_setmetatable(L, -2);
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "LuaObjectHelpers.hpp"
#include "../../../desktop/DesktopTypes.hpp"
namespace Config::Lua::Objects {
class CLuaWorkspace : public ILuaObjectWrapper {
public:
void setup(lua_State* L) override;
static void push(lua_State* L, PHLWORKSPACE ws);
};
}
+52
View File
@@ -0,0 +1,52 @@
#include "LuaConfigBool.hpp"
using namespace Config;
using namespace Config::Lua;
CLuaConfigBool::CLuaConfigBool(Config::BOOL def) : m_default(def), m_data(def) {
;
}
SParseError CLuaConfigBool::parse(lua_State* s) {
if (lua_isnumber(s, -1)) {
auto number = lua_tonumber(s, -1);
if (number != 0 && number != 1)
return {.errorCode = PARSE_ERROR_BAD_TYPE, .message = "boolean type requires a bool, or 0/1."};
m_data = !!number;
m_bSetByUser = true;
return {.errorCode = PARSE_ERROR_OK};
}
if (lua_isboolean(s, -1)) {
m_data = lua_toboolean(s, -1);
m_bSetByUser = true;
return {.errorCode = PARSE_ERROR_OK};
}
return {.errorCode = PARSE_ERROR_BAD_TYPE, .message = "boolean type requires a bool"};
}
const std::type_info* CLuaConfigBool::underlying() {
return &typeid(decltype(m_data));
}
void const* CLuaConfigBool::data() {
return &m_data;
}
std::string CLuaConfigBool::toString() {
return m_data ? "1" : "0";
}
void CLuaConfigBool::push(lua_State* s) {
lua_pushboolean(s, m_data);
}
const Config::BOOL& CLuaConfigBool::parsed() {
return m_data;
}
void CLuaConfigBool::reset() {
m_data = m_default;
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "LuaConfigValue.hpp"
namespace Config::Lua {
class CLuaConfigBool : public ILuaConfigValue {
public:
CLuaConfigBool(Config::BOOL def);
virtual ~CLuaConfigBool() = default;
virtual SParseError parse(lua_State* s);
virtual const std::type_info* underlying();
virtual void const* data();
virtual std::string toString();
virtual void push(lua_State* s);
virtual void reset();
const Config::BOOL& parsed();
private:
Config::BOOL m_default = false;
Config::BOOL m_data = false;
};
};
+63
View File
@@ -0,0 +1,63 @@
#include "LuaConfigColor.hpp"
#include <expected>
#include <format>
#include "../../../helpers/Color.hpp"
#include "../../../helpers/MiscFunctions.hpp"
using namespace Config;
using namespace Config::Lua;
static std::expected<CHyprColor, std::string> parseColorString(const std::string& str) {
auto result = configStringToInt(str);
if (!result)
return std::unexpected(std::format("invalid color \"{}\"", str));
return CHyprColor(sc<uint64_t>(*result));
}
CLuaConfigColor::CLuaConfigColor(Config::INTEGER def) : m_default(def), m_data(def) {
;
}
SParseError CLuaConfigColor::parse(lua_State* s) {
if (lua_isstring(s, -1)) {
auto data = lua_tostring(s, -1);
auto col = parseColorString(data);
if (!col)
return {.errorCode = PARSE_ERROR_BAD_VALUE, .message = col.error()};
m_data = col->getAsHex();
m_bSetByUser = true;
return {.errorCode = PARSE_ERROR_OK};
}
return {.errorCode = PARSE_ERROR_BAD_TYPE, .message = "color type requires a color string"};
}
const std::type_info* CLuaConfigColor::underlying() {
return &typeid(decltype(m_data));
}
void const* CLuaConfigColor::data() {
return &m_data;
}
std::string CLuaConfigColor::toString() {
return std::format("0x{:08X}", (uint32_t)m_data);
}
void CLuaConfigColor::push(lua_State* s) {
const auto col = toString();
lua_pushstring(s, col.c_str());
}
const Config::INTEGER& CLuaConfigColor::parsed() {
return m_data;
}
void CLuaConfigColor::reset() {
m_data = m_default;
}

Some files were not shown because too many files have changed in this diff Show More