Compare commits

...

312 commits

Author SHA1 Message Date
fuyu147
315806f598
tablet: added option to hide cursor (#12525) 2025-12-19 16:14:22 +00:00
Vaxry
6175ecd4c4
debug: move to hyprutils' logger (#12673) 2025-12-18 17:23:24 +00:00
f88deb928a
compositor: warn on start via a log about start-hyprland 2025-12-17 19:26:25 +00:00
Lichie
18901b8e59
desktop/windowRule: force center and move rules to override each other (#12618) 2025-12-17 18:23:12 +00:00
7098558420
desktop/layer: store aboveFs property and use that 2025-12-16 16:32:37 +00:00
SASANO Takayoshi
59438908de
i18n: more natural Japanese translation (#12649)
* more natural Japanese translation

* src/i18n/Engine.cpp: change パーミッション -> 権限, fix TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD Japanese translation

* src/i18n/Engine.cpp: clang-format processed
2025-12-16 16:13:26 +00:00
cbfdbe9fa1
desktop/popup: fix invalid surface coord 2025-12-16 15:56:04 +00:00
c94a981711
input: simplify mouseMoveUnified a tad 2025-12-16 15:55:54 +00:00
beb1b578e8
input: cleanup sendMotionEventsToFocused() 2025-12-16 15:18:53 +00:00
c5beecb2c3
desktop/popup: minor improvements 2025-12-16 15:18:53 +00:00
Lichie
6e09eb2e6c
desktop/windowRules: fix disabling binary window rules with override (#12635) 2025-12-15 22:19:13 +00:00
Mason Davy
6b491e4d6b
core/compositor: remove a monitor reset on cleanup (#12645)
I've tested this change with different modes from the monitor default
and validated that dpms still works, at least on my machine. If there's
a good reason why this exists, feel free to correct me, but this helps
get us closer to a flicker-free experience.
2025-12-15 21:37:48 +00:00
4036c37e55
hyprctl: add nix flag (#12653) 2025-12-15 15:59:08 +00:00
Maximilian Seidler
7ccc57eb7c
animation: migrate PHLANIMVAR from SP to UP (#12486) 2025-12-14 19:46:49 +00:00
jmanc3
e4a8f2b14f
renderer: add zoom with detached camera (#12548) 2025-12-14 19:42:02 +00:00
Luke Barkess
6535ff07c9
anr: don't create for anr dialogs (#12601) 2025-12-14 17:19:35 +00:00
Luke Barkess
05ccbb2f2d
hyprpm: added plugin author (#12594) 2025-12-14 17:16:58 +00:00
09e195d1f2
compositor: fix isPointOnReservedArea 2025-12-13 13:55:49 +00:00
fd5e790d08
compositor: return nullptr when cursor is outside of a maximized windows' box 2025-12-13 13:55:48 +00:00
Tom Englund
69db0bcae6
compositor: early return on no monitor (#12637)
getMonitorFromVector can return nullptr on empty m_monitors, as such is
the case when the compositor is going down and a surface exist. return
early in vectorToWindowUnified if that happends.
2025-12-12 12:47:56 +00:00
Tom Englund
8dfdcfb353
compositor: dont try to focus unmapped window (#12629)
* compositor: dont try to focus unmapped window

if lastwindow is unmapped it hits getWindowInDirection and nullptr
derefs window->m_workspace. and coredumps. triggered by dual monitor and
one client on each surface with a combination of animation and
killactive / movefocus keybind.

* keybindmgr: use newly added aliveAndVisible()

use newly added aliveAndVisible() over visible()
2025-12-11 23:59:47 +00:00
Dominick DiMaggio
5700736505
cm: handle CM for SDR content with cm=hdr, cm_sdr_eotf=2 (#12127) 2025-12-11 23:50:57 +00:00
Tom Englund
75f6435f70
window: only damage floating on clamped size change (#12633)
currently it damage the entire window if its floating and not x11 nor
fullscreen meaning damage isnt working at all for floating. im tracing
this back to a364df4 where the logic changed from damaging window only
if size was being forced to now unconditonally doing it.

change clampWindowSize to return as a bool if size changed and only
damage window if it got clamped.
2025-12-11 18:54:43 +00:00
Vaxry
5dd224805d
desktop/view: use aliveAndVisible for most things (#12631) 2025-12-11 16:29:26 +00:00
2ca7ad7efc
ci: disable comments for members 2025-12-11 12:40:02 +00:00
9aa313402b
protocols/datadevice: avoid double leave
ref https://github.com/hyprwm/Hyprland/discussions/12494
2025-12-11 00:50:45 +00:00
Maximilian Seidler
1ff801f5f3
Nix: fix glaze build for CI and devShell (#12616) 2025-12-11 00:32:51 +00:00
Tom Englund
3cf6dfd7e6
opengl: default initialize m_capStatus (#12619)
ubsan reports under wonky situation a runtime error of uninitialized
value lookups because of m_capStatus isnt initialized. so default
initialize it.

OpenGL.cpp:3331:26: runtime error: load of value 190, which is not a valid value for type 'bool'
2025-12-11 00:32:11 +00:00
EvilLary
f58c80fd39
monitor: remove monitor from list on disconnect before unsafestate (#12544) 2025-12-09 22:30:35 +00:00
Aureus
6712fb954f
cmake: only use system glaze package if above version 6.0.0 (#12559) 2025-12-09 12:44:02 +00:00
efe665b455
protocols/compositor: fix null deref on unassigned surface image desc
ref #12603
2025-12-08 22:49:53 +00:00
Vaxry
920353370b
desktop: cleanup, unify desktop elements as views (#12563) 2025-12-08 15:04:40 +00:00
Hasan Arthur Altuntaş
834f019bab
cmake: fail if scripts/generateShaderIncludes.sh fails (#12588) 2025-12-08 13:49:23 +00:00
a5b7c91329
ci: run pr comment in target 2025-12-07 21:05:10 +00:00
byddha
916e5d1aea
renderer/cm: make needsHDRupdate per-monitor state (#12564)
Co-authored-by: drzbida <55928036+drzbida@users.noreply.github.com>
2025-12-07 20:47:27 +00:00
Matias Paavilainen
9584b2d40e
i18n: Added Finnish translations (#12505)
* desktop/overridableVar: improve performance

drop usage of ::map which sucks performance-wise

* Added Finnish translations

* Revised translations, and fixed html tags.

---------

Co-authored-by: Vaxry <vaxry@vaxry.net>
2025-12-07 20:47:20 +00:00
Dominick DiMaggio
532ca053d6
renderer/cm: higher-quality tonemapping (#12204) 2025-12-07 17:58:49 +00:00
Nikolai Nechaev
ca99e8228c
internal/start: More careful signal handling (#12573)
- Take out signal set up into a subroutine;

- Use `sigaction` instead of `signal` for consistent behavior across UNIX platforms;

- Enable a warning when a signal handler set up fails;

- Don't do anything to SIGKILL, since it cannot be handled.
2025-12-07 17:53:24 +00:00
Khing
8ca40479a7
desktop: Update Exec command for UWSM Hyprland desktop entry (#12580)
* Update Exec command for UWSM Hyprland desktop entry

This is from the comment in https://github.com/hyprwm/Hyprland/pull/12484 

https://github.com/hyprwm/Hyprland/pull/12484#issuecomment-3621979533

* Update hyprland-uwsm.desktop dumb me
2025-12-07 17:48:14 +00:00
c26e91f074
ci: fix yaml file 2025-12-07 17:29:07 +00:00
Nikolai Nechaev
76ac655c9e
CI: add the start PR label for start-hyprland (#12574) 2025-12-07 10:49:12 +00:00
f8d5aad1a1
tests: fix a test case 2025-12-06 12:42:26 +00:00
b8bb5e9bde
renderer: avoid crash on arrangeLayers for an empty mon 2025-12-06 11:34:04 +00:00
vaxerski
7797deb935 [gha] Nix: update inputs 2025-12-06 11:33:40 +00:00
d3c9c54b79
layouts: fix maximize size 2025-12-06 11:32:01 +00:00
norinorin
cedadf4fdc
cmake: fix XKBCOMMON variable typo (#12550) 2025-12-06 00:48:38 +00:00
Nikolai Nechaev
222dbe99d0
keybinds: fix previous workspace remembering (#12399)
* swipe: Fix previous workspace remembering in workspace gesture

Fixes a bug that previous workspace does not exist after swiping to a workspace

* tests: Test that `workspace previous` works after workspace gesture

* moveActiveToWorkspace: remember previous workspace unconditionally
2025-12-05 20:43:30 +00:00
EvilLary
ebe74be75a
dispatcher: include mirrors of monitor in dpms (#12552)
* dispatcher/dpms: include mirrors

* use m_realMonitors instead
2025-12-05 20:29:39 +00:00
afeda6cee6
ci: add new pr comment workflow 2025-12-05 20:29:02 +00:00
6a1daff5f3
example/config: use hyprshutdown if available 2025-12-05 17:48:45 +00:00
Vaxry
016eb7a23d
start: init start-hyprland and safe mode (#12484) 2025-12-05 15:40:03 +00:00
Zeide
ec6756f961
cmake: add missing space (#12549) 2025-12-05 15:03:10 +00:00
Vaxry
9264436f35
desktop: rewrite reserved area handling + improve tests (#12383) 2025-12-05 14:16:22 +00:00
SAM
d5c52ef58e
renderer/cm: fix typo on color simage description op (#12551)
fix: typo on color simage description op
2025-12-05 14:11:52 +00:00
Gilang ramadhan
52b3c8cbc6
i18n: add Indonesian translations (#12468) 2025-12-04 20:42:13 +00:00
Aivaz Latypov
279a07c2ce
i18n: add Tatar translations (#12538) 2025-12-04 18:06:17 +00:00
Hleb Shauchenka
17ae3fb704
pointer: apply locked pointer workaround only on xwayland (#12402) 2025-12-04 18:05:50 +00:00
Björn Kettunen
43ed0db3b3
cmake: track dependencies in pkgconfig file (#12543)
Depedencies where not tracked in the pkgconfig leading to programs
who scan dependencies using it to fail/not track them.

I noticed this while building Hyprland on openSUSE where the -devel
package didn't include the dependencies it once had when Meson was
used previously.
2025-12-04 18:04:20 +00:00
jmanc3
38f912c401
renderer: remove unnecessary assert from renderRoundedShadow (#12540) 2025-12-04 18:03:12 +00:00
Vaxry
9cd070fd31
hyprpm: check for abi strings in headersValid (#12504)
---------

Co-authored-by: Virt <41426325+VirtCode@users.noreply.github.com>
2025-12-04 18:00:15 +00:00
Vaxry
d9657a95cb
hyprctl: use new hyprpaper ipc format (#12537)
---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2025-12-04 17:59:47 +00:00
9b1891e476
desktop/overridableVar: fix possible crash 2025-12-03 22:43:26 +00:00
Vaxry
93e5e92b0a
crashReporter: cleanup code (#12534)
various code cleanups, reorders, move off of global NS
2025-12-03 16:01:45 +00:00
UjinT34
3cf0280b11
renderer: add quirks:prefer_hdr to fix HDR activation for some clients (#12436) 2025-12-03 01:30:43 +00:00
Vaxry
2cadc8abab
welcome: init welcome manager (#12409)
---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2025-12-02 22:26:43 +00:00
littleblack111
f82a8630d7
desktop/rules: tag static rule being ignored (#12514)
* chore: apply exec rules after removal and use CWindowRule

* refactor: unregister exec rules after applying them

Remove the unused toRemove vector and defer unregistering exec rules
until after applyStaticRule/applyDynamicRule so exec rules are applied
before being removed from the rule engine.
2025-12-01 16:47:59 +00:00
Honkazel
bb963fb002
protocols/cursor-shape: impl version 2 (#12270) 2025-11-30 15:05:31 +00:00
EvilLary
f11cf6f1de
renderer: fix uv sufrace calc with scales < 1 (#12481) 2025-11-29 21:16:49 +00:00
574ee71d56
desktop/overridableVar: improve performance
drop usage of ::map which sucks performance-wise
2025-11-29 17:17:24 +00:00
ふゆ
7e1e24fea6
i18n: fix typos/unnatural spellings in french translation (#12443) 2025-11-27 22:51:34 +00:00
Vaxry
68eecf61cd
desktop/windowRule: return reset props from resetProps and recheck them (#12458)
fixes #12457
2025-11-27 21:14:24 +00:00
f9742ab501
keybinds: restore pointer warp on switch
ref https://github.com/hyprwm/Hyprland/pull/12033#pullrequestreview-3516413924
2025-11-27 21:10:01 +00:00
sadbhav
e42185b83d
i18n: add Nepali translations (#12451) 2025-11-27 19:34:51 +00:00
SASANO Takayoshi
a51918fd27
src/protocols/types/DMABuffer.cpp: <sys/ioctl.h> is required for ioctl(), not only linux (#12483) 2025-11-27 15:52:04 +00:00
Hiroki Tagato
379ee99c68
window: implement CWindow::getEnv() for BSDs (#12462)
Some BSDs provide procfs to access kernel information. However, BSDs'
procfs does not provide information on a process' environment
variables. Instead sysctl(3) function is usually used for system
information retrieval on BSDs.
2025-11-26 22:12:50 +00:00
Tom Englund
4036e35e73
protocols/lock: fix missing output enter on surface (#12448) 2025-11-26 22:12:17 +00:00
Luke Barkess
d21f2e5729
config: move config parsers to VarList2 (#12465) 2025-11-26 22:11:29 +00:00
SASANO Takayoshi
4e5a284bc4
CMakeLists.txt: improve libudis86 and librt detection (#12472) 2025-11-26 22:10:02 +00:00
Tom Englund
210930bef9
buffers: revert state merging (#12461)
8e8bfbb0b1 added fifo and merged non
buffer states before comitting them, something about certain xwl non
buffer commits expects a commit to happend and causes regressions as in
low fps.
2025-11-25 22:51:51 +00:00
Nikolai Nechaev
40d8fa8491
compositor: Configurable behavior when window to be focused conflicts with fullscreen (#12033)
Renames `misc:new_window_takes_over_fullscreen` into
`misc:on_focus_under_fullscreen` and implements the following behavior:

- By default, when a tiling window is being focused on a workspace where
  a fullscreen/maximized window exists, respect
  the `misc:on_focus_under_fullscreen` config variable.
2025-11-25 22:44:26 +00:00
Tim
1c1746de61
i18n: add Croatian translations (#12374) 2025-11-25 14:21:45 +00:00
vaxerski
619e9d285b [gha] Nix: update inputs 2025-11-25 13:41:42 +00:00
Tomáš Zierl
ec3b3403e7
i18n: add Czech translations (#12428) 2025-11-25 13:40:25 +00:00
703394affb
protocols/workspace: avoid crash on inert outputs 2025-11-25 13:35:25 +00:00
Vaxry
fe6a855bbb
renderer: stop looping over null texture surfaces (#12446)
fixes #12445
2025-11-24 23:48:18 +00:00
EvilLary
475e87b351
windowrules: fix persistent_size not applying (#12441) 2025-11-24 23:48:10 +00:00
SASANO Takayoshi
3d7ea9c02f
CrashReporter.cpp: fix stderr conflict (#12440) 2025-11-24 20:11:15 +00:00
bea4dev
2b0fd417d3
animation: improve animations on multi refresh rate monitors (#12418) 2025-11-23 15:48:15 +00:00
OCbwoy3
56904edbd2
i18n: add Latvian translations (#12430) 2025-11-23 12:37:19 +00:00
Luke Barkess
e584a8bade
config: added locale config option (#12416) 2025-11-22 13:59:36 +00:00
2ac9ded2ac
ci: fix ai workflow for the nth time 2025-11-22 13:56:41 +00:00
abb2f7ee6f
renderer: fix render_unfocused 2025-11-21 18:48:45 +00:00
79a2781923
protocols/workspace: fix crash in initial group sending
fixes #12419
2025-11-21 14:46:05 +00:00
d66c9222b0
CI/AI translate: expose output 2025-11-21 14:17:16 +02:00
Pastilhas
b5a2ef77b7
i18n: add Português (Portugal) translation (#12328) 2025-11-20 23:37:00 +00:00
Hleb Shauchenka
c5d45b7653
hyprctl: fix no_vrr prop ref (#12410) 2025-11-20 22:40:39 +00:00
Vaxry
c249a9f4b8
windowrules: fix group rule recalcs (#12403) 2025-11-20 16:57:31 +00:00
Raúl Salinas
00cce1c8ff
i18n: improve Spanish translations for clarity and consistency (#12378)
* i18n: improve Spanish translations for clarity and consistency

Improve the Spanish translation strings in Engine.cpp by:

- Refining ANR (Application Not Responding) dialog to use "La aplicación"
  and change the terminate option to "Forzar cierre" for clarity
- Standardizing permission prompts with "¿Deseas...?" for consistency
- Rewording keyboard permission prompt: "permitir su uso" is clearer than
  "permitir su funcionamiento"
- Adding explicit reference to "permisos" in the persistence hint
- Improving Wayland app identification: "ID de cliente de Wayland" instead
  of "wayland client ID {wayland_id}"
- Enhancing environment variable notification with better punctuation and
  clarity ("gestionarse externamente" vs "estar gestionada externamente")
- Simplifying hyprland-guiutils message with direct, concise wording
- Rewriting failed assets lambda to be more natural and add context about
  distribution packagers
- Improving monitor-related messages with clearer tone and better sentence
  structure (monitor layout, mode fallback, auto-scale)
- Using "shader" instead of "sombreador" (more common in Spanish tech)
- Changing "10-bit" to "10 bits" for proper Spanish plural form

Overall tone improvements include consistent use of "tú" forms and clearer,
more natural Spanish phrasing throughout the user-facing messages.

* i18n: update Spanish error messages for clarity and support
2025-11-20 12:32:58 +00:00
6b8e3358d6
CI/AI translate: change path filter action 2025-11-20 14:31:11 +02:00
MithicSpirit
80b96a3166
hyprctl: show contentType in activewindow (#12214) 2025-11-20 12:01:07 +00:00
Aaron Blasko
f9d1da6667
i18n: slight update to it_IT translations (#12372) 2025-11-19 23:37:55 +00:00
7532115318
rule: nuke parseRelativeVector 2025-11-19 19:08:27 +00:00
Tetrapak
1c29d6b1ba
i18n: add Slovenian translation (#12369) 2025-11-19 18:33:44 +00:00
MyNameIsKitsune
d0503bea43
i18n: add Ukrainian translation (#12370) 2025-11-19 18:33:22 +00:00
fbb31503f1
CI/AI translate: fix yet again 2025-11-19 10:13:54 +02:00
xyrd
9f02dca8de
i18n: add Norwegian Bokmål translations (#12354) 2025-11-19 01:00:13 +00:00
Kamikadze
6a8d306992
examples: fix example config (#12394) 2025-11-18 17:54:54 +00:00
e4b40abce6
windowrules: bring back windowUpdateRules 2025-11-18 16:49:18 +00:00
KAGEYAM4
9495f989b4
hyprpm: remove -nn flag and make notification behaviour more consist… (#11272)
* [hyprpm] Remove -nn flag and make notification behaviour more consistent.

Before -> -nn turns on -n explicitly, and many notify() are ran without checking the flag.
After -> warning and error notification will always work, -n will only give visual confirmation that plugin loaded successfully, eye candy.

* [hyprpm] Add -nn breaking change fallback to nofity users.

Added deprecation warning for the --notify-fail flag.
2025-11-18 16:33:02 +00:00
fazzi
2c9c4d0905
windowrules: fix matching against xdgTag (#12393) 2025-11-18 16:32:33 +00:00
e15409bbeb
CMake: fix GIT_COMMIT_MESSAGE parsing 2025-11-18 17:50:10 +02:00
Guillermo Tomás Fernández Martín
312073ce79
CMake: add min version for xkbcommon 2025-11-18 17:46:14 +02:00
edc311544a
dwindle: Revert rework split logic to be fully gap-aware (#12047)
This reverts commit 151b5f6978.

Fixes #12380
2025-11-18 00:59:21 +00:00
37fe7b2efd
config: export version variable for versioned configs
fixes #12274
2025-11-17 18:43:04 +00:00
Vaxry
c2670e9ab9
windowrules: rewrite completely (#12269)
Reworks the window rule syntax completely

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2025-11-17 18:34:02 +00:00
95ee08b340
ci: fix translation ci again 2025-11-17 17:53:34 +00:00
Ali Ebadi
ff6d771be0
i18n: add Persian translations (#12361) 2025-11-17 17:47:35 +00:00
64e4e913e1
ci: fix translator ci 2025-11-17 16:57:17 +00:00
Martijn
ad52ba9c13
i18n: Add Dutch translations (#12326) 2025-11-17 13:34:57 +00:00
526aa1d020
CI/Nix: simplify cache config 2025-11-17 14:47:30 +02:00
5f0575737f
CI/AI translate: only run on src/i18n 2025-11-17 14:46:21 +02:00
27Onion Nebell
4695f85829
i18n: add Simplified Chinese translations (#12332) 2025-11-17 12:39:06 +00:00
Kosa Matyas
1796dbcdc3
i18n: Add hungarian translations (#12346) 2025-11-17 12:38:57 +00:00
Jochim
e354066945
groupbar: fix rounding logic for edge cases (#12366) 2025-11-17 12:13:29 +00:00
fufexan
2b14f27ca8 [gha] Nix: update inputs 2025-11-17 07:59:34 +00:00
68c23fbdaf CI: drop no_pch and make default, drop noxwayland 2025-11-17 09:58:14 +02:00
484d87d469 CI: drop meson build, simplify c-f check 2025-11-17 08:54:47 +02:00
cefa63c2af meson: drop 2025-11-17 08:54:47 +02:00
nnra
9d67511871
i18n: add Serbian Translations (#12341) 2025-11-16 23:59:05 +00:00
Darkiu1337
5265fa3be8
i18n: add pt_BR translations (#12351) 2025-11-16 23:58:54 +00:00
Antarip Barman
dfb4dcd55c
i18n: add Assamese translations (#12356) 2025-11-16 23:58:43 +00:00
Abdul
9d02fe9c23
i18n: add Arabic (ar) translations (#12352) 2025-11-16 23:58:23 +00:00
Aliaksiej
76edcfc66c
i18n: add Belarusian language (#12358) 2025-11-16 23:57:37 +00:00
Eren
11451d68b7
i18n: add Turkish translations (#12331) 2025-11-16 21:40:47 +00:00
3534dbdb89
ci: translation note fix 2025-11-16 21:19:36 +00:00
Lone Detective
6e2fe103bc
i18n: add Malayalam translations (#12345) 2025-11-16 21:18:17 +00:00
bea4dev
7910bc42af
renderer: fix fractional scale artifacts (#12287) 2025-11-16 21:17:05 +00:00
Aivaz Latypov
0770494ddf
i18n: add Russian translations (#12335) 2025-11-16 20:56:00 +00:00
49c0c97c5a
CI: fix translator 2025-11-16 20:55:15 +00:00
e948445f6e
CI: minor translation fixes 2025-11-16 20:22:07 +00:00
Álvaro Salcedo García
7a6177532b
i18n: add Spanish translations (#12334) 2025-11-16 20:09:08 +00:00
f0de61ca21
CI: run translator in pull_request_target for comment access 2025-11-16 19:34:36 +00:00
c02a6184d3
CI: add a fail note to translation ci 2025-11-16 19:32:26 +00:00
15b4b1dd91
ci: fix comment workflow for translations 2025-11-16 19:01:22 +00:00
a6b877fec2 CMake: prepopulate GIT vars from env 2025-11-16 20:33:01 +02:00
d2d1613e4f Nix: fix GIT_* env vars 2025-11-16 20:33:01 +02:00
Aditya An1l
c7e14ecd30
i18n: Add Hindi translations (#12324) 2025-11-16 18:28:50 +00:00
Vaxry
9321f52e07
CI: Add AI translation checks (#12342)
Adds AI-driven translation checks for translation MRs. Uses GPT-5-Mini.

Runs on a new translation MR, or can be manually triggered by me with "ai, please recheck"
2025-11-16 18:28:16 +00:00
Giacomo Zama
b04e8e00b0
cursor: fix m_cursorSurfaceInfo not being updated while a cursor override is set (#12327) 2025-11-16 17:43:55 +00:00
Lumine
5b373ea9f5
i18n: add French translations (#12330) 2025-11-16 17:10:40 +00:00
Virt
d52639fdfa
i18n: init german translations (#12323) 2025-11-16 15:54:43 +00:00
Vaxry
e616e595ae
i18n: init localization for ANR, Permissions and Notifications (#12316)
Adds localization support for en, it, pl and jp

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
Co-authored-by: Aaron Blasko <blaskoazzolaaaron@gmail.com>
2025-11-16 14:51:14 +00:00
Jochim
cb47eb1d11
deco/groupbar: add groupbar blur (#12310) 2025-11-16 12:23:45 +00:00
Vaxry
9b006b2c85
plugin/hook: disallow multiple hooks per function (#12320)
this was never safe. After recent changes, it's become even less so. Just disallow it.

ref #11992
2025-11-16 12:01:48 +00:00
Alexandru Spînu
b35f78431f
cursor: ensure cursor reset on changed window states (#12301) 2025-11-15 19:23:32 +00:00
Lucas Ritzdorf
b62ab4b578
cmake,meson: fix inclusion of GPG info in Git commit info (#12302)
This manifested for me as a failure to build plugins with `hyprpm`, but
the root cause was GPG data getting incorporated into `src/version.h`,
like so:

```c
#define GIT_COMMIT_MESSAGE "gpg: Signature made Sun 09 Nov 2025 03:31:36 PM PST
gpg:                using EDDSA key E26A4A2AB9676F54149F8EAA665806380871D640
gpg: Can't check signature: No public key
version: bump to 0.52.1"
```

This affected both `GIT_COMMIT_MESSAGE` and `GIT_COMMIT_DATE`, since
those are generated via `git show` (which can generate that extra GPG
info if the user's personal Git config sets `log.showSignature`).

See: https://github.com/hyprwm/Hyprland/discussions/12282
2025-11-15 19:23:19 +00:00
Hiroki Tagato
43527d3634
internal: fix crash at startup on FreeBSD (#12298)
Hyprland at the latest commit crashes at starting up on FreeBSD with
SIGSEGV. Checking the validity of g_pXWayland->m_wm before calling
updateWorkArea() appears to fix the issue.
2025-11-13 22:06:34 +00:00
Hiroki Tagato
55a93b8a52
internal: put Linux-only header behind ifdef (#12300) 2025-11-13 22:06:25 +00:00
Vaxry
64ee8f8a72
layout: include reserved area in float fit (#12289)
Ref https://github.com/basecamp/omarchy/issues/3327
2025-11-13 00:08:04 +00:00
b77cbad502
screencopy: fix possible crash in renderMon() 2025-11-12 22:43:46 +00:00
Luke Barkess
308226a4fc
config/keybinds: add a submap universal keybind flag (#12100) 2025-11-11 22:59:21 +00:00
bea4dev
ee2168c665
renderer/ime: fix fcitx5 popup artifacts (#12263) 2025-11-11 20:43:43 +00:00
usering-around
c330d4334f
renderer: fix noscreenshare layerrule popups (#12260) 2025-11-11 20:42:53 +00:00
Tom Englund
cadf922417
presentation: only send sync output on presented (#12255)
as protocol states there is two events. 'presented' or 'discarded'.

wp_presentation_feedback::sync_output
As presentation can be synchronized to only one output at a time, this event tells which output it was.
This event is only sent prior to the presented event.
2025-11-11 20:00:59 +00:00
Chudnikov Alexander
ac8edc6a80
internal: fix subtractWindow typo for POSYSTR (#12259)
This type really pisses me off
2025-11-11 16:11:54 +00:00
Dominick DiMaggio
b2ea6b010c
renderer: Allow DS for surfaces with inert subsurfaces (#12133) 2025-11-11 12:18:15 +00:00
0b1d690676
flake.nix: update guiutils and override hw-s 2025-11-10 08:15:26 +02:00
2931184921
CI/release: populate git info (#12247) 2025-11-09 20:50:56 +00:00
Aurelle
0bd11d5eb9
protocols/layershell: do not raise protocol error if layer surface is not anchored (#12241) 2025-11-09 15:59:14 +00:00
nikromen
06b37c3907
protocols/outputMgmt: fix wlr-randr by defering success event until monitor reloads (#12236)
wlr-randr disconnects immediately after receiving success event, but
before applying the monitor configuration. This causes the state to be
lost when performMonitorReload() is called.

By postponing the success event until the call of the hook
monitorLayoutChanged we ensure the configuration to remain valid during
the reload process.
2025-11-08 23:45:53 +00:00
522edc8712
meson: fix version.h install location 2025-11-07 21:08:40 +02:00
f56ec180d3
version: bump to 0.52.0 2025-11-07 16:39:26 +00:00
UjinT34
fd50e78bc9
render/cm: change non_shader_cm ignore behavior and set default to it (#12210) 2025-11-07 15:58:25 +00:00
Vaxry
061981201d
core: qtutils -> guiutils (#12231)
* core: qtutils -> guiutils

* nix: qtutils -> guiutils

* flake.lock: update

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2025-11-07 15:48:13 +00:00
Dominick DiMaggio
3fc8cb828c
cm: follow preferred srgb eotf for screencopy (#12230) 2025-11-07 14:02:26 +00:00
Amadej Kastelic
1ca6058bda
chore: fix non-relative imports (#12228) 2025-11-06 20:32:08 +00:00
bea4dev
ca4b68e425
renderer: fix fractional scale artifact (#12219) 2025-11-06 19:18:16 +00:00
Tom Englund
8e8bfbb0b1
protocols: add Fifo-v1 and commit-timing-v1 (#12052)
* protocols: add Fifo-v1

introduce fifo-v1

* fifo: only present locked surfaces

dont present to unlocked surfaces and commit pending states from the
fifo protocol.

* fifo: cformat

cformat

* protocols: add committiming and surface state queue

introduce CSurfaceStateQueue and commit-timing-v1

* fifo: schedule a frame if waiting on barrier

if we are waiting on a barrier the state doesnt commit until the next
refresh cycle meaning the monitor might have no pending damage and we
never get onPresented to unlock the barrier, moment 22. so schedule a
frame.

* fifo: properly check monitor intersection

check for m_enteredoutputs or monitor intersection if client hasnt bound
one yet, and dont fifo lock it until the surface is mapped.

* buffer: try to merge states before committing them

try to merge states before committing them meaning way less churn and
surface commits if a surface sends multiple small ones while we wait for
buffer readyness from either fifo locks or simply fences.

* buffer: dont commit states past the buffer

certain changes are relative to the buffer attached, cant go beyond it
and apply those onto the next buffer.

* buffer: set the lockmask directly

cant use .lock since the state hasnt been queued yet, set the lockmask
directly when exporting buffer fence.

* fifo: dont fifo lock on tearing

dont fifo lock on tearing.

* buffer: queue the state directly

queue the state directly and use the .lock function instead of directly
modify the lockMask on the state.

* buffer: revert creating texture at commit time

fifo barriers introduces such long wait that upon commit time a
race happends with current xdg configure implentation that the buffer
and image is actually destroyed when entering commitState, doing it at
buffer creation time with EGL_PRESERVED_KHR means it sticks around until
we are done. so revert 82759d4 and 32f3233 for now.

* buffer: rename enum and lockreasons

eLockReason and LOCK_REASON_NONE.

* fifo: workaround direct scanout lock

workaround cursor commits causing fifo to get forever locked, this
entire thing needs to be worked out.
2025-11-06 13:25:49 +00:00
Alvaro Parker
c757fd375c
compositor: block parent window interaction when modal dialog children window is open (#12057) 2025-11-06 00:06:31 +00:00
vaxerski
46b71eda64 [gha] Nix: update inputs 2025-11-04 15:15:08 +00:00
André Silva
d82538c69f
protocols/dmabuf: handle null pointer in CLinuxDMABufV1Protocol::resetFormatTable (#12207) 2025-11-04 15:13:50 +00:00
Matteo Golinelli
8e9add2afd
sessionlock: fix crash when sendScale is called on a disconnected (#12171) 2025-10-31 00:15:18 +00:00
Vaxry
5e6cec962c
cursor: refactor override handling (#12166)
much cleaner and more reliable. Should fix https://github.com/hyprwm/Hyprland/issues/12088
2025-10-31 00:14:08 +00:00
Vaxry
6ade4d58ca
layout: fit floating window on toggle to float (#12139) 2025-10-29 23:21:28 +00:00
83a0a62004
protocols/core: round dnd drop surface box 2025-10-29 17:20:44 +00:00
Dominick DiMaggio
ff50dc36e9
renderer/cm: allow gamma 2.2 instead of sRGB EOTF (#12094) 2025-10-29 12:53:42 +00:00
jmanc3
ce9787b3f4
xwayland: set _NET_WORKAREA property (#12148) 2025-10-29 11:24:34 +00:00
9eb82774e5 Nix: build hyprtester along with hyprland 2025-10-29 12:18:29 +02:00
a2f48ea418 CMake: allow building hyprtester without running tests 2025-10-29 12:18:29 +02:00
309c3c7848
Nix/tests: wl-copy -> wl-clipboard 2025-10-27 23:49:49 +02:00
0907fdf49c
CI/release: run cmake configure 2025-10-27 23:47:35 +02:00
431325ff0c
config/rule: don't populate ID field for automatically id-managed workspaces 2025-10-27 21:29:35 +00:00
40831a90a0
Nix/tests: add wl-copy 2025-10-27 23:25:54 +02:00
b186d3bf1b
pass/surface: check for LS size anim for misaligned fractional 2025-10-27 17:22:04 +00:00
560c53d87d
monitor/dpms: fix possible invalid state
If dpms gets immediately re-enabled, a commit could fail, not schedule any frames anymore, and the monitor would be stuck off. Fix this by adding a timer to retry if commit fails.

ref #12045
2025-10-27 13:34:14 +00:00
fd42e9d082
CI/release: remove generateVersion call
Addresses https://github.com/hyprwm/Hyprland/pull/12110#issuecomment-3442583784
2025-10-26 21:27:49 +02:00
Dominick DiMaggio
17d0d696be
screencopy: wait longer to re-enable DS (#12135) 2025-10-26 18:57:20 +00:00
JS Deck
88e34d7dd2
IME: do not share keys/mods states from grabbed keyboards with ime keys/mods (#11917) 2025-10-26 18:54:48 +00:00
05aa4e1c54
compositor: check for monitor layout issues post rule apply
fixes #12108
2025-10-26 13:52:09 +00:00
748d2f656e
xdg-shell: implement invalid parent errors 2025-10-26 12:34:35 +00:00
Tom Englund
6ea4769b39
EGL: minor egl changes (#12132)
* opengl: use EGLint and we dont have to cast data

use EGLint in the attrib array and we dont have to cast the resulting
data.

* opengl: add linear to correct vector

drop empty check, what if we get mods that isnt linear. then it wont be
added, also add it to the result vector that we actually return.
2025-10-25 20:36:02 +01:00
Virt
72cbb7906a
layer-shell: fix fullscreen alpha when changing layers (#12124)
* layer-shell: fix fullscreen alpha when changing layers

* this is intended

* ooops

* ooops #2
2025-10-25 18:53:01 +01:00
Tom Englund
b6f946991d
meson: disable lto (#12129)
seems to accidently got enabled again in 019589e
2025-10-25 15:19:47 +01:00
ccos89
b10b966000
screencopy: fix missing XBGR2101010 format with screencopy_force_8b (#12125)
Adds missing DRM_FORMAT_XBGR2101010 to screencopy_force_8b that leads to
"no more input formats" Pipewire error for monitors set to 10-bit color
depth/where currentFormat is XBGR2101010.

Fixes implementation of #11623
Fixes hyprwm/xdg-desktop-portal-hyprland#270
Fixes hyprwm/xdg-desktop-portal-hyprland#102
2025-10-25 11:57:46 +01:00
Vaxry
da04afa44e
surface: fix xwayland zero scaling damage calcs (#12123) 2025-10-24 22:19:21 +01:00
Filip Mikina
34812c33db
hyprctl: include color management presets and sdr information (#12019)
* move string parsing for eCMType to its own namespace, similar to how
`src/protocols/types/ContentType.cpp` is done
* expose cm type and sdr settings in `hyprctl monitors`, format floats
to .2f
2025-10-24 20:18:39 +01:00
ItsOhen
117e38db35
cmake: fix git lookup for when building out of srcdir(#12116) 2025-10-24 19:13:38 +01:00
crossatko
151b5f6978
dwindle: rework split logic to be fully gap-aware (#12047) 2025-10-24 19:01:05 +01:00
vaxerski
aa5a239ac9 [gha] Nix: update inputs 2025-10-23 19:51:54 +00:00
nnra
019589e23f
build: replace generateVersion.sh (#12110)
* Implemented the CMake version of generateVersion.sh

* Made version.h.in compatible with the new build system and included version.h in helpers/MiscFunctions.cpp

* Deleted the scripts/generateVersion.sh as it's no longer needed

* Updated meson.build to match the new workflow

* Added an empty line between includes and namespaces that I accidentally removed
2025-10-23 20:50:32 +01:00
Vaxry
057695bc3f
desktopAnimationMgr: don't set fade 0 for members of a fs group (#12091)
fixes a flash of opacity that shouldnt be there
2025-10-22 11:32:42 +01:00
Vaxry
892f642f58
plugins: incorporate hyprdep ABI into plugin info (#12001) 2025-10-21 22:47:50 +01:00
d560c26419
internal: fix cf 2025-10-21 22:47:06 +01:00
02b0c563f3
xwm: attempt to guess mime in sendData for DnD 2025-10-21 19:11:18 +01:00
4926332c37
monitor: remove spammy trace log 2025-10-21 19:11:18 +01:00
Mozzarella32
46dab01bcc
renderer: add more uniforms to the screen shader (#11986)
These are: pointer_shape from the cursor-shape-v1 protocol prepared for v2, along with left_ptr...bottom_right_corner and killing (Hyprland specific)
           pointer_shape_previous with
           pointer_switch_time to blend between shapes
           pointer_size scaled size as used by the normal cursor
           pointer_pressed_positions[32] with
           pointer_pressed_times[32] and
           pointer_pressed_killed(32 bits) for click/touch animations and if they killed something
           pointer_inactive_timeout with
           pointer_last_active to smoothly fade the pointer out
           pointer_hidden to hide it when the cursor is hidden (excluding by cursor:invisible as this config value can be used to turn off the normal cursor, which is useful when drawing it with the screen shader)
2025-10-20 12:22:50 +01:00
vaxerski
474cd004df [gha] Nix: update inputs 2025-10-20 11:14:45 +00:00
0rtz
a4200acfa6
input: fix refocus on grab dismiss (#12014) 2025-10-20 12:13:27 +01:00
MithicSpirit
59ff7b2f89
dispatchers: add forceidle (#11922)
The forceidle dispatcher resets all ext-idle-notify timers as if the
user had been idle for the specified number of seconds. If a
notification has already fired, but would now be set with a nonzero
delay, then it is reset. Conversely, if a timer has not yet fired, but
would now be set to a nonpositive delay, then it is immediately fired.
This process ignores any existing inhibitors, but timers are otherwise
reset as normal if any new inhibitors are created or destroyed.
2025-10-19 13:54:27 +02:00
Vaxry
ba077d8ff0
renderer: clean up surface UV size calcs, fix issues (#12070) 2025-10-19 02:56:55 +02:00
Bang Lee
39d62e1487
protocols: fix output power protocol not sending mode confirmation (#12072)
Use setDPMS() instead of directly manipulating m_dpmsStatus to ensure the dpmsChanged event fires and protocol
clients receive mode change confirmation via sendMode().
2025-10-18 20:44:55 +02:00
Mozzarella32
6607c6440d
renderer: add 1fv and 2fv uniform support (#12080) 2025-10-18 13:34:33 +02:00
Mozzarella32
f3e13193a6
timer: constify methods (#12079) 2025-10-18 13:34:07 +02:00
Matteo Golinelli
8164b90bc2
config: fix crash when some configurations include non-integer values (#12056) 2025-10-16 15:33:06 +02:00
36c0477dd0
tests: disable shortcut test for ci 2025-10-16 14:32:26 +01:00
Virt
ab11af9664
ext-foreign-toplevel: remove stale entries when remapping (#12037) 2025-10-15 14:37:39 +02:00
Vaxry
e40873be51
renderer: add cursor:zoom_disable_aa for controlling AA on zoom (#12025) 2025-10-15 14:08:34 +02:00
60529e810d
renderer: round box in damageBox 2025-10-15 00:37:07 +01:00
f324a3a564
functionHook: fix distance check 2025-10-14 19:31:49 +01:00
ee5d05f0fc
hookSystem: fix anchor point for mmap 2025-10-14 13:39:22 +01:00
bbb83317c0 Nix: drop ninja for CMake build 2025-10-13 23:15:18 +03:00
0d6d19b280 Revert "nix: use meson"
This reverts commit d505b33665.
2025-10-13 23:15:18 +03:00
541ef60fd7 CMake: print pch messages based on var 2025-10-13 23:15:18 +03:00
Vaxry
4b55ec6830
windowrules: add modal prop (#12024)
adds a modal prop for targeting modal windows with rules
2025-10-13 13:16:48 +01:00
Richard Potter
7fcaf332e8
layouts: apply [min|max]size window rules to dwindle & master layouts (#11898)
Uses min/max rules in the tiled layouts, akin to pseudotiling
2025-10-13 13:08:40 +01:00
6582f42db8 meson: disable lto explicitly 2025-10-13 09:27:33 +03:00
Virt
ed93643021
core: disable lto for hyprland builds (#11972)
LTO has the tendency to remove functions completely by inlining them, which breaks function hooks.

Force disable LTO to not have plugins break.
2025-10-12 02:06:31 +02:00
ItsOhen
d599513d4a
config: add automatic closing to submaps (#11760)
* Allow submaps to auto reset to parent.

* Really should be a stack instead.

If hyprlang would allow for { } i would be so happy.

* Fixed: Somewhat better way to do it..

Lets you define what submap you want to go to instead.

* squash! Fixed: Somewhat better way to do it..

* God i hate cf..

* Force clang-format on the whole thing..

* Removed {}.

* Added tests

Tests and reset fix.
2025-10-11 02:40:18 +02:00
epsilonshmepsilon
6a01c399a9
input: add option to rotate device input (#11947) 2025-10-10 17:05:51 +02:00
Nikolai Nechaev
da31e82aab
internal: prevent early exit processes from being zombies (#11995)
Prevent `exec`/`exec-once` processes which terminate very early
(before Hyprland declares that it does not want to reap zombies)
from getting stuck as zombie processes.
2025-10-10 17:03:34 +02:00
Tom Englund
32f3233324
dmabuffer: ensure we only create one texture per buffer (#11990)
buffer can be recomitted, when moving texture creation from constructor
to committime it means same buffer recommit can cause a new texture
unless we store it per buffer and return the pointer for it.
2025-10-10 14:13:14 +02:00
2b0926dcd4
tests: disable one test as it fails on ci 2025-10-10 13:11:32 +01:00
Damino
b965fb2a40 flake.lock: bump hyprutils 2025-10-09 08:35:34 +03:00
Tom Englund
82759d4095
buffer: move texture creation to commit time (#11964)
* buffer: move texture creation to commit time

move creating texture away from buffer attach into commitstate in an
attempt to postpone gpu work until its really needed. best case scenario
gpu clocks have ramped up before because we are actively doing things
causing surface states and a commit to happend meaning less visible lag.

* buffer: simplify texture creation

make createTexture return a shared ptr directly, remove not needed
member variables as m_success and m_texture.
2025-10-08 22:25:55 +02:00
Linux User
0dc45b54f3
managers/helpers: add missing includes (#11969)
* managers: include string header

Fixes error `implicit instantiation of undefined template 'std::basic_string<char>'` on llvm/musl

* helpers: include unistd header

Fixes error `use of undeclared identifier 'pipe'` on llvm/musl
2025-10-08 22:24:40 +02:00
Nj0be
ba24547d3d
dispatchers: add set, unset and toggle to fullscreen (#11893)
Add set, unset and toggle to fullscreen
2025-10-08 11:07:55 +01:00
Tom Englund
5ba2d2217b
compositor: make wl_surface::frame follow pending states (#11953)
* compositor: make pending states store frame callbacks

move frame callbacks to pending states so they are only committed in the
order they came depending on the buffer wait for readyness.

* buffer: damage is relative to current commit

ensure the damage is only used once, or we are constantly redrawing
things on state commits that isnt a buffer.

thanks PlasmaPower.

* compositor: move callbacks back to compositor

move SSurfaceStateFrameCB back to compositor in the class
CWLCallbackResource as per request, but still keep the state as owning.

* compositor: ensure commits come in order

if a buffer is waiting any commits after it might be non buffer commits
from the "future" and applying directly. and when the old buffer that
was waiting becomes ready it applies its states and overwrites the
future changes.

move things to scheduleState and add a m_pendingWaiting guard. and
schedule the next pending state from the old buffer commit when done.
and as such it loops itself and keeps thing orderly.
2025-10-07 12:49:38 +01:00
5a20862126
hookSystem: use a full trampo setup for hooks
instead of planting a longjmp at the fn head, make a shortjmp to a launch trampo

this helps with shortjmps that can be in the fn and will break when relocated. They are very unlikely to occur within the first 5 bytes (jmp rel32) but can happen in the first 10 or so (longjmp)

fixes csgo-vk-fix on latest main with release building on gcc / clang
2025-10-07 12:49:36 +01:00
c3747fab56
hookSystem: fix anchoring in seekNewPageAddr()
otherwise we might miss the chance to get an anchor
2025-10-07 01:44:03 +01:00
dc72259a54
core/compositor: revert make wl_surface::frame follow pending states (#11896)
This reverts commit 17e77e0407.

Reverted due to severe performance degradation due to accumulating frame
callbacks
2025-10-06 23:44:47 +01:00
Vaxry
02cda6bebf
systeminfo: log system package versions (#11946) 2025-10-06 23:20:21 +02:00
rfresh2
73f06434a4
keybinds: fix repeat and long press keybinds release (#11863) 2025-10-06 21:10:56 +02:00
Tom Englund
17e77e0407
core/compositor: make wl_surface::frame follow pending states (#11896)
* compositor: make pending states store frame callbacks

move frame callbacks to pending states so they are only committed in the
order they came depending on the buffer wait for readyness.

* buffer: damage is relative to current commit

ensure the damage is only used once, or we are constantly redrawing
things on state commits that isnt a buffer.

thanks PlasmaPower.

* compositor: move callbacks back to compositor

move SSurfaceStateFrameCB back to compositor in the class
CWLCallbackResource as per request, but still keep the state as owning.

* compositor: ensure commits come in order

if a buffer is waiting any commits after it might be non buffer commits
from the "future" and applying directly. and when the old buffer that
was waiting becomes ready it applies its states and overwrites the
future changes.

move things to scheduleState and add a m_pendingWaiting guard. and
schedule the next pending state from the old buffer commit when done.
and as such it loops itself and keeps thing orderly.
2025-10-06 12:20:04 +01:00
Dave Walker
cfac27251a
debug: fix data race in Debug::log() (#11931)
* debug: fix data race in Debug::log()

The templated Debug::log() had mutex protection but the non-template
overload it calls didn't, causing crashes when plugins called log from
background threads (like hypr-dynamic-cursors loading cursor themes).

Fixed by moving the mutex lock from the template version into the
non-template version, so all writes to shared state are protected.

Fixes: hyprwm/Hyprland#11929
Fixes: VirtCode/hypr-dynamic-cursors#99

* debug: apply clang-format to Log.cpp

Fix formatting to satisfy CI clang-format check.

---------

Co-authored-by: Dave Walker <dave@daviey.com>
2025-10-05 16:24:49 +02:00
UjinT34
76d998743a
cm: handle inert cm outputs (#11916) 2025-10-04 00:35:22 +02:00
vaxerski
b7ef892ecf [gha] Nix: update inputs 2025-10-03 19:52:11 +00:00
UjinT34
f0b4164e2e
cm: fix primaries to proto scale (#11914) 2025-10-03 21:50:57 +02:00
UjinT34
3bcfa94ee4
renderer: add render:non_shader_cm and fixes (#11900) 2025-10-02 12:05:54 +02:00
Vaxry
c467bb2640
renderer: fix popup fadeout blur (#11756)
popups dont get no xray
2025-10-02 12:01:16 +02:00
Vaxry
e0c96276df
renderer: optimize border drawcalls (#11891)
calculates the specific border region to avoid sampling on regions where the border cannot be at
2025-10-01 12:38:17 +01:00
378438ffe7 config: increase default anr_missed_pings value
ref https://github.com/hyprwm/Hyprland/discussions/11884
2025-10-01 12:19:16 +01:00
Vaxry
13648d196a
protocols/seat: force down rounding of coords at the surface edge (#11890)
ref https://github.com/hyprwm/Hyprland/discussions/11665
2025-10-01 12:15:23 +01:00
UjinT34
8c54c9b412
protocols/cm: remove unneeded preferred ref (#11877) 2025-10-01 11:04:49 +01:00
ItsOhen
38c1e72c9d
rules: fix some monitor rules (#11873) 2025-09-29 20:10:34 +02:00
UjinT34
0959672591
renderer/cm: add more monitor cm options (#11861)
Adds more cm options for monitors: DCIP3, Apple P3, Adobe
2025-09-29 13:22:42 +01:00
UjinT34
4d82cc5957
internal: fix clang-tidy "errors" (#11862) 2025-09-29 13:10:15 +01:00
Vaxry
43fb4753fc
gestures: fix gesture direction detection (#11852) 2025-09-29 12:29:40 +01:00
Tom Englund
f854b5bffb deco: reduce virtual calls in drop shadow
damageEntire() in CHyprdDropShadow is pretty much called per window per
frame, instead of all the PWINDOW-> virtual calls, store pos and size
once and move the duplicated code to a lambda. reducing it a bit.

shows up in profiling as minor waste.
2025-09-28 23:20:52 +02:00
Tom Englund
eb25dfd399 opengl: move from unordered_set to array
setCapStatus is a a heavy used function in hot rendering paths, it
shows up in profiling as using a bit of cpu just because of
unordered_set hashing etc, move to a enum and array and cache only the
heavily used ones.
2025-09-28 23:20:52 +02:00
Tom Englund
b627885788 decoration: reduce virtual calls
this shows up as top contender in idle cpu usage, because decos in
animations keeps locking weak pointers to shared pointers per window per
frame when its not really needed, use weakpointers all the way and it
drops to a bottom contender. marginal gains in the big picture. but
gains is gains.
2025-09-28 23:20:52 +02:00
c30036bdac
CI/Arch: build hyprgraphics after hyprutils
Ensure no ABI breaks. Hyprgraphics depends on hyprutils.
2025-09-28 19:19:41 +03:00
usering-around
766acadcf1
seat: release depressed modifiers on leave (#11854) 2025-09-28 00:05:30 +02:00
omar
ef479ff539
viewporter: clamp sub-pixel overflow (#11845)
Clamps the pending wp_viewport source rect back inside the attached buffer when it misses by <= 1 px, so if clients request something that falls within the 256-increment wl_fixed_from_double precision error it’s still treated as valid.
2025-09-27 20:14:43 +02:00
ItsOhen
6f1d2e771d
config: fix rules with no parameters not being counted as invalid (#11849)
Quite a big whoopsie to insert invalid rules.

Also adds special: cases.
2025-09-27 01:04:22 +02:00
ItsOhen
ae445606e2
config: allow negative to be used with tags. (#11779) 2025-09-26 18:19:53 +02:00
Nikolai Nechaev
4f3dd1ddb4
config: fix gesture dispatcher parsing with whitespaces (#11784)
* config: fix gesture dispatcher parsing with whitespaces

Some dispatcher functions (e.g., `moveFocusTo`) expect the given string to be
stripped of whitepsaces.

This fixes `gesture` line parsing: rather than calling dispatcher functions
with the original string, we reuse words parsed by `CConstVarList` and join
them with a comma.

* tests/gestures: Add a test for `movecursortocorner`
2025-09-26 15:49:07 +02:00
ItsOhen
d8f615751a
config: support more than 1 window rule per rule line. (#11689)
Adds support for specifying multiple rules in one line
2025-09-26 00:33:58 +02:00
João V. Farias
7ce451d20c
renderer: disable anti-aliasing on cursor:zoom_factor (#6135) (#11828)
use nearest-neighbor filtering for cursor scaling to avoid blurriness
2025-09-25 21:14:04 +02:00
Tom Englund
5212099b9f
layout: avoid nullptr deref (#11831)
OPENINGON can be a nullptr and that makes FLOATEDINTOTILED to nullptr
deref and segfault.
2025-09-25 15:30:04 +02:00
Tom Englund
8cce3b98ce
shm: refactor to UP and correct m_data check (#11820)
use unique pointers and rvalue references where applicable, buffer is
still a shared pointer because CHLBufferReference uses it to hold it
locked.

in CSHMPool destructor add a check for m_data != MAP_FAILED same in
resize, because mmap returns (void *) -1 on failure and that is not
comparable to nullptr. delete default constructor so we dont end up in
weird states with m_data.
2025-09-25 01:44:33 +02:00
Tom Englund
683fc77f80
hyprctl: nullptr guard --systeminfo (#11822)
running Hyprland --systeminfo from a console/tty calls systemInfoRequest
without us having a g_pCompositor or g_pHyprOpengl, so just if check them
and make --systeminfo not segfault and atleast print what info we can.
2025-09-25 01:44:07 +02:00
Vaxry
ec9a72d9fb
workspaces: fix persistence with no monitor specified (#11807) 2025-09-23 21:08:30 +02:00
omar
31bd9ec417
foreign-toplevel: continue past skipped invalid windows (#11804) 2025-09-23 19:50:57 +02:00
Nikolai Nechaev
29b103c376
exec: Spawn processes as direct children (#11735)
* exec: Spawn processes as direct children

Spawn processes as children rather than grandchildren.
This way, spawned processes may track Hyprland's state
by watching their parent, either directly or indirectly
(e.g., Linux's `PR_SET_PDEATH_SIG`).

Fixes #11728

* tests/exec: Add the test on process spawning

Add a test that ensures that:
- A spawned process remains a direct child of Hyprland;
- Upon termination, the process does not become a zombie.
2025-09-23 19:32:48 +02:00
Vaxry
70a7047ee1
renderer: fix uv scaling detection (#11789) 2025-09-22 13:01:59 +01:00
0xFMD
26f293523a
renderer: add "noscreenshare" layer rule (#11664) 2025-09-22 12:26:14 +01:00
Bahaa Mohamed
45f007d412 ci: remove duplicate cp and redundant mkdir commands 2025-09-22 12:31:36 +03:00
Nikolai Nechaev
22c8bc9b9b CI/Nix: Allow running CI in forks
Rather than hardcoding the repository name in the workflow file,
use a context value. This allows running workflows in forks.
2025-09-22 12:30:39 +03:00
Vaxry
26cbc67385
renderer: fix uv calculations once and for all (#11770)
fixes synchronization of ackd sizes, fixes wrong xdg stuff
2025-09-21 19:27:56 +02:00
Nikolai Nechaev
41dad38177
config: fix multi-argument gesture dispatcher parsing (#11721)
* config: Fix multi-argument gesture dispatchers parsing

The `dispatcher` gesture handler used to only handle
the first argument to the dispatcher, while some dispatchers
(e.g., `sendshortcut`) want multiple arguments.

This fixes `ConfigManager` to handle all the arguments
provided to the dispatcher gesture handler.

Fixes #11684.

* test/gestures: Add a test for a gesture with a multi-argument dispatcher

* test/gestures: Factor out `waitForWindowCount`

Reduce code duplication in the gestures test.
2025-09-20 17:57:49 +02:00
JS Deck
838439080a
vkeyboard: update cached mods before IME; add share_states = 2 config option (#11720) 2025-09-20 17:57:39 +02:00
ItsOhen
6a88f2e880
monitors: auto apply suggested scale and notify the user. (#11753) 2025-09-20 17:42:02 +02:00
vaxerski
8832607574 [gha] Nix: update inputs 2025-09-19 14:59:16 +00:00
usering-around
8fc7b2c171
input: fix virtual keyboard keymaps (#11763) 2025-09-19 16:58:03 +02:00
REVO9
afd1e71761
renderer: fix inconsistent border thickness for roundingPower < 2 (#11752) 2025-09-19 00:34:54 +02:00
Vaxry
4fc95d646d
renderer: asynchronously load background tex (#11749)
Bumps required hyprgraphics to 0.1.6

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2025-09-18 22:10:30 +02:00
91f592a875
workspace: fix relative workspaces with monitor descs 2025-09-18 20:46:57 +01:00
059ec60e9f
hyprpm: make temp root if not present 2025-09-18 13:25:04 +01:00
nikitax44
5648077978
animation: fix slide/slidefade to accept forced direction (#11725) 2025-09-18 13:53:28 +02:00
Vaxry
1cb8cd3930
solitary: fix check for config error (#11733)
Adds a blocker for solitary optimizations if there is a hyprerror present
2025-09-17 14:03:49 +02:00
7fd6998f7c
core: fix clang-format 2025-09-17 13:02:56 +01:00
5e96fac52f
presentation: fix vrr check for reporting no refresh time
ref #11608
2025-09-16 00:09:30 +01:00
4a9c4dbc04
gestures/fs: fix typo
fixes #11678
2025-09-15 22:30:08 +01:00
9e74d0aea7
renderer: clamp blur:passes 1-8
fixes some UB and dumb things

ref #11707
2025-09-15 12:44:12 +01:00
559024c331
gestures/float: fix typo 2025-09-14 01:52:41 +01:00
ItsOhen
16c18dde24
windows: fix no decorate not disabling borders (#11673) 2025-09-13 16:37:02 +02:00
Stanislav Senotrusov
adbf7c8663
input: handle tablet active area scaling when axes swap due to rotation (#11661)
Some tablet rotation modes (90°, 270°, and flipped variants) swap the X and Y axes.
This change adjusts the effective physical size based on axis orientation
to ensure tablet active area coordinates are normalized correctly.
2025-09-13 01:11:30 +02:00
0xFMD
797bfe905e
dispatchers: fix movecursor not updating client pos (#11672) 2025-09-11 21:52:30 +02:00
usering-around
38169c8fdd
input: support xkb v2 format (#11482) 2025-09-11 19:42:20 +02:00
Florian "sp1rit
c7b9969129
render/OpenGL: fix compilation for 32bit systems (#11667) 2025-09-11 19:41:33 +02:00
231b800784
flake.lock: update 2025-09-11 18:54:08 +03:00
8a959b4342
meson: set minimum version 2025-09-11 18:53:13 +03:00
394 changed files with 18130 additions and 9719 deletions

View file

@ -1,4 +1,111 @@
WarningsAsErrors: '*'
WarningsAsErrors: >
-*,
bugprone-*,
-bugprone-multi-level-implicit-pointer-conversion,
-bugprone-empty-catch,
-bugprone-unused-return-value,
-bugprone-reserved-identifier,
-bugprone-switch-missing-default-case,
-bugprone-unused-local-non-trivial-variable,
-bugprone-easily-swappable-parameters,
-bugprone-forward-declararion-namespace,
-bugprone-forward-declararion-namespace,
-bugprone-macro-parentheses,
-bugprone-narrowing-conversions,
-bugprone-branch-clone,
-bugprone-assignment-in-if-condition,
concurrency-*,
-concurrency-mt-unsafe,
cppcoreguidelines-*,
-cppcoreguidelines-pro-type-const-cast,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-avoid-const-or-ref-data-members,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-avoid-do-while,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-special-member-functions,
-cppcoreguidelines-explicit-virtual-functions,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-member-init,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-macro-to-enum,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-pro-type-cstyle-cast,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-pro-type-reinterpret-cast,
-google-global-names-in-headers,
-google-readability-casting,
google-runtime-operator,
misc-*,
-misc-use-internal-linkage,
-misc-unused-parameters,
-misc-no-recursion,
-misc-non-private-member-variables-in-classes,
-misc-include-cleaner,
-misc-use-anonymous-namespace,
-misc-const-correctness,
modernize-*,
-modernize-use-emplace,
-modernize-redundant-void-arg,
-modernize-use-starts-ends-with,
-modernize-use-designated-initializers,
-modernize-use-std-numbers,
-modernize-return-braced-init-list,
-modernize-use-trailing-return-type,
-modernize-use-using,
-modernize-use-override,
-modernize-avoid-c-arrays,
-modernize-macro-to-enum,
-modernize-loop-convert,
-modernize-use-nodiscard,
-modernize-pass-by-value,
-modernize-use-auto,
performance-*,
-performance-inefficient-vector-operation,
-performance-inefficient-string-concatenation,
-performance-enum-size,
-performance-move-const-arg,
-performance-avoid-endl,
-performance-unnecessary-value-param,
portability-std-allocator-const,
readability-*,
-readability-identifier-naming,
-readability-use-std-min-max,
-readability-math-missing-parentheses,
-readability-simplify-boolean-expr,
-readability-static-accessed-through-instance,
-readability-use-anyofallof,
-readability-enum-initial-value,
-readability-redundant-inline-specifier,
-readability-function-cognitive-complexity,
-readability-function-size,
-readability-identifier-length,
-readability-magic-numbers,
-readability-uppercase-literal-suffix,
-readability-braces-around-statements,
-readability-redundant-access-specifiers,
-readability-else-after-return,
-readability-container-data-pointer,
-readability-implicit-bool-conversion,
-readability-avoid-nested-conditional-operator,
-readability-redundant-member-init,
-readability-redundant-string-init,
-readability-avoid-const-params-in-decls,
-readability-named-parameter,
-readability-convert-member-functions-to-static,
-readability-qualified-auto,
-readability-make-member-function-const,
-readability-isolate-declaration,
-readability-inconsistent-declaration-parameter-name,
-clang-diagnostic-error,
HeaderFilterRegex: '.*\.hpp'
FormatStyle: file
Checks: >

View file

@ -24,6 +24,7 @@ runs:
glm \
glslang \
go \
gtest \
hyprlang \
hyprcursor \
jq \
@ -45,6 +46,7 @@ runs:
libxkbfile \
lld \
meson \
muparser \
ninja \
pango \
pixman \
@ -74,16 +76,25 @@ runs:
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
cmake --install build
- name: Get hyprgraphics-git
- name: Get hyprwire-git
shell: bash
run: |
git clone https://github.com/hyprwm/hyprgraphics && cd hyprgraphics && cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -B build && cmake --build build --target hyprgraphics && cmake --install build
git clone https://github.com/hyprwm/hyprwire --recursive
cd hyprwire
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
cmake --install build
- name: Get hyprutils-git
shell: bash
run: |
git clone https://github.com/hyprwm/hyprutils && cd hyprutils && cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -B build && cmake --build build --target hyprutils && cmake --install build
- name: Get hyprgraphics-git
shell: bash
run: |
git clone https://github.com/hyprwm/hyprgraphics && cd hyprgraphics && cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -B build && cmake --build build --target hyprgraphics && cmake --install build
- name: Get aquamarine-git
shell: bash
run: |

4
.github/labeler.yml vendored
View file

@ -22,6 +22,10 @@ protocols:
- changed-files:
- any-glob-to-any-file: ["protocols/**", "src/protocols/**"]
start:
- changed-files:
- any-glob-to-any-file: "start/**"
core:
- changed-files:
- any-glob-to-any-file: "src/**"

View file

@ -21,19 +21,16 @@ jobs:
- name: Build Hyprland
run: |
CFLAGS=-Werror CXXFLAGS=-Werror make all
CFLAGS=-Werror CXXFLAGS=-Werror make nopch
- name: Compress and package artifacts
run: |
mkdir x86_64-pc-linux-gnu
mkdir hyprland
mkdir hyprland/example
mkdir hyprland/assets
cp ./LICENSE hyprland/
cp build/Hyprland hyprland/
cp build/hyprctl/hyprctl hyprland/
cp build/hyprpm/hyprpm hyprland/
cp build/Hyprland hyprland/
cp -r example/ hyprland/
cp -r assets/ hyprland/
tar -cvJf Hyprland.tar.xz hyprland
@ -44,86 +41,16 @@ jobs:
name: Build archive
path: Hyprland.tar.xz
meson:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
name: "Build Hyprland with Meson (Arch)"
runs-on: ubuntu-latest
container:
image: archlinux
steps:
- name: Checkout repository actions
uses: actions/checkout@v4
with:
sparse-checkout: .github/actions
- name: Setup base
uses: ./.github/actions/setup_base
- name: Configure
run: meson setup build -Ddefault_library=static
- name: Compile
run: ninja -C build
no-pch:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
name: "Build Hyprland without precompiled headers (Arch)"
runs-on: ubuntu-latest
container:
image: archlinux
steps:
- name: Checkout repository actions
uses: actions/checkout@v4
with:
sparse-checkout: .github/actions
- name: Setup base
uses: ./.github/actions/setup_base
with:
INSTALL_XORG_PKGS: true
- name: Compile
run: make nopch
noxwayland:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
name: "Build Hyprland in pure Wayland (Arch)"
runs-on: ubuntu-latest
container:
image: archlinux
steps:
- name: Checkout repository actions
uses: actions/checkout@v4
with:
sparse-checkout: .github/actions
- name: Setup base
uses: ./.github/actions/setup_base
- name: Configure
run: mkdir -p build && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DNO_XWAYLAND:STRING=true -H./ -B./build -G Ninja
- name: Compile
run: make release
clang-format:
permissions: read-all
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
name: "Code Style (Arch)"
name: "Code Style"
runs-on: ubuntu-latest
container:
image: archlinux
steps:
- name: Checkout repository actions
- name: Checkout repository
uses: actions/checkout@v4
with:
sparse-checkout: .github/actions
- name: Setup base
uses: ./.github/actions/setup_base
- name: Configure
run: meson setup build -Ddefault_library=static
- name: clang-format check
run: ninja -C build clang-format-check
uses: jidicula/clang-format-action@v4.16.0
with:
exclude-regex: ^subprojects$

View file

@ -4,43 +4,23 @@ jobs:
clang-format:
permissions: write-all
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
name: "Code Style (Arch)"
name: "Code Style"
runs-on: ubuntu-latest
container:
image: archlinux
steps:
- name: Checkout repository actions
- name: Checkout repository
uses: actions/checkout@v4
with:
sparse-checkout: .github/actions
- name: Setup base
uses: ./.github/actions/setup_base
- name: Configure
run: meson setup build -Ddefault_library=static
- name: clang-format check
run: ninja -C build clang-format-check
uses: jidicula/clang-format-action@v4.16.0
with:
exclude-regex: ^subprojects$
- name: clang-format apply
if: ${{ failure() && github.event_name == 'pull_request' }}
run: ninja -C build clang-format
- name: Create patch
- name: Create comment
if: ${{ failure() && github.event_name == 'pull_request' }}
run: |
echo 'Please fix the formatting issues by running [`clang-format`](https://wiki.hyprland.org/Contributing-and-Debugging/PR-Guidelines/#code-style), or directly apply this patch:' > clang-format.patch
echo '<details>' >> clang-format.patch
echo '<summary>clang-format.patch</summary>' >> clang-format.patch
echo >> clang-format.patch
echo '```diff' >> clang-format.patch
git diff >> clang-format.patch
echo '```' >> clang-format.patch
echo >> clang-format.patch
echo '</details>' >> clang-format.patch
echo 'Please fix the formatting issues by running [`clang-format`](https://wiki.hyprland.org/Contributing-and-Debugging/PR-Guidelines/#code-style).' > clang-format.patch
- name: Comment patch
- name: Post comment
if: ${{ failure() && github.event_name == 'pull_request' }}
uses: mshick/add-pr-comment@v2
with:

45
.github/workflows/new-pr-comment.yml vendored Normal file
View file

@ -0,0 +1,45 @@
name: "New MR welcome comment"
on:
pull_request_target:
types:
- opened
jobs:
comment:
if: >
github.event.pull_request.user.login != 'vaxerski' &&
github.event.pull_request.user.login != 'fufexan' &&
github.event.pull_request.user.login != 'gulafaran' &&
github.event.pull_request.user.login != 'ujint34' &&
github.event.pull_request.user.login != 'paideiadilemma' &&
github.event.pull_request.user.login != 'notashelf'
runs-on: ubuntu-latest
permissions:
pull-requests: write
env:
PR_COMMENT: |
Hello and thank you for making a PR to Hyprland!
Please check the [PR Guidelines](https://wiki.hypr.land/Contributing-and-Debugging/PR-Guidelines/) and make sure your PR follows them.
It will make the entire review process faster. :)
If your code can be tested, please always add tests. See more [here](https://wiki.hypr.land/Contributing-and-Debugging/Tests/).
_beep boop, I'm just a bot. A real human will review your PR soon._
steps:
- name: Add comment to PR
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const pr = context.payload.pull_request;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: process.env.PR_COMMENT,
});

View file

@ -13,7 +13,7 @@ jobs:
uses: ./.github/workflows/nix.yml
secrets: inherit
with:
command: nix build 'github:hyprwm/Hyprland?ref=${{ github.ref }}' -L --extra-substituters "https://hyprland.cachix.org"
command: nix build 'github:${{ github.repository }}?ref=${{ github.ref }}' -L --extra-substituters "https://hyprland.cachix.org"
xdph:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)
@ -21,10 +21,9 @@ jobs:
uses: ./.github/workflows/nix.yml
secrets: inherit
with:
command: nix build 'github:hyprwm/Hyprland?ref=${{ github.ref }}#xdg-desktop-portal-hyprland' -L --extra-substituters "https://hyprland.cachix.org"
command: nix build 'github:${{ github.repository }}?ref=${{ github.ref }}#xdg-desktop-portal-hyprland' -L --extra-substituters "https://hyprland.cachix.org"
test:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)
needs: hyprland
uses: ./.github/workflows/nix-test.yml
secrets: inherit

View file

@ -20,25 +20,13 @@ jobs:
- name: Restore and save Nix store
uses: nix-community/cache-nix-action@v6
with:
# restore and save a cache using this key
primary-key: nix-${{ runner.os }}
# restore and save a cache using this key (per job)
primary-key: nix-${{ runner.os }}-${{ github.job }}
# if there's no cache hit, restore a cache by this prefix
restore-prefixes-first-match: nix-${{ runner.os }}
# collect garbage until the Nix store size (in bytes) is at most this number
# before trying to save a new cache
# 1G = 1073741824
gc-max-store-size-linux: 5G
# do purge caches
purge: true
# purge all versions of the cache
purge-prefixes: nix-${{ runner.os }}
# created more than this number of seconds ago
purge-created: 0
# or, last accessed more than this number of seconds ago
# relative to the start of the `Post Restore and save Nix store` phase
purge-last-accessed: 0
# except any version with the key that is the same as the `primary-key`
purge-primary-key: never
- uses: cachix/cachix-action@v15
with:
@ -46,7 +34,7 @@ jobs:
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Run test VM
run: nix build 'github:hyprwm/Hyprland?ref=${{ github.ref }}#checks.x86_64-linux.tests' -L --extra-substituters "https://hyprland.cachix.org"
run: nix build 'github:${{ github.repository }}?ref=${{ github.ref }}#checks.x86_64-linux.tests' -L --extra-substituters "https://hyprland.cachix.org"
- name: Check exit status
run: grep 0 result/exit_status

View file

@ -27,25 +27,13 @@ jobs:
- name: Restore and save Nix store
uses: nix-community/cache-nix-action@v6
with:
# restore and save a cache using this key
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
# restore and save a cache using this key (per job)
primary-key: nix-${{ runner.os }}-${{ github.job }}
# if there's no cache hit, restore a cache by this prefix
restore-prefixes-first-match: nix-${{ runner.os }}-
restore-prefixes-first-match: nix-${{ runner.os }}
# collect garbage until the Nix store size (in bytes) is at most this number
# before trying to save a new cache
# 1G = 1073741824
gc-max-store-size-linux: 1G
# do purge caches
purge: true
# purge all versions of the cache
purge-prefixes: nix-${{ runner.os }}-
# created more than this number of seconds ago
purge-created: 0
# or, last accessed more than this number of seconds ago
# relative to the start of the `Post Restore and save Nix store` phase
purge-last-accessed: 0
# except any version with the key that is the same as the `primary-key`
purge-primary-key: never
gc-max-store-size-linux: 5G
- name: Update inputs
run: nix/update-inputs.sh

View file

@ -25,25 +25,13 @@ jobs:
- name: Restore and save Nix store
uses: nix-community/cache-nix-action@v6
with:
# restore and save a cache using this key
primary-key: nix-${{ runner.os }}
# restore and save a cache using this key (per job)
primary-key: nix-${{ runner.os }}-${{ github.job }}
# if there's no cache hit, restore a cache by this prefix
restore-prefixes-first-match: nix-${{ runner.os }}
# collect garbage until the Nix store size (in bytes) is at most this number
# before trying to save a new cache
# 1G = 1073741824
gc-max-store-size-linux: 5G
# do purge caches
purge: true
# purge all versions of the cache
purge-prefixes: nix-${{ runner.os }}
# created more than this number of seconds ago
purge-created: 0
# or, last accessed more than this number of seconds ago
# relative to the start of the `Post Restore and save Nix store` phase
purge-last-accessed: 0
# except any version with the key that is the same as the `primary-key`
purge-primary-key: never
- uses: cachix/cachix-action@v15
with:

View file

@ -9,17 +9,36 @@ jobs:
source-tarball:
runs-on: ubuntu-latest
steps:
- name: Checkout Hyprland
id: checkout
uses: actions/checkout@v4
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0
submodules: recursive
- name: Generate version
id: genversion
- name: Populate git info in version.h.in
run: |
git fetch --unshallow || echo "failed unshallowing"
bash -c scripts/generateVersion.sh
git fetch --tags --unshallow || true
COMMIT_HASH=$(git rev-parse HEAD)
BRANCH="${GITHUB_REF_NAME:-$(git rev-parse --abbrev-ref HEAD)}"
COMMIT_MSG=$(git show -s --format=%s | sed 's/[&/]/\\&/g')
COMMIT_DATE=$(git show -s --format=%cd --date=local)
GIT_DIRTY=$(git diff-index --quiet HEAD -- && echo "clean" || echo "dirty")
GIT_TAG=$(git describe --tags --always || echo "unknown")
GIT_COMMITS=$(git rev-list --count HEAD)
echo "Branch: $BRANCH"
echo "Tag: $GIT_TAG"
sed -i \
-e "s|@GIT_COMMIT_HASH@|$COMMIT_HASH|" \
-e "s|@GIT_BRANCH@|$BRANCH|" \
-e "s|@GIT_COMMIT_MESSAGE@|$COMMIT_MSG|" \
-e "s|@GIT_COMMIT_DATE@|$COMMIT_DATE|" \
-e "s|@GIT_DIRTY@|$GIT_DIRTY|" \
-e "s|@GIT_TAG@|$GIT_TAG|" \
-e "s|@GIT_COMMITS@|$GIT_COMMITS|" \
src/version.h.in
- name: Create tarball with submodules
id: tar

View file

@ -0,0 +1,139 @@
name: AI Translation Check
on:
# pull_request_target:
# types:
# - opened
issue_comment:
types:
- created
permissions:
contents: read
pull-requests: write
issues: write
jobs:
review:
name: Review Translation
if: ${{ github.event_name == 'pull_request_target' || (github.event_name == 'issue_comment' && github.event.action == 'created' && github.event.issue.pull_request != null && github.event.comment.user.login == 'vaxerski' && github.event.comment.body == 'ai, please recheck' ) }}
runs-on: ubuntu-latest
env:
OPENAI_MODEL: gpt-5-mini
SYSTEM_PROMPT: |
You are a programmer and a translator. Your job is to review the attached patch for adding translation to a piece of software and make sure the submitted translation is not malicious, and that it makes sense. If the translation is not malicious, and doesn't contain obvious grammatical mistakes, say "Translation check OK". Otherwise, say "Translation check not ok" and list bad entries.
Examples of bad translations include obvious trolling (slurs, etc) or nonsense sentences. Meaningful improvements may be suggested, but if there are only minor improvements, just reply with "Translation check OK". Do not provide anything but the result and (if applicable) the bad entries or improvements.
AI_PROMPT: Translation patch below.
steps:
- name: Checkout source code
uses: actions/checkout@v5
- uses: dorny/paths-filter@v3
id: changes
with:
filters: |
i18n:
- 'src/i18n/**'
- name: Stop if i18n not changed
if: steps.changes.outputs.i18n != 'true'
run: echo "No i18n changes in this PR; skipping." && exit 0
- name: Determine PR number
id: pr
run: |
if [ "${{ github.event_name }}" = "pull_request_target" ]; then
echo "number=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT"
else
echo "number=${{ github.event.issue.number }}" >> "$GITHUB_OUTPUT"
fi
- name: Download combined PR diff
id: get_diff
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.pr.outputs.number }}
run: |
# Get the combined diff for the entire PR
curl -sSL \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3.diff" \
"https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUMBER" \
-o pr.diff
# Compute character length
LEN=$(wc -c < pr.diff | tr -d ' ')
echo "len=$LEN" >> "$GITHUB_OUTPUT"
if [ "$LEN" -gt 25000 ]; then
echo "too_long=true" >> "$GITHUB_OUTPUT"
else
echo "too_long=false" >> "$GITHUB_OUTPUT"
fi
echo "got diff:"
cat pr.diff
- name: Comment when diff length exceeded
if: steps.get_diff.outputs.too_long == 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.pr.outputs.number }}
run: |
jq -n --arg body "Diff length exceeded, can't query API" '{body: ("AI translation check result:\n\n" + $body)}' > body.json
curl -sS -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Content-Type: application/json" \
"https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUMBER/comments" \
--data @body.json
- name: Query OpenAI and post review
if: steps.get_diff.outputs.too_long == 'false'
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_MODEL: ${{ env.OPENAI_MODEL }}
SYSTEM_PROMPT: ${{ env.SYSTEM_PROMPT }}
AI_PROMPT: ${{ env.AI_PROMPT }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.pr.outputs.number }}
run: |
# Prepare OpenAI chat request payload (embed diff safely)
jq -n \
--arg model "$OPENAI_MODEL" \
--arg sys "$SYSTEM_PROMPT" \
--arg prompt "$AI_PROMPT" \
--rawfile diff pr.diff \
'{model:$model,
messages:[
{role:"system", content:$sys},
{role:"user", content: ($prompt + "\n\n```diff\n" + $diff + "\n```")}
]
}' > payload.json
# Call OpenAI
curl -sS https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d @payload.json > response.json
# Extract response text
COMMENT=$(jq -r '.choices[0].message.content // empty' response.json)
if [ -z "$COMMENT" ]; then
COMMENT="AI did not return a response."
fi
# If failed, add a note
ADDITIONAL_NOTE=""
if [[ "$COMMENT" == *"not ok"* ]]; then
ADDITIONAL_NOTE=$(echo -ne "\n\nPlease note this check is a guideline, not a hard requirement. It is here to help you translate. If you disagree with some points, just state that. Any typos should be fixed.")
fi
# Post the review as a PR comment
jq -n --arg body "$COMMENT" --arg note "$ADDITIONAL_NOTE" '{body: ("AI translation check result:\n\n" + $body + $note)}' > body.json
echo "CURLing https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUMBER/comments"
curl -sS -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Content-Type: application/json" \
"https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUMBER/comments" \
--data @body.json

2
.gitignore vendored
View file

@ -32,6 +32,8 @@ src/render/shaders/*.inc
src/render/shaders/Shaders.hpp
hyprctl/hyprctl
hyprctl/hw-protocols/*.c*
hyprctl/hw-protocols/*.h*
gmon.out
*.out

View file

@ -17,18 +17,21 @@ set(HYPRLAND_VERSION ${VER})
set(PREFIX ${CMAKE_INSTALL_PREFIX})
set(INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR})
set(BINDIR ${CMAKE_INSTALL_BINDIR})
configure_file(hyprland.pc.in hyprland.pc @ONLY)
set(CMAKE_MESSAGE_LOG_LEVEL "STATUS")
message(STATUS "Gathering git info")
# Get git info hash and branch
execute_process(COMMAND ./scripts/generateVersion.sh
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
# Make shader files includable
execute_process(COMMAND ./scripts/generateShaderIncludes.sh
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
RESULT_VARIABLE HYPR_SHADER_GEN_RESULT)
if(NOT HYPR_SHADER_GEN_RESULT EQUAL 0)
message(
FATAL_ERROR
"Failed to generate shader includes (scripts/generateShaderIncludes.sh), exit code: ${HYPR_SHADER_GEN_RESULT}"
)
endif()
find_package(PkgConfig REQUIRED)
@ -36,11 +39,23 @@ find_package(PkgConfig REQUIRED)
# provide a .pc file and won't be detected this way
pkg_check_modules(udis_dep IMPORTED_TARGET udis86>=1.7.2)
# Fallback to subproject
# Find non-pkgconfig udis86, otherwise fallback to subproject
if(NOT udis_dep_FOUND)
add_subdirectory("subprojects/udis86")
include_directories("subprojects/udis86")
message(STATUS "udis86 dependency not found, falling back to subproject")
find_library(udis_nopc udis86)
if(NOT("${udis_nopc}" MATCHES "udis_nopc-NOTFOUND"))
message(STATUS "Found udis86 at ${udis_nopc}")
else()
add_subdirectory("subprojects/udis86")
include_directories("subprojects/udis86")
message(STATUS "udis86 dependency not found, falling back to subproject")
endif()
endif()
find_library(librt rt)
if("${librt}" MATCHES "librt-NOTFOUND")
unset(LIBRT)
else()
set(LIBRT rt)
endif()
if(CMAKE_BUILD_TYPE)
@ -71,9 +86,11 @@ message(
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
message(STATUS "Configuring Hyprland in Debug with CMake")
add_compile_definitions(HYPRLAND_DEBUG)
set(BUILD_TESTING ON)
else()
add_compile_options(-O3)
message(STATUS "Configuring Hyprland in Release with CMake")
set(BUILD_TESTING OFF)
endif()
add_compile_definitions(HYPRLAND_VERSION="${HYPRLAND_VERSION}")
@ -95,6 +112,9 @@ add_compile_options(
-Wno-clobbered
-fmacro-prefix-map=${CMAKE_SOURCE_DIR}/=)
# disable lto as it may break plugins
add_compile_options(-fno-lto)
set(CMAKE_EXECUTABLE_ENABLE_EXPORTS TRUE)
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
@ -105,48 +125,153 @@ find_package(Threads REQUIRED)
set(GLES_VERSION "GLES3")
find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION})
pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=0.9.3)
pkg_check_modules(hyprlang_dep REQUIRED IMPORTED_TARGET hyprlang>=0.3.2)
pkg_check_modules(hyprcursor_dep REQUIRED IMPORTED_TARGET hyprcursor>=0.1.7)
pkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=0.8.2)
pkg_check_modules(hyprgraphics_dep REQUIRED IMPORTED_TARGET hyprgraphics>=0.1.3)
set(AQUAMARINE_MINIMUM_VERSION 0.9.3)
set(HYPRLANG_MINIMUM_VERSION 0.6.7)
set(HYPRCURSOR_MINIMUM_VERSION 0.1.7)
set(HYPRUTILS_MINIMUM_VERSION 0.11.0)
set(HYPRGRAPHICS_MINIMUM_VERSION 0.1.6)
pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=${AQUAMARINE_MINIMUM_VERSION})
pkg_check_modules(hyprlang_dep REQUIRED IMPORTED_TARGET hyprlang>=${HYPRLANG_MINIMUM_VERSION})
pkg_check_modules(hyprcursor_dep REQUIRED IMPORTED_TARGET hyprcursor>=${HYPRCURSOR_MINIMUM_VERSION})
pkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=${HYPRUTILS_MINIMUM_VERSION})
pkg_check_modules(hyprgraphics_dep REQUIRED IMPORTED_TARGET hyprgraphics>=${HYPRGRAPHICS_MINIMUM_VERSION})
string(REPLACE "." ";" AQ_VERSION_LIST ${aquamarine_dep_VERSION})
list(GET AQ_VERSION_LIST 0 AQ_VERSION_MAJOR)
list(GET AQ_VERSION_LIST 1 AQ_VERSION_MINOR)
list(GET AQ_VERSION_LIST 2 AQ_VERSION_PATCH)
add_compile_definitions(AQUAMARINE_VERSION="${aquamarine_dep_VERSION}")
add_compile_definitions(AQUAMARINE_VERSION_MAJOR=${AQ_VERSION_MAJOR})
add_compile_definitions(AQUAMARINE_VERSION_MINOR=${AQ_VERSION_MINOR})
add_compile_definitions(AQUAMARINE_VERSION_PATCH=${AQ_VERSION_PATCH})
add_compile_definitions(HYPRLANG_VERSION="${hyprlang_dep_VERSION}")
add_compile_definitions(HYPRUTILS_VERSION="${hyprutils_dep_VERSION}")
add_compile_definitions(HYPRCURSOR_VERSION="${hyprcursor_dep_VERSION}")
add_compile_definitions(HYPRGRAPHICS_VERSION="${hyprgraphics_dep_VERSION}")
set(AQUAMARINE_VERSION "${aquamarine_dep_VERSION}")
set(AQUAMARINE_VERSION_MAJOR "${AQ_VERSION_MAJOR}")
set(AQUAMARINE_VERSION_MINOR "${AQ_VERSION_MINOR}")
set(AQUAMARINE_VERSION_PATCH "${AQ_VERSION_PATCH}")
set(HYPRLANG_VERSION "${hyprlang_dep_VERSION}")
set(HYPRUTILS_VERSION "${hyprutils_dep_VERSION}")
set(HYPRCURSOR_VERSION "${hyprcursor_dep_VERSION}")
set(HYPRGRAPHICS_VERSION "${hyprgraphics_dep_VERSION}")
find_package(Git QUIET)
# Populate variables with env vars if present
set(GIT_COMMIT_HASH "$ENV{GIT_COMMIT_HASH}")
if(NOT GIT_COMMIT_HASH)
set(GIT_COMMIT_HASH "unknown")
endif()
set(GIT_BRANCH "$ENV{GIT_BRANCH}")
if(NOT GIT_BRANCH)
set(GIT_BRANCH "unknown")
endif()
set(GIT_COMMIT_MESSAGE "$ENV{GIT_COMMIT_MESSAGE}")
if(NOT GIT_COMMIT_MESSAGE)
set(GIT_COMMIT_MESSAGE "unknown")
endif()
set(GIT_COMMIT_DATE "$ENV{GIT_COMMIT_DATE}")
if(NOT GIT_COMMIT_DATE)
set(GIT_COMMIT_DATE "unknown")
endif()
set(GIT_DIRTY "$ENV{GIT_DIRTY}")
if(NOT GIT_DIRTY)
set(GIT_DIRTY "unknown")
endif()
set(GIT_TAG "$ENV{GIT_TAG}")
if(NOT GIT_TAG)
set(GIT_TAG "unknown")
endif()
set(GIT_COMMITS "$ENV{GIT_COMMITS}")
if(NOT GIT_COMMITS)
set(GIT_COMMITS "0")
endif()
if(Git_FOUND)
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-parse --show-toplevel
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_TOPLEVEL
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
RESULT_VARIABLE GIT_TOPLEVEL_RESULT
)
if(GIT_TOPLEVEL_RESULT EQUAL 0)
message(STATUS "Detected git repository root: ${GIT_TOPLEVEL}")
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
WORKING_DIRECTORY ${GIT_TOPLEVEL}
OUTPUT_VARIABLE GIT_COMMIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${GIT_EXECUTABLE} branch --show-current
WORKING_DIRECTORY ${GIT_TOPLEVEL}
OUTPUT_VARIABLE GIT_BRANCH OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND sh "-c" "${GIT_EXECUTABLE} show -s --format=%s --no-show-signature | sed \"s/\\\"/\'/g\""
WORKING_DIRECTORY ${GIT_TOPLEVEL}
OUTPUT_VARIABLE GIT_COMMIT_MESSAGE OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${GIT_EXECUTABLE} show -s --format=%cd --date=local --no-show-signature
WORKING_DIRECTORY ${GIT_TOPLEVEL}
OUTPUT_VARIABLE GIT_COMMIT_DATE OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${GIT_EXECUTABLE} diff-index --quiet HEAD --
WORKING_DIRECTORY ${GIT_TOPLEVEL}
RESULT_VARIABLE GIT_DIRTY_RESULT)
if(NOT GIT_DIRTY_RESULT EQUAL 0)
set(GIT_DIRTY "dirty")
else()
set(GIT_DIRTY "clean")
endif()
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags
WORKING_DIRECTORY ${GIT_TOPLEVEL}
OUTPUT_VARIABLE GIT_TAG OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${GIT_EXECUTABLE} rev-list --count HEAD
WORKING_DIRECTORY ${GIT_TOPLEVEL}
OUTPUT_VARIABLE GIT_COMMITS OUTPUT_STRIP_TRAILING_WHITESPACE)
else()
message(WARNING "No Git repository detected in ${CMAKE_SOURCE_DIR}")
endif()
endif()
configure_file(
${CMAKE_SOURCE_DIR}/src/version.h.in
${CMAKE_SOURCE_DIR}/src/version.h
@ONLY
)
set_source_files_properties(${CMAKE_SOURCE_DIR}/src/version.h PROPERTIES GENERATED TRUE)
set(XKBCOMMON_MINIMUM_VERSION 1.11.0)
set(WAYLAND_SERVER_MINIMUM_VERSION 1.22.90)
set(WAYLAND_SERVER_PROTOCOLS_MINIMUM_VERSION 1.45)
set(LIBINPUT_MINIMUM_VERSION 1.28)
pkg_check_modules(
deps
REQUIRED
IMPORTED_TARGET
xkbcommon
IMPORTED_TARGET GLOBAL
xkbcommon>=${XKBCOMMON_MINIMUM_VERSION}
uuid
wayland-server>=1.22.90
wayland-protocols>=1.45
wayland-server>=${WAYLAND_SERVER_MINIMUM_VERSION}
wayland-protocols>=${WAYLAND_SERVER_PROTOCOLS_MINIMUM_VERSION}
cairo
pango
pangocairo
pixman-1
xcursor
libdrm
libinput>=1.28
libinput>=${LIBINPUT_MINIMUM_VERSION}
gbm
gio-2.0
re2)
re2
muparser)
find_package(hyprwayland-scanner 0.3.10 REQUIRED)
file(GLOB_RECURSE SRCFILES "src/*.cpp")
get_filename_component(FULL_MAIN_PATH ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp ABSOLUTE)
list(REMOVE_ITEM SRCFILES "${FULL_MAIN_PATH}")
set(TRACY_CPP_FILES "")
if(USE_TRACY)
@ -154,7 +279,12 @@ if(USE_TRACY)
message(STATUS "Tracy enabled, TRACY_CPP_FILES: " ${TRACY_CPP_FILES})
endif()
add_executable(Hyprland ${SRCFILES} ${TRACY_CPP_FILES})
add_library(hyprland_lib STATIC ${SRCFILES})
add_executable(Hyprland src/main.cpp ${TRACY_CPP_FILES})
target_link_libraries(Hyprland hyprland_lib)
target_include_directories(hyprland_lib PUBLIC ${deps_INCLUDE_DIRS})
target_include_directories(Hyprland PUBLIC ${deps_INCLUDE_DIRS})
set(USE_GPROF OFF)
@ -164,8 +294,8 @@ if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
if(WITH_ASAN)
message(STATUS "Enabling ASan")
target_link_libraries(Hyprland asan)
target_compile_options(Hyprland PUBLIC -fsanitize=address)
target_link_libraries(hyprland_lib PUBLIC asan)
target_compile_options(hyprland_lib PUBLIC -fsanitize=address)
endif()
if(USE_TRACY)
@ -175,7 +305,7 @@ if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
option(TRACY_ON_DEMAND "" ON)
add_subdirectory(subprojects/tracy)
target_link_libraries(Hyprland Tracy::TracyClient)
target_link_libraries(hyprland_lib PUBLIC Tracy::TracyClient)
if(USE_TRACY_GPU)
message(STATUS "Tracy GPU Profiling is turned on")
@ -191,6 +321,10 @@ if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
endif()
endif()
if(BUILT_WITH_NIX)
add_compile_definitions(BUILT_WITH_NIX)
endif()
check_include_file("execinfo.h" EXECINFOH)
if(EXECINFOH)
message(STATUS "Configuration supports execinfo")
@ -200,19 +334,19 @@ endif()
include(CheckLibraryExists)
check_library_exists(execinfo backtrace "" HAVE_LIBEXECINFO)
if(HAVE_LIBEXECINFO)
target_link_libraries(Hyprland execinfo)
target_link_libraries(hyprland_lib PUBLIC execinfo)
endif()
check_include_file("sys/timerfd.h" HAS_TIMERFD)
pkg_check_modules(epoll IMPORTED_TARGET epoll-shim)
if(NOT HAS_TIMERFD AND epoll_FOUND)
target_link_libraries(Hyprland PkgConfig::epoll)
target_link_libraries(hyprland_lib PUBLIC PkgConfig::epoll)
endif()
check_include_file("sys/inotify.h" HAS_INOTIFY)
pkg_check_modules(inotify IMPORTED_TARGET libinotify)
if(NOT HAS_INOTIFY AND inotify_FOUND)
target_link_libraries(Hyprland PkgConfig::inotify)
target_link_libraries(hyprland_lib PUBLIC PkgConfig::inotify)
endif()
if(NO_XWAYLAND)
@ -220,10 +354,7 @@ if(NO_XWAYLAND)
add_compile_definitions(NO_XWAYLAND)
else()
message(STATUS "XWAYLAND Enabled (NO_XWAYLAND not defined) checking deps...")
pkg_check_modules(
xdeps
REQUIRED
IMPORTED_TARGET
set(XWAYLAND_DEPENDENCIES
xcb
xcb-render
xcb-xfixes
@ -231,9 +362,21 @@ else()
xcb-composite
xcb-res
xcb-errors)
target_link_libraries(Hyprland PkgConfig::xdeps)
pkg_check_modules(
xdeps
REQUIRED
IMPORTED_TARGET
${XWAYLAND_DEPENDENCIES})
string(JOIN ", " PKGCONFIG_XWAYLAND_DEPENDENCIES ${XWAYLAND_DEPENDENCIES})
string(PREPEND PKGCONFIG_XWAYLAND_DEPENDENCIES ", ")
target_link_libraries(hyprland_lib PUBLIC PkgConfig::xdeps)
endif()
configure_file(hyprland.pc.in hyprland.pc @ONLY)
if(NO_SYSTEMD)
message(STATUS "SYSTEMD support is disabled...")
else()
@ -254,30 +397,42 @@ set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)
message(STATUS "Setting precompiled headers")
target_precompile_headers(Hyprland PRIVATE
$<$<COMPILE_LANGUAGE:CXX>:src/pch/pch.hpp>)
if(CMAKE_DISABLE_PRECOMPILE_HEADERS)
message(STATUS "Not using precompiled headers")
else()
message(STATUS "Setting precompiled headers")
target_precompile_headers(hyprland_lib PRIVATE
$<$<COMPILE_LANGUAGE:CXX>:src/pch/pch.hpp>)
endif()
message(STATUS "Setting link libraries")
target_link_libraries(
Hyprland
rt
hyprland_lib
PUBLIC
PkgConfig::aquamarine_dep
PkgConfig::hyprlang_dep
PkgConfig::hyprutils_dep
PkgConfig::hyprcursor_dep
PkgConfig::hyprgraphics_dep
PkgConfig::deps)
PkgConfig::deps
)
target_link_libraries(
Hyprland
${LIBRT}
hyprland_lib)
if(udis_dep_FOUND)
target_link_libraries(Hyprland PkgConfig::udis_dep)
target_link_libraries(hyprland_lib PUBLIC PkgConfig::udis_dep)
elseif(NOT("${udis_nopc}" MATCHES "udis_nopc-NOTFOUND"))
target_link_libraries(hyprland_lib PUBLIC ${udis_nopc})
else()
target_link_libraries(Hyprland libudis86)
target_link_libraries(hyprland_lib PUBLIC libudis86)
endif()
# used by `make installheaders`, to ensure the headers are generated
add_custom_target(generate-protocol-headers)
set(PROTOCOL_SOURCES "")
function(protocolnew protoPath protoName external)
if(external)
@ -291,10 +446,15 @@ function(protocolnew protoPath protoName external)
COMMAND hyprwayland-scanner ${path}/${protoName}.xml
${CMAKE_SOURCE_DIR}/protocols/
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
target_sources(Hyprland PRIVATE protocols/${protoName}.cpp
target_sources(hyprland_lib PRIVATE protocols/${protoName}.cpp
protocols/${protoName}.hpp)
target_sources(generate-protocol-headers
PRIVATE ${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp)
list(APPEND PROTOCOL_SOURCES "${CMAKE_SOURCE_DIR}/protocols/${protoName}.cpp")
set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE)
list(APPEND PROTOCOL_SOURCES "${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp")
set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE)
endfunction()
function(protocolWayland)
add_custom_command(
@ -304,12 +464,17 @@ function(protocolWayland)
hyprwayland-scanner --wayland-enums
${WAYLAND_SCANNER_PKGDATA_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
target_sources(Hyprland PRIVATE protocols/wayland.cpp protocols/wayland.hpp)
target_sources(hyprland_lib PRIVATE protocols/wayland.cpp protocols/wayland.hpp)
target_sources(generate-protocol-headers
PRIVATE ${CMAKE_SOURCE_DIR}/protocols/wayland.hpp)
list(APPEND PROTOCOL_SOURCES "${CMAKE_SOURCE_DIR}/protocols/wayland.hpp")
set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE)
list(APPEND PROTOCOL_SOURCES "${CMAKE_SOURCE_DIR}/protocols/wayland.cpp")
set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE)
endfunction()
target_link_libraries(Hyprland OpenGL::EGL OpenGL::GL Threads::Threads)
target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GL Threads::Threads)
pkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.6.4)
if(hyprland_protocols_dep_FOUND)
@ -384,11 +549,14 @@ protocolnew("staging/xdg-system-bell" "xdg-system-bell-v1" false)
protocolnew("staging/ext-workspace" "ext-workspace-v1" false)
protocolnew("staging/ext-data-control" "ext-data-control-v1" false)
protocolnew("staging/pointer-warp" "pointer-warp-v1" false)
protocolnew("staging/fifo" "fifo-v1" false)
protocolnew("staging/commit-timing" "commit-timing-v1" false)
protocolwayland()
# tools
add_subdirectory(hyprctl)
add_subdirectory(start)
if(NO_HYPRPM)
message(STATUS "hyprpm is disabled")
@ -415,7 +583,6 @@ add_compile_definitions(DATAROOTDIR="${CMAKE_INSTALL_FULL_DATAROOTDIR}")
# installable assets
file(GLOB_RECURSE INSTALLABLE_ASSETS "assets/install/*")
list(FILTER INSTALLABLE_ASSETS EXCLUDE REGEX "meson.build")
install(FILES ${INSTALLABLE_ASSETS}
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr)
@ -453,19 +620,47 @@ install(
PATTERN "*.hpp"
PATTERN "*.inc")
if(TESTS)
message(STATUS "building tests is enabled TESTS")
if(BUILD_TESTING OR WITH_TESTS)
message(STATUS "Building tests")
# hyprtester
add_subdirectory(hyprtester)
# GTest
find_package(GTest CONFIG REQUIRED)
include(GoogleTest)
file(GLOB_RECURSE TESTFILES "tests/*.cpp")
add_executable(hyprland_gtests ${TESTFILES})
target_compile_options(hyprland_gtests PRIVATE --coverage)
target_link_options(hyprland_gtests PRIVATE --coverage)
target_include_directories(
hyprland_gtests
PUBLIC "./include"
PRIVATE "./src" "./src/include" "./protocols" "${CMAKE_BINARY_DIR}")
target_link_libraries(hyprland_gtests hyprland_lib GTest::gtest_main)
gtest_discover_tests(hyprland_gtests)
# Enable coverage in main hyprland lib
target_compile_options(hyprland_lib PRIVATE --coverage)
target_link_options(hyprland_lib PRIVATE --coverage)
target_link_libraries(hyprland_lib PUBLIC gcov)
# Enable coverage in hyprland exe
target_compile_options(Hyprland PRIVATE --coverage)
target_link_options(Hyprland PRIVATE --coverage)
target_link_libraries(Hyprland gcov)
endif()
if(BUILD_TESTING)
message(STATUS "Testing is enabled")
enable_testing()
add_custom_target(tests)
add_subdirectory(hyprtester)
add_test(
NAME "Main Test"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/hyprtester
COMMAND hyprtester)
add_dependencies(tests hyprland_gtests)
add_dependencies(tests hyprtester)
else()
message(STATUS "building tests is disabled")
message(STATUS "Testing is disabled")
endif()

View file

@ -8,7 +8,7 @@ release:
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
debug:
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -S . -B ./build
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DTESTS=true -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -S . -B ./build
cmake --build ./build --config Debug --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
nopch:

View file

@ -1 +1 @@
0.51.0
0.52.0

View file

@ -1,10 +0,0 @@
globber = run_command('sh', '-c', 'find . -type f -not -name "*.build"', check: true)
files = globber.stdout().strip().split('\n')
foreach file : files
install_data(
file,
install_dir: join_paths(get_option('datadir'), 'hypr'),
install_tag: 'runtime',
)
endforeach

View file

@ -1,7 +0,0 @@
install_data(
'hyprland-portals.conf',
install_dir: join_paths(get_option('datadir'), 'xdg-desktop-portal'),
install_tag: 'runtime',
)
subdir('install')

View file

@ -1,2 +0,0 @@
install_man('Hyprland.1')
install_man('hyprctl.1')

View file

@ -27,7 +27,7 @@ monitor=,preferred,auto,auto
# Set programs that you use
$terminal = kitty
$fileManager = dolphin
$menu = wofi --show drun
$menu = hyprlauncher
#################
@ -159,10 +159,23 @@ animations {
# uncomment all if you wish to use that.
# workspace = w[tv1], gapsout:0, gapsin:0
# workspace = f[1], gapsout:0, gapsin:0
# windowrule = bordersize 0, floating:0, onworkspace:w[tv1]
# windowrule = rounding 0, floating:0, onworkspace:w[tv1]
# windowrule = bordersize 0, floating:0, onworkspace:f[1]
# windowrule = rounding 0, floating:0, onworkspace:f[1]
# windowrule {
# name = no-gaps-wtv1
# match:float = false
# match:workspace = w[tv1]
#
# border_size = 0
# rounding = 0
# }
#
# windowrule {
# name = no-gaps-f1
# match:float = false
# match:workspace = f[1]
#
# border_size = 0
# rounding = 0
# }
# See https://wiki.hypr.land/Configuring/Dwindle-Layout/ for more
dwindle {
@ -224,7 +237,7 @@ $mainMod = SUPER # Sets "Windows" key as main modifier
# Example binds, see https://wiki.hypr.land/Configuring/Binds/ for more
bind = $mainMod, Q, exec, $terminal
bind = $mainMod, C, killactive,
bind = $mainMod, M, exit,
bind = $mainMod, M, exec, command -v hyprshutdown >/dev/null 2>&1 && hyprshutdown || hyprctl dispatch exit
bind = $mainMod, E, exec, $fileManager
bind = $mainMod, V, togglefloating,
bind = $mainMod, R, exec, $menu
@ -294,11 +307,35 @@ bindl = , XF86AudioPrev, exec, playerctl previous
# See https://wiki.hypr.land/Configuring/Window-Rules/ for more
# See https://wiki.hypr.land/Configuring/Workspace-Rules/ for workspace rules
# Example windowrule
# windowrule = float,class:^(kitty)$,title:^(kitty)$
# Example windowrules that are useful
# Ignore maximize requests from apps. You'll probably like this.
windowrule = suppressevent maximize, class:.*
windowrule {
# Ignore maximize requests from all apps. You'll probably like this.
name = suppress-maximize-events
match:class = .*
# Fix some dragging issues with XWayland
windowrule = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0
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
}
# Hyprland-run windowrule
windowrule {
name = move-hyprland-run
match:class = hyprland-run
move = 20 monitor_h-120
float = yes
}

View file

@ -1,7 +1,7 @@
[Desktop Entry]
Name=Hyprland
Comment=An intelligent dynamic tiling Wayland compositor
Exec=Hyprland
Exec=start-hyprland
Type=Application
DesktopNames=Hyprland
Keywords=tiling;wayland;compositor;

View file

@ -1,10 +0,0 @@
install_data(
'hyprland.conf',
install_dir: join_paths(get_option('datadir'), 'hypr'),
install_tag: 'runtime',
)
install_data(
'hyprland.desktop',
install_dir: join_paths(get_option('datadir'), 'wayland-sessions'),
install_tag: 'runtime',
)

234
flake.lock generated
View file

@ -16,11 +16,11 @@
]
},
"locked": {
"lastModified": 1755946532,
"narHash": "sha256-POePremlUY5GyA1zfbtic6XLxDaQcqHN6l+bIxdT5gc=",
"lastModified": 1764714051,
"narHash": "sha256-AjcMlM3UoavFoLzr0YrcvsIxALShjyvwe+o7ikibpCM=",
"owner": "hyprwm",
"repo": "aquamarine",
"rev": "81584dae2df6ac79f6b6dae0ecb7705e95129ada",
"rev": "a43bedcceced5c21ad36578ed823e6099af78214",
"type": "github"
},
"original": {
@ -32,11 +32,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1747046372,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"lastModified": 1761588595,
"narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
"type": "github"
},
"original": {
@ -105,11 +105,11 @@
]
},
"locked": {
"lastModified": 1756891319,
"narHash": "sha256-/e6OXxzbAj/o97Z1dZgHre4bNaVjapDGscAujSCQSbI=",
"lastModified": 1763733840,
"narHash": "sha256-JnET78yl5RvpGuDQy3rCycOCkiKoLr5DN1fPhRNNMco=",
"owner": "hyprwm",
"repo": "hyprgraphics",
"rev": "621e2e00f1736aa18c68f7dfbf2b9cff94b8cc4d",
"rev": "8f1bec691b2d198c60cccabca7a94add2df4ed1a",
"type": "github"
},
"original": {
@ -118,6 +118,45 @@
"type": "github"
}
},
"hyprland-guiutils": {
"inputs": {
"aquamarine": [
"aquamarine"
],
"hyprgraphics": [
"hyprgraphics"
],
"hyprlang": [
"hyprlang"
],
"hyprtoolkit": "hyprtoolkit",
"hyprutils": [
"hyprutils"
],
"hyprwayland-scanner": [
"hyprwayland-scanner"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1764812575,
"narHash": "sha256-1bK1yGgaR82vajUrt6z+BSljQvFn91D74WJ/vJsydtE=",
"owner": "hyprwm",
"repo": "hyprland-guiutils",
"rev": "fd321368a40c782cfa299991e5584ca338e36ebe",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprland-guiutils",
"type": "github"
}
},
"hyprland-protocols": {
"inputs": {
"nixpkgs": [
@ -128,11 +167,11 @@
]
},
"locked": {
"lastModified": 1749046714,
"narHash": "sha256-kymV5FMnddYGI+UjwIw8ceDjdeg7ToDVjbHCvUlhn14=",
"lastModified": 1759610243,
"narHash": "sha256-+KEVnKBe8wz+a6dTLq8YDcF3UrhQElwsYJaVaHXJtoI=",
"owner": "hyprwm",
"repo": "hyprland-protocols",
"rev": "613878cb6f459c5e323aaafe1e6f388ac8a36330",
"rev": "bd153e76f751f150a09328dbdeb5e4fab9d23622",
"type": "github"
},
"original": {
@ -141,67 +180,6 @@
"type": "github"
}
},
"hyprland-qt-support": {
"inputs": {
"hyprlang": [
"hyprland-qtutils",
"hyprlang"
],
"nixpkgs": [
"hyprland-qtutils",
"nixpkgs"
],
"systems": [
"hyprland-qtutils",
"systems"
]
},
"locked": {
"lastModified": 1749154592,
"narHash": "sha256-DO7z5CeT/ddSGDEnK9mAXm1qlGL47L3VAHLlLXoCjhE=",
"owner": "hyprwm",
"repo": "hyprland-qt-support",
"rev": "4c8053c3c888138a30c3a6c45c2e45f5484f2074",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprland-qt-support",
"type": "github"
}
},
"hyprland-qtutils": {
"inputs": {
"hyprland-qt-support": "hyprland-qt-support",
"hyprlang": [
"hyprlang"
],
"hyprutils": [
"hyprland-qtutils",
"hyprlang",
"hyprutils"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1753819801,
"narHash": "sha256-tHe6XeNeVeKapkNM3tcjW4RuD+tB2iwwoogWJOtsqTI=",
"owner": "hyprwm",
"repo": "hyprland-qtutils",
"rev": "b308a818b9dcaa7ab8ccab891c1b84ebde2152bc",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprland-qtutils",
"type": "github"
}
},
"hyprlang": {
"inputs": {
"hyprutils": [
@ -215,11 +193,11 @@
]
},
"locked": {
"lastModified": 1756810301,
"narHash": "sha256-wgZ3VW4VVtjK5dr0EiK9zKdJ/SOqGIBXVG85C3LVxQA=",
"lastModified": 1764612430,
"narHash": "sha256-54ltTSbI6W+qYGMchAgCR6QnC1kOdKXN6X6pJhOWxFg=",
"owner": "hyprwm",
"repo": "hyprlang",
"rev": "3d63fb4a42c819f198deabd18c0c2c1ded1de931",
"rev": "0d00dc118981531aa731150b6ea551ef037acddd",
"type": "github"
},
"original": {
@ -228,6 +206,51 @@
"type": "github"
}
},
"hyprtoolkit": {
"inputs": {
"aquamarine": [
"hyprland-guiutils",
"aquamarine"
],
"hyprgraphics": [
"hyprland-guiutils",
"hyprgraphics"
],
"hyprlang": [
"hyprland-guiutils",
"hyprlang"
],
"hyprutils": [
"hyprland-guiutils",
"hyprutils"
],
"hyprwayland-scanner": [
"hyprland-guiutils",
"hyprwayland-scanner"
],
"nixpkgs": [
"hyprland-guiutils",
"nixpkgs"
],
"systems": [
"hyprland-guiutils",
"systems"
]
},
"locked": {
"lastModified": 1764592794,
"narHash": "sha256-7CcO+wbTJ1L1NBQHierHzheQGPWwkIQug/w+fhTAVuU=",
"owner": "hyprwm",
"repo": "hyprtoolkit",
"rev": "5cfe0743f0e608e1462972303778d8a0859ee63e",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprtoolkit",
"type": "github"
}
},
"hyprutils": {
"inputs": {
"nixpkgs": [
@ -238,11 +261,11 @@
]
},
"locked": {
"lastModified": 1756117388,
"narHash": "sha256-oRDel6pNl/T2tI+nc/USU9ZP9w08dxtl7hiZxa0C/Wc=",
"lastModified": 1764962281,
"narHash": "sha256-rGbEMhTTyTzw4iyz45lch5kXseqnqcEpmrHdy+zHsfo=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "b2ae3204845f5f2f79b4703b441252d8ad2ecfd0",
"rev": "fe686486ac867a1a24f99c753bb40ffed338e4b0",
"type": "github"
},
"original": {
@ -261,11 +284,11 @@
]
},
"locked": {
"lastModified": 1755184602,
"narHash": "sha256-RCBQN8xuADB0LEgaKbfRqwm6CdyopE1xIEhNc67FAbw=",
"lastModified": 1763640274,
"narHash": "sha256-Uan1Nl9i4TF/kyFoHnTq1bd/rsWh4GAK/9/jDqLbY5A=",
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"rev": "b3b0f1f40ae09d4447c20608e5a4faf8bf3c492d",
"rev": "f6cf414ca0e16a4d30198fd670ec86df3c89f671",
"type": "github"
},
"original": {
@ -274,13 +297,39 @@
"type": "github"
}
},
"hyprwire": {
"inputs": {
"hyprutils": [
"hyprutils"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1764872015,
"narHash": "sha256-INI9AVrQG5nJZFvGPSiUZ9FEUZJLfGdsqjF1QSak7Gc=",
"owner": "hyprwm",
"repo": "hyprwire",
"rev": "7997451dcaab7b9d9d442f18985d514ec5891608",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprwire",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1757068644,
"narHash": "sha256-NOrUtIhTkIIumj1E/Rsv1J37Yi3xGStISEo8tZm3KW4=",
"lastModified": 1764950072,
"narHash": "sha256-BmPWzogsG2GsXZtlT+MTcAWeDK5hkbGRZTeZNW42fwA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "8eb28adfa3dc4de28e792e3bf49fcf9007ca8ac9",
"rev": "f61125a668a320878494449750330ca58b78c557",
"type": "github"
},
"original": {
@ -299,11 +348,11 @@
]
},
"locked": {
"lastModified": 1757239681,
"narHash": "sha256-E9spYi9lxm2f1zWQLQ7xQt8Xs2nWgr1T4QM7ZjLFphM=",
"lastModified": 1765016596,
"narHash": "sha256-rhSqPNxDVow7OQKi4qS5H8Au0P4S3AYbawBSmJNUtBQ=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "ab82ab08d6bf74085bd328de2a8722c12d97bd9d",
"rev": "548fc44fca28a5e81c5d6b846e555e6b9c2a5a3c",
"type": "github"
},
"original": {
@ -317,11 +366,12 @@
"aquamarine": "aquamarine",
"hyprcursor": "hyprcursor",
"hyprgraphics": "hyprgraphics",
"hyprland-guiutils": "hyprland-guiutils",
"hyprland-protocols": "hyprland-protocols",
"hyprland-qtutils": "hyprland-qtutils",
"hyprlang": "hyprlang",
"hyprutils": "hyprutils",
"hyprwayland-scanner": "hyprwayland-scanner",
"hyprwire": "hyprwire",
"nixpkgs": "nixpkgs",
"pre-commit-hooks": "pre-commit-hooks",
"systems": "systems",
@ -365,11 +415,11 @@
]
},
"locked": {
"lastModified": 1755354946,
"narHash": "sha256-zdov5f/GcoLQc9qYIS1dUTqtJMeDqmBmo59PAxze6e4=",
"lastModified": 1761431178,
"narHash": "sha256-xzjC1CV3+wpUQKNF+GnadnkeGUCJX+vgaWIZsnz9tzI=",
"owner": "hyprwm",
"repo": "xdg-desktop-portal-hyprland",
"rev": "a10726d6a8d0ef1a0c645378f983b6278c42eaa0",
"rev": "4b8801228ff958d028f588f0c2b911dbf32297f9",
"type": "github"
},
"original": {

View file

@ -35,11 +35,15 @@
inputs.systems.follows = "systems";
};
hyprland-qtutils = {
url = "github:hyprwm/hyprland-qtutils";
hyprland-guiutils = {
url = "github:hyprwm/hyprland-guiutils";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
inputs.aquamarine.follows = "aquamarine";
inputs.hyprgraphics.follows = "hyprgraphics";
inputs.hyprutils.follows = "hyprutils";
inputs.hyprlang.follows = "hyprlang";
inputs.hyprwayland-scanner.follows = "hyprwayland-scanner";
};
hyprlang = {
@ -61,6 +65,13 @@
inputs.systems.follows = "systems";
};
hyprwire = {
url = "github:hyprwm/hyprwire";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
inputs.hyprutils.follows = "hyprutils";
};
xdph = {
url = "github:hyprwm/xdg-desktop-portal-hyprland";
inputs.nixpkgs.follows = "nixpkgs";
@ -148,7 +159,7 @@
# hyprland-packages
hyprland
hyprland-unwrapped
hyprtester
hyprland-with-tests
# hyprland-extras
xdg-desktop-portal-hyprland
;

View file

@ -5,11 +5,32 @@ project(
DESCRIPTION "Control utility for Hyprland"
)
pkg_check_modules(hyprctl_deps REQUIRED IMPORTED_TARGET hyprutils>=0.2.4 re2)
pkg_check_modules(hyprctl_deps REQUIRED IMPORTED_TARGET hyprutils>=0.2.4 hyprwire re2)
add_executable(hyprctl "main.cpp")
file(GLOB_RECURSE HYPRCTL_SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "hw-protocols/*.cpp" "include/*.hpp")
add_executable(hyprctl ${HYPRCTL_SRCFILES})
target_link_libraries(hyprctl PUBLIC PkgConfig::hyprctl_deps)
target_include_directories(hyprctl PRIVATE "hw-protocols")
# Hyprwire
function(hyprprotocol protoPath protoName)
set(path ${CMAKE_CURRENT_SOURCE_DIR}/${protoPath})
add_custom_command(
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-client.cpp
${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-client.hpp
${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-spec.hpp
COMMAND hyprwire-scanner --client ${path}/${protoName}.xml
${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
target_sources(hyprctl PRIVATE hw-protocols/${protoName}-client.cpp
hw-protocols/${protoName}-client.hpp
hw-protocols/${protoName}-spec.hpp)
endfunction()
hyprprotocol(hw-protocols hyprpaper_core)
# binary
install(TARGETS hyprctl)

View file

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="hyprpaper_core" version="1">
<copyright>
BSD 3-Clause License
Copyright (c) 2025, Hypr Development
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</copyright>
<object name="hyprpaper_core_manager" version="1">
<description summary="manager object">
This is the core manager object for hyprpaper operations
</description>
<c2s name="get_wallpaper_object">
<description summary="Get a wallpaper object">
Creates a wallpaper object
</description>
<returns iface="hyprpaper_wallpaper"/>
</c2s>
<s2c name="add_monitor">
<description summary="New monitor added">
Emitted when a new monitor is added.
</description>
<arg name="monitor_name" type="varchar" summary="the monitor's name"/>
</s2c>
<s2c name="remove_monitor">
<description summary="A monitor was removed">
Emitted when a monitor is removed.
</description>
<arg name="monitor_name" type="varchar" summary="the monitor's name"/>
</s2c>
<c2s name="destroy" destructor="true">
<description summary="Destroy this object">
Destroys this object. Children remain alive until destroyed.
</description>
</c2s>
</object>
<enum name="wallpaper_fit_mode">
<value idx="0" name="stretch"/>
<value idx="1" name="cover"/>
<value idx="2" name="contain"/>
<value idx="3" name="tile"/>
</enum>
<enum name="wallpaper_errors">
<value idx="0" name="inert_wallpaper_object" description="attempted to use an inert wallpaper object"/>
</enum>
<enum name="applying_error">
<value idx="0" name="invalid_path" description="path provided was invalid"/>
<value idx="1" name="invalid_monitor" description="monitor provided was invalid"/>
<value idx="2" name="unknown_error" description="unknown error"/>
</enum>
<object name="hyprpaper_wallpaper" version="1">
<description summary="wallpaper object">
This is an object describing a wallpaper
</description>
<c2s name="path">
<description summary="Set a path">
Set a file path for the wallpaper. This has to be an absolute path from the fs root.
This is required.
</description>
<arg name="wallpaper" type="varchar" summary="path"/>
</c2s>
<c2s name="fit_mode">
<description summary="Set a fit mode">
Set a fit mode for the wallpaper. This is set to cover by default.
</description>
<arg name="fit_mode" type="enum" interface="wallpaper_fit_mode" summary="path"/>
</c2s>
<c2s name="monitor_name">
<description summary="Set the monitor name">
Set a monitor for the wallpaper. Setting this to empty (or not setting at all) will
treat this as a wildcard fallback.
See hyprpaper_core_manager.add_monitor and hyprpaper_core_manager.remove_monitor
for tracking monitor names.
</description>
<arg name="monitor_name" type="varchar" summary="monitor name"/>
</c2s>
<c2s name="apply">
<description summary="Apply this wallpaper">
Applies this object's state to the wallpaper state. Will emit .success on success,
and .failed on failure.
This object becomes inert after .succeess or .failed, the only valid operation
is to destroy it afterwards.
</description>
</c2s>
<s2c name="success">
<description summary="Operation succeeded">
Wallpaper was applied successfully.
</description>
</s2c>
<s2c name="failed">
<description summary="Operation failed">
Wallpaper was not applied. See the error field for more information.
</description>
<arg name="error" type="enum" interface="hyprpaper_wallpaper_application_error" summary="path"/>
</s2c>
<c2s name="destroy" destructor="true">
<description summary="Destroy this object">
Destroys this object.
</description>
</c2s>
</object>
</protocol>

View file

@ -1,27 +0,0 @@
executable(
'hyprctl',
'main.cpp',
dependencies: [
dependency('hyprutils', version: '>= 0.1.1'),
dependency('re2', required: true)
],
install: true,
)
install_data(
'hyprctl.bash',
install_dir: join_paths(get_option('datadir'), 'bash-completion/completions'),
install_tag: 'runtime',
rename: 'hyprctl',
)
install_data(
'hyprctl.fish',
install_dir: join_paths(get_option('datadir'), 'fish/vendor_completions.d'),
install_tag: 'runtime',
)
install_data(
'hyprctl.zsh',
install_dir: join_paths(get_option('datadir'), 'zsh/site-functions'),
install_tag: 'runtime',
rename: '_hyprctl',
)

View file

@ -74,11 +74,8 @@ flags:
const std::string_view HYPRPAPER_HELP = R"#(usage: hyprctl [flags] hyprpaper <request>
requests:
listactive Lists all active images
listloaded Lists all loaded images
preload <path> Preloads image
unload <path> Unloads image. Pass 'all' as path to unload all images
wallpaper Issue a wallpaper to call a config wallpaper dynamically
wallpaper Issue a wallpaper to call a config wallpaper dynamically.
Arguments are [mon],[path],[fit_mode]. Fit mode is optional.
flags:
See 'hyprctl --help')#";

View file

@ -0,0 +1,11 @@
#pragma once
#include <hyprutils/memory/SharedPtr.hpp>
#include <hyprutils/memory/UniquePtr.hpp>
#include <hyprutils/memory/Atomic.hpp>
using namespace Hyprutils::Memory;
#define SP CSharedPointer
#define WP CWeakPointer
#define UP CUniquePointer

View file

@ -0,0 +1,148 @@
#include "Hyprpaper.hpp"
#include "../helpers/Memory.hpp"
#include <optional>
#include <format>
#include <filesystem>
#include <hyprpaper_core-client.hpp>
#include <hyprutils/string/VarList2.hpp>
using namespace Hyprutils::String;
using namespace std::string_literals;
constexpr const char* SOCKET_NAME = ".hyprpaper.sock";
static SP<CCHyprpaperCoreImpl> g_coreImpl;
constexpr const uint32_t PROTOCOL_VERSION_SUPPORTED = 1;
//
static hyprpaperCoreWallpaperFitMode fitFromString(const std::string_view& sv) {
if (sv == "contain")
return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_CONTAIN;
if (sv == "fit" || sv == "stretch")
return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_STRETCH;
if (sv == "tile")
return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_TILE;
return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_COVER;
}
static std::expected<std::string, std::string> resolvePath(const std::string_view& sv) {
std::error_code ec;
auto can = std::filesystem::canonical(sv, ec);
if (ec)
return std::unexpected(std::format("invalid path: {}", ec.message()));
return can;
}
static std::expected<std::string, std::string> getFullPath(const std::string_view& sv) {
if (sv.empty())
return std::unexpected("empty path");
if (sv[0] == '~') {
static auto HOME = getenv("HOME");
if (!HOME || HOME[0] == '\0')
return std::unexpected("home path but no $HOME");
return resolvePath(std::string{HOME} + "/"s + std::string{sv.substr(1)});
}
return resolvePath(sv);
}
std::expected<void, std::string> Hyprpaper::makeHyprpaperRequest(const std::string_view& rq) {
if (!rq.contains(' '))
return std::unexpected("Invalid request");
if (!rq.starts_with("/hyprpaper "))
return std::unexpected("Invalid request");
std::string_view LHS, RHS;
auto spacePos = rq.find(' ', 12);
LHS = rq.substr(11, spacePos - 11);
RHS = rq.substr(spacePos + 1);
if (LHS != "wallpaper")
return std::unexpected("Unknown hyprpaper request");
CVarList2 args(std::string{RHS}, 0, ',');
const std::string MONITOR = std::string{args[0]};
const auto& PATH_RAW = args[1];
const auto& FIT = args[2];
if (PATH_RAW.empty())
return std::unexpected("not enough args");
const auto RTDIR = getenv("XDG_RUNTIME_DIR");
if (!RTDIR || RTDIR[0] == '\0')
return std::unexpected("can't send: no XDG_RUNTIME_DIR");
const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
if (!HIS || HIS[0] == '\0')
return std::unexpected("can't send: no HYPRLAND_INSTANCE_SIGNATURE (not running under hyprland)");
const auto PATH = getFullPath(PATH_RAW);
if (!PATH)
return std::unexpected(std::format("bad path: {}", PATH_RAW));
auto socketPath = RTDIR + "/hypr/"s + HIS + "/"s + SOCKET_NAME;
auto socket = Hyprwire::IClientSocket::open(socketPath);
if (!socket)
return std::unexpected("can't send: failed to connect to hyprpaper (is it running?)");
g_coreImpl = makeShared<CCHyprpaperCoreImpl>(1);
socket->addImplementation(g_coreImpl);
if (!socket->waitForHandshake())
return std::unexpected("can't send: wire handshake failed");
auto spec = socket->getSpec(g_coreImpl->protocol()->specName());
if (!spec)
return std::unexpected("can't send: hyprpaper doesn't have the spec?!");
auto manager = makeShared<CCHyprpaperCoreManagerObject>(socket->bindProtocol(g_coreImpl->protocol(), PROTOCOL_VERSION_SUPPORTED));
if (!manager)
return std::unexpected("wire error: couldn't create manager");
auto wallpaper = makeShared<CCHyprpaperWallpaperObject>(manager->sendGetWallpaperObject());
if (!wallpaper)
return std::unexpected("wire error: couldn't create wallpaper object");
bool canExit = false;
std::optional<std::string> err;
wallpaper->setFailed([&canExit, &err](uint32_t code) {
canExit = true;
err = std::format("failed to set wallpaper, code {}", code);
});
wallpaper->setSuccess([&canExit]() { canExit = true; });
wallpaper->sendPath(PATH->c_str());
wallpaper->sendMonitorName(MONITOR.c_str());
if (!FIT.empty())
wallpaper->sendFitMode(fitFromString(FIT));
wallpaper->sendApply();
while (!canExit) {
socket->dispatchEvents(true);
}
if (err)
return std::unexpected(*err);
return {};
}

View file

@ -0,0 +1,8 @@
#pragma once
#include <expected>
#include <string>
namespace Hyprpaper {
std::expected<void, std::string> makeHyprpaperRequest(const std::string_view& rq);
};

View file

@ -31,6 +31,7 @@ using namespace Hyprutils::String;
using namespace Hyprutils::Memory;
#include "Strings.hpp"
#include "hyprpaper/Hyprpaper.hpp"
std::string instanceSignature;
bool quiet = false;
@ -305,10 +306,6 @@ int requestIPC(std::string_view filename, std::string_view arg) {
return 0;
}
int requestHyprpaper(std::string_view arg) {
return requestIPC(".hyprpaper.sock", arg);
}
int requestHyprsunset(std::string_view arg) {
return requestIPC(".hyprsunset.sock", arg);
}
@ -500,9 +497,12 @@ int main(int argc, char** argv) {
if (fullRequest.contains("/--batch"))
batchRequest(fullRequest, json);
else if (fullRequest.contains("/hyprpaper"))
exitStatus = requestHyprpaper(fullRequest);
else if (fullRequest.contains("/hyprsunset"))
else if (fullRequest.contains("/hyprpaper")) {
auto result = Hyprpaper::makeHyprpaperRequest(fullRequest);
if (!result)
log(std::format("error: {}", result.error()));
exitStatus = !result;
} else if (fullRequest.contains("/hyprsunset"))
exitStatus = requestHyprsunset(fullRequest);
else if (fullRequest.contains("/switchxkblayout"))
exitStatus = request(fullRequest, 2);

View file

@ -4,4 +4,5 @@ Name: Hyprland
URL: https://github.com/hyprwm/Hyprland
Description: Hyprland header files
Version: @HYPRLAND_VERSION@
Requires: aquamarine >= @AQUAMARINE_MINIMUM_VERSION@, hyprcursor >= @HYPRCURSOR_MINIMUM_VERSION@, hyprgraphics >= @HYPRGRAPHICS_MINIMUM_VERSION@, hyprlang >= @HYPRLANG_MINIMUM_VERSION@, hyprutils >= @HYPRUTILS_MINIMUM_VERSION@, libdrm, egl, cairo, xkbcommon >= @XKBCOMMON_MINIMUM_VERSION@, libinput >= @LIBINPUT_MINIMUM_VERSION@, wayland-server >= @WAYLAND_SERVER_MINIMUM_VERSION@@PKGCONFIG_XWAYLAND_DEPENDENCIES@
Cflags: -I${prefix} -I${prefix}/hyprland/protocols -I${prefix}/hyprland

View file

@ -11,9 +11,9 @@ set(CMAKE_CXX_STANDARD 23)
pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.7.0)
find_package(glaze QUIET)
find_package(glaze 6.0.0 QUIET)
if (NOT glaze_FOUND)
set(GLAZE_VERSION v5.1.1)
set(GLAZE_VERSION v6.1.0)
message(STATUS "glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent")
include(FetchContent)
FetchContent_Declare(

View file

@ -18,6 +18,9 @@ static std::string getTempRoot() {
const auto STR = ENV + std::string{"/hyprpm/"};
if (!std::filesystem::exists(STR))
mkdir(STR.c_str(), S_IRWXU);
return STR;
}
@ -90,6 +93,7 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) {
auto DATA = toml::table{
{"repository", toml::table{
{"name", repo.name},
{"author", repo.author},
{"hash", repo.hash},
{"url", repo.url},
{"rev", repo.rev}
@ -119,31 +123,32 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) {
Debug::die("{}", failureString("Failed to write plugin state"));
}
bool DataState::pluginRepoExists(const std::string& urlOrName) {
bool DataState::pluginRepoExists(const SPluginRepoIdentifier identifier) {
ensureStateStoreExists();
for (const auto& stateFile : getPluginStates()) {
const auto STATE = toml::parse_file(stateFile.c_str());
const auto NAME = STATE["repository"]["name"].value_or("");
const auto URL = STATE["repository"]["url"].value_or("");
const auto STATE = toml::parse_file(stateFile.c_str());
const auto NAME = STATE["repository"]["name"].value_or("");
const auto AUTHOR = STATE["repository"]["author"].value_or("");
const auto URL = STATE["repository"]["url"].value_or("");
if (URL == urlOrName || NAME == urlOrName)
if (identifier.matches(URL, NAME, AUTHOR))
return true;
}
return false;
}
void DataState::removePluginRepo(const std::string& urlOrName) {
void DataState::removePluginRepo(const SPluginRepoIdentifier identifier) {
ensureStateStoreExists();
for (const auto& stateFile : getPluginStates()) {
const auto STATE = toml::parse_file(stateFile.c_str());
const auto NAME = STATE["repository"]["name"].value_or("");
const auto URL = STATE["repository"]["url"].value_or("");
if (URL == urlOrName || NAME == urlOrName) {
const auto STATE = toml::parse_file(stateFile.c_str());
const auto NAME = STATE["repository"]["name"].value_or("");
const auto AUTHOR = STATE["repository"]["author"].value_or("");
const auto URL = STATE["repository"]["url"].value_or("");
if (identifier.matches(URL, NAME, AUTHOR)) {
// unload the plugins!!
for (const auto& file : std::filesystem::directory_iterator(stateFile.parent_path())) {
if (!file.path().string().ends_with(".so"))
@ -178,7 +183,7 @@ void DataState::updateGlobalState(const SGlobalState& state) {
// clang-format off
auto DATA = toml::table{
{"state", toml::table{
{"hash", state.headersHashCompiled},
{"hash", state.headersAbiCompiled},
{"dont_warn_install", state.dontWarnInstall}
}}
};
@ -203,8 +208,8 @@ SGlobalState DataState::getGlobalState() {
auto DATA = toml::parse_file(stateFile.c_str());
SGlobalState state;
state.headersHashCompiled = DATA["state"]["hash"].value_or("");
state.dontWarnInstall = DATA["state"]["dont_warn_install"].value_or(false);
state.headersAbiCompiled = DATA["state"]["hash"].value_or("");
state.dontWarnInstall = DATA["state"]["dont_warn_install"].value_or(false);
return state;
}
@ -216,16 +221,18 @@ std::vector<SPluginRepository> DataState::getAllRepositories() {
for (const auto& stateFile : getPluginStates()) {
const auto STATE = toml::parse_file(stateFile.c_str());
const auto NAME = STATE["repository"]["name"].value_or("");
const auto URL = STATE["repository"]["url"].value_or("");
const auto REV = STATE["repository"]["rev"].value_or("");
const auto HASH = STATE["repository"]["hash"].value_or("");
const auto NAME = STATE["repository"]["name"].value_or("");
const auto AUTHOR = STATE["repository"]["author"].value_or("");
const auto URL = STATE["repository"]["url"].value_or("");
const auto REV = STATE["repository"]["rev"].value_or("");
const auto HASH = STATE["repository"]["hash"].value_or("");
SPluginRepository repo;
repo.hash = HASH;
repo.name = NAME;
repo.url = URL;
repo.rev = REV;
repo.hash = HASH;
repo.name = NAME;
repo.author = AUTHOR;
repo.url = URL;
repo.rev = REV;
for (const auto& [key, val] : STATE) {
if (key == "repository")
@ -244,7 +251,7 @@ std::vector<SPluginRepository> DataState::getAllRepositories() {
return repos;
}
bool DataState::setPluginEnabled(const std::string& name, bool enabled) {
bool DataState::setPluginEnabled(const SPluginRepoIdentifier identifier, bool enabled) {
ensureStateStoreExists();
for (const auto& stateFile : getPluginStates()) {
@ -253,8 +260,17 @@ bool DataState::setPluginEnabled(const std::string& name, bool enabled) {
if (key == "repository")
continue;
if (key.str() != name)
continue;
switch (identifier.type) {
case IDENTIFIER_NAME:
if (key.str() != identifier.name)
continue;
break;
case IDENTIFIER_AUTHOR_NAME:
if (STATE["repository"]["author"] != identifier.author || key.str() != identifier.name)
continue;
break;
default: return false;
}
const auto FAILED = STATE[key]["failed"].value_or(false);

View file

@ -5,8 +5,8 @@
#include "Plugin.hpp"
struct SGlobalState {
std::string headersHashCompiled = "";
bool dontWarnInstall = false;
std::string headersAbiCompiled = "";
bool dontWarnInstall = false;
};
namespace DataState {
@ -15,11 +15,11 @@ namespace DataState {
std::vector<std::filesystem::path> getPluginStates();
void ensureStateStoreExists();
void addNewPluginRepo(const SPluginRepository& repo);
void removePluginRepo(const std::string& urlOrName);
bool pluginRepoExists(const std::string& urlOrName);
void removePluginRepo(const SPluginRepoIdentifier identifier);
bool pluginRepoExists(const SPluginRepoIdentifier identifier);
void updateGlobalState(const SGlobalState& state);
void purgeAllCache();
SGlobalState getGlobalState();
bool setPluginEnabled(const std::string& name, bool enabled);
bool setPluginEnabled(const SPluginRepoIdentifier identifier, bool enabled);
std::vector<SPluginRepository> getAllRepositories();
};
};

View file

@ -0,0 +1,48 @@
#include "Plugin.hpp"
SPluginRepoIdentifier SPluginRepoIdentifier::fromUrl(const std::string& url) {
return SPluginRepoIdentifier{.type = IDENTIFIER_URL, .url = url};
}
SPluginRepoIdentifier SPluginRepoIdentifier::fromName(const std::string& name) {
return SPluginRepoIdentifier{.type = IDENTIFIER_NAME, .name = name};
}
SPluginRepoIdentifier SPluginRepoIdentifier::fromAuthorName(const std::string& author, const std::string& name) {
return SPluginRepoIdentifier{.type = IDENTIFIER_AUTHOR_NAME, .name = name, .author = author};
}
SPluginRepoIdentifier SPluginRepoIdentifier::fromString(const std::string& string) {
if (string.find(':') != std::string::npos) {
return SPluginRepoIdentifier{.type = IDENTIFIER_URL, .url = string};
} else {
auto slashPos = string.find('/');
if (slashPos != std::string::npos) {
std::string author = string.substr(0, slashPos);
std::string name = string.substr(slashPos + 1, string.size() - slashPos - 1);
return SPluginRepoIdentifier{.type = IDENTIFIER_AUTHOR_NAME, .name = name, .author = author};
} else {
return SPluginRepoIdentifier{.type = IDENTIFIER_NAME, .name = string};
}
}
}
std::string SPluginRepoIdentifier::toString() const {
switch (type) {
case IDENTIFIER_NAME: return name;
case IDENTIFIER_AUTHOR_NAME: return author + '/' + name;
case IDENTIFIER_URL: return url;
}
return "";
}
bool SPluginRepoIdentifier::matches(const std::string& url, const std::string& name, const std::string& author) const {
switch (type) {
case IDENTIFIER_URL: return this->url == url;
case IDENTIFIER_NAME: return this->name == name;
case IDENTIFIER_AUTHOR_NAME: return this->author == author && this->name == name;
}
return false;
}

View file

@ -14,6 +14,27 @@ struct SPluginRepository {
std::string url;
std::string rev;
std::string name;
std::string author;
std::vector<SPlugin> plugins;
std::string hash;
};
};
enum ePluginRepoIdentifierType {
IDENTIFIER_URL,
IDENTIFIER_NAME,
IDENTIFIER_AUTHOR_NAME
};
struct SPluginRepoIdentifier {
ePluginRepoIdentifierType type;
std::string url = "";
std::string name = "";
std::string author = "";
static SPluginRepoIdentifier fromString(const std::string& string);
static SPluginRepoIdentifier fromUrl(const std::string& Url);
static SPluginRepoIdentifier fromName(const std::string& name);
static SPluginRepoIdentifier fromAuthorName(const std::string& author, const std::string& name);
std::string toString() const;
bool matches(const std::string& url, const std::string& name, const std::string& author) const;
};

View file

@ -11,6 +11,7 @@
#include <cstdio>
#include <iostream>
#include <filesystem>
#include <string>
#include <print>
#include <fstream>
#include <algorithm>
@ -78,40 +79,30 @@ SHyprlandVersion CPluginManager::getHyprlandVersion(bool running) {
else
onceInstalled = true;
const auto HLVERCALL = running ? NHyprlandSocket::send("/version") : execAndGet("Hyprland --version");
if (m_bVerbose)
std::println("{}", verboseString("{} version returned: {}", running ? "running" : "installed", HLVERCALL));
const auto HLVERCALL = running ? NHyprlandSocket::send("j/version") : execAndGet("Hyprland --version-json");
if (!HLVERCALL.contains("Tag:")) {
std::println(stderr, "\n{}", failureString("You don't seem to be running Hyprland."));
auto jsonQuery = glz::read_json<glz::generic>(HLVERCALL);
if (!jsonQuery) {
std::println("{}", failureString("failed to get the current hyprland version. Are you running hyprland?"));
return SHyprlandVersion{};
}
std::string hlcommit = HLVERCALL.substr(HLVERCALL.find("at commit") + 10);
hlcommit = hlcommit.substr(0, hlcommit.find_first_of(' '));
auto hlbranch = (*jsonQuery)["branch"].get_string();
auto hlcommit = (*jsonQuery)["commit"].get_string();
auto abiHash = (*jsonQuery)["abiHash"].get_string();
auto hldate = (*jsonQuery)["commit_date"].get_string();
auto hlcommits = (*jsonQuery)["commits"].get_string();
std::string hlbranch = HLVERCALL.substr(HLVERCALL.find("from branch") + 12);
hlbranch = hlbranch.substr(0, hlbranch.find(" at commit "));
std::string hldate = HLVERCALL.substr(HLVERCALL.find("Date: ") + 6);
hldate = hldate.substr(0, hldate.find('\n'));
std::string hlcommits;
if (HLVERCALL.contains("commits:")) {
hlcommits = HLVERCALL.substr(HLVERCALL.find("commits:") + 9);
hlcommits = hlcommits.substr(0, hlcommits.find(' '));
}
int commits = 0;
size_t commits = 0;
try {
commits = std::stoi(hlcommits);
commits = std::stoull(hlcommits);
} catch (...) { ; }
if (m_bVerbose)
std::println("{}", verboseString("parsed commit {} at branch {} on {}, commits {}", hlcommit, hlbranch, hldate, commits));
auto ver = SHyprlandVersion{hlbranch, hlcommit, hldate, commits};
auto ver = SHyprlandVersion{hlbranch, hlcommit, hldate, abiHash, commits};
if (running)
verRunning = ver;
@ -146,7 +137,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
return false;
}
if (DataState::pluginRepoExists(url)) {
if (DataState::pluginRepoExists(SPluginRepoIdentifier::fromUrl(url))) {
std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. Repository already installed."));
return false;
}
@ -161,7 +152,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
DataState::updateGlobalState(GLOBALSTATE);
}
if (GLOBALSTATE.headersHashCompiled.empty()) {
if (GLOBALSTATE.headersAbiCompiled.empty()) {
std::println("\n{}", failureString("Cannot find headers in the global state. Try running hyprpm update first."));
return false;
}
@ -343,10 +334,13 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
std::string repohash = execAndGet("cd " + m_szWorkingPluginDirectory + " && git rev-parse HEAD");
if (repohash.length() > 0)
repohash.pop_back();
repo.name = pManifest->m_repository.name.empty() ? url.substr(url.find_last_of('/') + 1) : pManifest->m_repository.name;
repo.url = url;
repo.rev = rev;
repo.hash = repohash;
auto lastSlash = url.find_last_of('/');
auto secondLastSlash = url.find_last_of('/', lastSlash - 1);
repo.name = pManifest->m_repository.name.empty() ? url.substr(lastSlash + 1) : pManifest->m_repository.name;
repo.author = url.substr(secondLastSlash + 1, lastSlash - secondLastSlash - 1);
repo.url = url;
repo.rev = rev;
repo.hash = repohash;
for (auto const& p : pManifest->m_plugins) {
repo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, false, p.failed});
}
@ -366,13 +360,13 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
return true;
}
bool CPluginManager::removePluginRepo(const std::string& urlOrName) {
if (!DataState::pluginRepoExists(urlOrName)) {
bool CPluginManager::removePluginRepo(const SPluginRepoIdentifier identifier) {
if (!DataState::pluginRepoExists(identifier)) {
std::println(stderr, "\n{}", failureString("Could not remove the repository. Repository is not installed."));
return false;
}
std::cout << Colors::YELLOW << "!" << Colors::RESET << Colors::RED << " removing a plugin repository: " << Colors::RESET << urlOrName << "\n "
std::cout << Colors::YELLOW << "!" << Colors::RESET << Colors::RED << " removing a plugin repository: " << Colors::RESET << identifier.toString() << "\n "
<< "Are you sure? [Y/n] ";
std::fflush(stdout);
std::string input;
@ -383,7 +377,7 @@ bool CPluginManager::removePluginRepo(const std::string& urlOrName) {
return false;
}
DataState::removePluginRepo(urlOrName);
DataState::removePluginRepo(identifier);
return true;
}
@ -444,11 +438,16 @@ eHeadersErrors CPluginManager::headersValid() {
if (hash != HLVER.hash)
return HEADERS_MISMATCHED;
// check ABI hash too
const auto GLOBALSTATE = DataState::getGlobalState();
if (GLOBALSTATE.headersAbiCompiled != HLVER.abiHash)
return HEADERS_ABI_MISMATCH;
return HEADERS_OK;
}
bool CPluginManager::updateHeaders(bool force) {
DataState::ensureStateStoreExists();
const auto HLVER = getHyprlandVersion(false);
@ -589,14 +588,15 @@ bool CPluginManager::updateHeaders(bool force) {
std::filesystem::remove_all(WORKINGDIR);
auto HEADERSVALID = headersValid();
if (HEADERSVALID == HEADERS_OK) {
if (HEADERSVALID == HEADERS_OK || HEADERSVALID == HEADERS_MISMATCHED || HEADERSVALID == HEADERS_ABI_MISMATCH) {
progress.printMessageAbove(successString("installed headers"));
progress.m_iSteps = 5;
progress.m_szCurrentMessage = "Done!";
progress.print();
auto GLOBALSTATE = DataState::getGlobalState();
GLOBALSTATE.headersHashCompiled = HLVER.hash;
auto GLOBALSTATE = DataState::getGlobalState();
GLOBALSTATE.headersAbiCompiled = HLVER.abiHash;
DataState::updateGlobalState(GLOBALSTATE);
std::print("\n");
@ -775,7 +775,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
const auto OLDPLUGINIT = std::find_if(repo.plugins.begin(), repo.plugins.end(), [&](const auto& other) { return other.name == p.name; });
newrepo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false});
}
DataState::removePluginRepo(newrepo.name);
DataState::removePluginRepo(SPluginRepoIdentifier::fromName(newrepo.name));
DataState::addNewPluginRepo(newrepo);
std::filesystem::remove_all(m_szWorkingPluginDirectory);
@ -787,8 +787,8 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
progress.m_szCurrentMessage = "Updating global state...";
progress.print();
auto GLOBALSTATE = DataState::getGlobalState();
GLOBALSTATE.headersHashCompiled = HLVER.hash;
auto GLOBALSTATE = DataState::getGlobalState();
GLOBALSTATE.headersAbiCompiled = HLVER.abiHash;
DataState::updateGlobalState(GLOBALSTATE);
progress.m_iSteps++;
@ -800,17 +800,23 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
return true;
}
bool CPluginManager::enablePlugin(const std::string& name) {
bool ret = DataState::setPluginEnabled(name, true);
bool CPluginManager::enablePlugin(const SPluginRepoIdentifier identifier) {
bool ret = false;
switch (identifier.type) {
case IDENTIFIER_NAME:
case IDENTIFIER_AUTHOR_NAME: ret = DataState::setPluginEnabled(identifier, true); break;
default: return false;
}
if (ret)
std::println("{}", successString("Enabled {}", name));
std::println("{}", successString("Enabled {}", identifier.name));
return ret;
}
bool CPluginManager::disablePlugin(const std::string& name) {
bool ret = DataState::setPluginEnabled(name, false);
bool CPluginManager::disablePlugin(const SPluginRepoIdentifier identifier) {
bool ret = DataState::setPluginEnabled(identifier, false);
if (ret)
std::println("{}", successString("Disabled {}", name));
std::println("{}", successString("Disabled {}", identifier.name));
return ret;
}
@ -828,7 +834,7 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState(bool forceReload)
}
const auto HYPRPMPATH = DataState::getDataStatePath();
const auto json = glz::read_json<glz::json_t::array_t>(NHyprlandSocket::send("j/plugins list"));
const auto json = glz::read_json<glz::generic::array_t>(NHyprlandSocket::send("j/plugins list"));
if (!json) {
std::println(stderr, "PluginManager: couldn't parse plugin list output");
return LOADSTATE_FAIL;
@ -913,9 +919,9 @@ bool CPluginManager::loadUnloadPlugin(const std::string& path, bool load) {
auto state = DataState::getGlobalState();
auto HLVER = getHyprlandVersion(true);
if (state.headersHashCompiled != HLVER.hash) {
if (state.headersAbiCompiled != HLVER.abiHash) {
if (load)
std::println("{}", infoString("Running Hyprland version ({}) differs from plugin state ({}), please restart Hyprland.", HLVER.hash, state.headersHashCompiled));
std::println("{}", infoString("Running Hyprland version ({}) differs from plugin state ({}), please restart Hyprland.", HLVER.hash, state.headersAbiCompiled));
return false;
}
@ -931,7 +937,7 @@ void CPluginManager::listAllPlugins() {
const auto REPOS = DataState::getAllRepositories();
for (auto const& r : REPOS) {
std::println("{}", infoString("Repository {}:", r.name));
std::println("{}", infoString("Repository {} (by {}):", r.name, r.author));
for (auto const& p : r.plugins) {
std::println(" │ Plugin {}", p.name);
@ -956,6 +962,7 @@ std::string CPluginManager::headerError(const eHeadersErrors err) {
case HEADERS_MISMATCHED: return failureString("Headers version mismatch. Please run hyprpm update to fix those.\n");
case HEADERS_NOT_HYPRLAND: return failureString("It doesn't seem you are running on hyprland.\n");
case HEADERS_MISSING: return failureString("Headers missing. Please run hyprpm update to fix those.\n");
case HEADERS_ABI_MISMATCH: return failureString("ABI is mismatched. Please run hyprpm update to fix that.\n");
case HEADERS_DUPLICATED: {
return failureString("Headers duplicated!!! This is a very bad sign.\n"
"This could be due to e.g. installing hyprland manually while a system package of hyprland is also installed.\n"

View file

@ -2,8 +2,10 @@
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "Plugin.hpp"
enum eHeadersErrors {
HEADERS_OK = 0,
@ -11,6 +13,7 @@ enum eHeadersErrors {
HEADERS_MISSING,
HEADERS_CORRUPTED,
HEADERS_MISMATCHED,
HEADERS_ABI_MISMATCH,
HEADERS_DUPLICATED
};
@ -36,6 +39,7 @@ struct SHyprlandVersion {
std::string branch;
std::string hash;
std::string date;
std::string abiHash;
int commits = 0;
};
@ -44,7 +48,7 @@ class CPluginManager {
CPluginManager();
bool addNewPluginRepo(const std::string& url, const std::string& rev);
bool removePluginRepo(const std::string& urlOrName);
bool removePluginRepo(const SPluginRepoIdentifier identifier);
eHeadersErrors headersValid();
bool updateHeaders(bool force = false);
@ -52,8 +56,8 @@ class CPluginManager {
void listAllPlugins();
bool enablePlugin(const std::string& name);
bool disablePlugin(const std::string& name);
bool enablePlugin(const SPluginRepoIdentifier identifier);
bool disablePlugin(const SPluginRepoIdentifier identifier);
ePluginLoadStateReturn ensurePluginsLoadState(bool forceReload = false);
bool loadUnloadPlugin(const std::string& path, bool load);

View file

@ -13,25 +13,25 @@ using namespace Hyprutils::Utils;
constexpr std::string_view HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager
add [url] [git rev] Install a new plugin repository from git. Git revision
is optional, when set, commit locks are ignored.
remove [url/name] Remove an installed plugin repository.
enable [name] Enable a plugin.
disable [name] Disable a plugin.
update Check and update all plugins if needed.
reload Reload hyprpm state. Ensure all enabled plugins are loaded.
list List all installed plugins.
purge-cache Remove the entire hyprpm cache, built plugins, hyprpm settings and headers.
add <url> [git rev] Install a new plugin repository from git. Git revision
is optional, when set, commit locks are ignored.
remove <url|name|author/name> Remove an installed plugin repository.
enable <name|author/name> Enable a plugin.
disable <name|author/name> Disable a plugin.
update Check and update all plugins if needed.
reload Reload hyprpm state. Ensure all enabled plugins are loaded.
list List all installed plugins.
purge-cache Remove the entire hyprpm cache, built plugins, hyprpm settings and headers.
Flags:
--notify | -n Send a hyprland notification for important events (including both successes and fail events).
--notify-fail | -nn Send a hyprland notification for fail events only.
--help | -h Show this menu.
--verbose | -v Enable too much logging.
--force | -f Force an operation ignoring checks (e.g. update -f).
--no-shallow | -s Disable shallow cloning of Hyprland sources.
--hl-url | Pass a custom hyprland source url.
--notify | -n Send a hyprland notification confirming successful plugin load.
Warnings/Errors trigger notifications regardless of this flag.
--help | -h Show this menu.
--verbose | -v Enable too much logging.
--force | -f Force an operation ignoring checks (e.g. update -f).
--no-shallow | -s Disable shallow cloning of Hyprland sources.
--hl-url | Pass a custom hyprland source url.
)#";
@ -47,7 +47,7 @@ int main(int argc, char** argv, char** envp) {
}
std::vector<std::string> command;
bool notify = false, notifyFail = false, verbose = false, force = false, noShallow = false;
bool notify = false, verbose = false, force = false, noShallow = false;
std::string customHlUrl;
for (int i = 1; i < argc; ++i) {
@ -58,7 +58,9 @@ int main(int argc, char** argv, char** envp) {
} else if (ARGS[i] == "--notify" || ARGS[i] == "-n") {
notify = true;
} else if (ARGS[i] == "--notify-fail" || ARGS[i] == "-nn") {
notifyFail = notify = true;
// TODO: Deprecated since v.053.0. Remove in version>0.56.0
std::println(stderr, "{}", failureString("Deprececated flag."));
g_pPluginManager->notify(ICON_INFO, 0, 10000, "[hyprpm] -n flag is deprecated, see hyprpm --help.");
} else if (ARGS[i] == "--verbose" || ARGS[i] == "-v") {
verbose = true;
} else if (ARGS[i] == "--no-shallow" || ARGS[i] == "-s") {
@ -104,7 +106,7 @@ int main(int argc, char** argv, char** envp) {
const auto HLVER = g_pPluginManager->getHyprlandVersion();
auto GLOBALSTATE = DataState::getGlobalState();
if (GLOBALSTATE.headersHashCompiled != HLVER.hash) {
if (GLOBALSTATE.headersAbiCompiled != HLVER.abiHash) {
std::println(stderr, "{}", failureString("Headers outdated, please run hyprpm update."));
return 1;
}
@ -124,7 +126,7 @@ int main(int argc, char** argv, char** envp) {
NSys::root::cacheSudo();
CScopeGuard x([] { NSys::root::dropSudo(); });
return g_pPluginManager->removePluginRepo(command[1]) ? 0 : 1;
return g_pPluginManager->removePluginRepo(SPluginRepoIdentifier::fromString(command[1])) ? 0 : 1;
} else if (command[0] == "update") {
NSys::root::cacheSudo();
CScopeGuard x([] { NSys::root::dropSudo(); });
@ -135,7 +137,7 @@ int main(int argc, char** argv, char** envp) {
if (headers) {
const auto HLVER = g_pPluginManager->getHyprlandVersion(false);
auto GLOBALSTATE = DataState::getGlobalState();
const auto COMPILEDOUTDATED = HLVER.hash != GLOBALSTATE.headersHashCompiled;
const auto COMPILEDOUTDATED = HLVER.abiHash != GLOBALSTATE.headersAbiCompiled;
bool ret1 = g_pPluginManager->updatePlugins(!headersValid || force || COMPILEDOUTDATED);
@ -149,15 +151,16 @@ int main(int argc, char** argv, char** envp) {
if (ret2 != LOADSTATE_OK)
return 1;
} else if (notify)
} else {
g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Couldn't update headers");
}
} else if (command[0] == "enable") {
if (command.size() < 2) {
std::println(stderr, "{}", failureString("Not enough args for enable."));
return 1;
}
if (!g_pPluginManager->enablePlugin(command[1])) {
if (!g_pPluginManager->enablePlugin(SPluginRepoIdentifier::fromString(command[1]))) {
std::println(stderr, "{}", failureString("Couldn't enable plugin (missing?)"));
return 1;
}
@ -178,7 +181,7 @@ int main(int argc, char** argv, char** envp) {
return 1;
}
if (!g_pPluginManager->disablePlugin(command[1])) {
if (!g_pPluginManager->disablePlugin(SPluginRepoIdentifier::fromString(command[1]))) {
std::println(stderr, "{}", failureString("Couldn't disable plugin (missing?)"));
return 1;
}
@ -194,19 +197,17 @@ int main(int argc, char** argv, char** envp) {
auto ret = g_pPluginManager->ensurePluginsLoadState(force);
if (ret != LOADSTATE_OK) {
if (notify) {
switch (ret) {
case LOADSTATE_FAIL:
case LOADSTATE_PARTIAL_FAIL: g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins"); break;
case LOADSTATE_HEADERS_OUTDATED:
g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins: Outdated headers. Please run hyprpm update manually.");
break;
default: break;
}
switch (ret) {
case LOADSTATE_FAIL:
case LOADSTATE_PARTIAL_FAIL: g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins"); break;
case LOADSTATE_HEADERS_OUTDATED:
g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins: Outdated headers. Please run hyprpm update manually.");
break;
default: break;
}
return 1;
} else if (notify && !notifyFail) {
} else if (notify) {
g_pPluginManager->notify(ICON_OK, 0, 4000, "[hyprpm] Loaded plugins");
}
} else if (command[0] == "purge-cache") {

View file

@ -1,32 +0,0 @@
globber = run_command('sh', '-c', 'find . -name "*.cpp" | sort', check: true)
src = globber.stdout().strip().split('\n')
executable(
'hyprpm',
src,
dependencies: [
dependency('hyprutils', version: '>= 0.1.1'),
dependency('threads'),
dependency('tomlplusplus'),
dependency('glaze', method: 'cmake'),
],
install: true,
)
install_data(
'../hyprpm.bash',
install_dir: join_paths(get_option('datadir'), 'bash-completion/completions'),
install_tag: 'runtime',
rename: 'hyprpm',
)
install_data(
'../hyprpm.fish',
install_dir: join_paths(get_option('datadir'), 'fish/vendor_completions.d'),
install_tag: 'runtime',
)
install_data(
'../hyprpm.zsh',
install_dir: join_paths(get_option('datadir'), 'zsh/site-functions'),
install_tag: 'runtime',
rename: '_hyprpm',
)

View file

@ -11,7 +11,10 @@
#include <src/managers/input/InputManager.hpp>
#include <src/managers/PointerManager.hpp>
#include <src/managers/input/trackpad/TrackpadGestures.hpp>
#include <src/desktop/rule/windowRule/WindowRuleEffectContainer.hpp>
#include <src/desktop/rule/windowRule/WindowRuleApplicator.hpp>
#include <src/Compositor.hpp>
#include <src/desktop/state/FocusState.hpp>
#undef private
#include <hyprutils/utils/ScopeGuard.hpp>
@ -43,7 +46,7 @@ static SDispatchResult test(std::string in) {
// Trigger a snap move event for the active window
static SDispatchResult snapMove(std::string in) {
const auto PLASTWINDOW = g_pCompositor->m_lastWindow.lock();
const auto PLASTWINDOW = Desktop::focusState()->window();
if (!PLASTWINDOW->m_isFloating)
return {.success = false, .error = "Window must be floating"};
@ -120,6 +123,7 @@ class CTestMouse : public IPointer {
};
SP<CTestMouse> g_mouse;
SP<CTestKeyboard> g_keyboard;
static SDispatchResult pressAlt(std::string in) {
g_pInputManager->m_lastMods = in == "1" ? HL_MODIFIER_ALT : 0;
@ -204,12 +208,12 @@ static SDispatchResult vkb(std::string in) {
}
static SDispatchResult scroll(std::string in) {
int by;
double by;
try {
by = std::stoi(in);
by = std::stod(in);
} catch (...) { return SDispatchResult{.success = false, .error = "invalid input"}; }
Debug::log(LOG, "tester: scrolling by {}", by);
Log::logger->log(Log::DEBUG, "tester: scrolling by {}", by);
g_mouse->m_pointerEvents.axis.emit(IPointer::SAxisEvent{
.delta = by,
@ -220,6 +224,56 @@ static SDispatchResult scroll(std::string in) {
return {};
}
static SDispatchResult keybind(std::string in) {
CVarList2 data(std::move(in));
// 0 = release, 1 = press
bool press;
// See src/devices/IKeyboard.hpp : eKeyboardModifiers for modifier bitmasks
// 0 = none, eKeyboardModifiers is shifted to start at 1
uint32_t modifier;
// keycode
uint32_t key;
try {
press = std::stoul(std::string{data[0]}) == 1;
modifier = std::stoul(std::string{data[1]});
key = std::stoul(std::string{data[2]}) - 8; // xkb offset
} catch (...) { return {.success = false, .error = "invalid input"}; }
uint32_t modifierMask = 0;
if (modifier > 0)
modifierMask = 1 << (modifier - 1);
g_pInputManager->m_lastMods = modifierMask;
g_keyboard->sendKey(key, press);
return {};
}
static Desktop::Rule::CWindowRuleEffectContainer::storageType ruleIDX = 0;
//
static SDispatchResult addRule(std::string in) {
ruleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule");
if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != ruleIDX)
return {.success = false, .error = "re-registering returned a different id?"};
return {};
}
static SDispatchResult checkRule(std::string in) {
const auto PLASTWINDOW = Desktop::focusState()->window();
if (!PLASTWINDOW)
return {.success = false, .error = "No window"};
if (!PLASTWINDOW->m_ruleApplicator->m_otherProps.props.contains(ruleIDX))
return {.success = false, .error = "No rule"};
if (PLASTWINDOW->m_ruleApplicator->m_otherProps.props[ruleIDX]->effect != "effect")
return {.success = false, .error = "Effect isn't \"effect\""};
return {};
}
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
PHANDLE = handle;
@ -229,15 +283,24 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
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:keybind", ::keybind);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_rule", ::addRule);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_rule", ::checkRule);
// init mouse
g_mouse = CTestMouse::create(false);
g_pInputManager->newMouse(g_mouse);
// init keyboard
g_keyboard = CTestKeyboard::create(false);
g_pInputManager->newKeyboard(g_keyboard);
return {"hyprtestplugin", "hyprtestplugin", "Vaxry", "1.0"};
}
APICALL EXPORT void PLUGIN_EXIT() {
g_mouse->destroy();
g_mouse.reset();
g_keyboard->destroy();
g_keyboard.reset();
}

View file

@ -18,6 +18,17 @@ namespace Colors {
constexpr const char* RESET = "\x1b[0m";
};
#define EXPECT_MAX_DELTA(expr, desired, delta) \
if (const auto RESULT = expr; std::abs(RESULT - (desired)) > delta) { \
NLog::log("{}Failed: {}{}, expected max delta of {}, got delta {} ({} - {}). Source: {}@{}.", Colors::RED, Colors::RESET, #expr, delta, (RESULT - (desired)), RESULT, \
desired, __FILE__, __LINE__); \
ret = 1; \
TESTS_FAILED++; \
} else { \
NLog::log("{}Passed: {}{}. Got {}", Colors::GREEN, Colors::RESET, #expr, (RESULT - (desired))); \
TESTS_PASSED++; \
}
#define EXPECT(expr, val) \
if (const auto RESULT = expr; RESULT != (val)) { \
NLog::log("{}Failed: {}{}, expected {}, got {}. Source: {}@{}.", Colors::RED, Colors::RESET, #expr, val, RESULT, __FILE__, __LINE__); \

View file

@ -64,7 +64,7 @@ static bool startClient(SClient& client) {
// wait for window to appear
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
if (getFromSocket(std::format("/dispatch setprop pid:{} noanim 1", client.proc->pid())) != "ok") {
if (getFromSocket(std::format("/dispatch setprop pid:{} no_anim 1", client.proc->pid())) != "ok") {
NLog::log("{}Failed to disable animations for client window", Colors::RED, ret);
return false;
}
@ -130,7 +130,7 @@ static bool test() {
EXPECT(sendScroll(10), true);
EXPECT(getLastDelta(client), 30);
EXPECT(getFromSocket("r/dispatch setprop active scrollmouse 4"), "ok");
EXPECT(getFromSocket("r/dispatch setprop active scroll_mouse 4"), "ok");
EXPECT(sendScroll(10), true);
EXPECT(getLastDelta(client), 40);

View file

@ -64,7 +64,7 @@ static bool startClient(SClient& client) {
// wait for window to appear
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
if (getFromSocket(std::format("/dispatch setprop pid:{} noanim 1", client.proc->pid())) != "ok") {
if (getFromSocket(std::format("/dispatch setprop pid:{} no_anim 1", client.proc->pid())) != "ok") {
NLog::log("{}Failed to disable animations for client window", Colors::RED, ret);
return false;
}

View file

@ -0,0 +1,27 @@
#include "tests.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include "../shared.hpp"
static int ret = 0;
static bool test() {
NLog::log("{}Testing hyprctl monitors", Colors::GREEN);
std::string monitorsSpec = getFromSocket("j/monitors");
EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset": "srgb")");
EXPECT_CONTAINS(getFromSocket("/keyword monitor HEADLESS-2,1920x1080x60.00000,0x0,1.0,bitdepth,10,cm,wide"), "ok")
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")
monitorsSpec = getFromSocket("j/monitors");
EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset": "srgb")");
EXPECT_CONTAINS(monitorsSpec, R"("sdrBrightness": 1.20)");
EXPECT_CONTAINS(monitorsSpec, R"("sdrSaturation": 0.98)");
return !ret;
}
REGISTER_TEST_FN(test)

View file

@ -0,0 +1,54 @@
#include "../shared.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include "tests.hpp"
static int ret = 0;
static void testFloatClamp() {
for (auto const& win : {"a", "b", "c"}) {
if (!Tests::spawnKitty(win)) {
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
++TESTS_FAILED;
ret = 1;
return;
}
}
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"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 698,158");
EXPECT_CONTAINS(str, "size: 1200,900");
}
OK(getFromSocket("/keyword dwindle:force_split 0"));
// clean up
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
}
static bool test() {
NLog::log("{}Testing Dwindle layout", Colors::GREEN);
// test
NLog::log("{}Testing float clamp", Colors::GREEN);
testFloatClamp();
// clean up
NLog::log("Cleaning up", Colors::YELLOW);
getFromSocket("/dispatch workspace 1");
OK(getFromSocket("/reload"));
return !ret;
}
REGISTER_TEST_FN(test);

View file

@ -0,0 +1,54 @@
#include "tests.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <chrono>
#include <thread>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include "../shared.hpp"
static int ret = 0;
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
#define UP CUniquePointer
#define SP CSharedPointer
static bool test() {
NLog::log("{}Testing process spawning", Colors::GREEN);
// Note: POSIX sleep does not support fractional seconds, so
// can't sleep for less than 1 second.
OK(getFromSocket("/dispatch exec sleep 1"));
// Ensure that sleep is our child
const std::string sleepPidS = Tests::execAndGet("pgrep sleep");
pid_t sleepPid;
try {
sleepPid = std::stoull(sleepPidS);
} catch (...) {
NLog::log("{}Sleep was not spawned or several sleeps are running: pgrep returned '{}'", Colors::RED, sleepPidS);
return false;
}
const std::string sleepParentComm = Tests::execAndGet("cat \"/proc/$(ps -o ppid:1= -p " + sleepPidS + ")/comm\"");
NLog::log("{}Expecting that sleep's parent is Hyprland", Colors::YELLOW);
EXPECT_CONTAINS(sleepParentComm, "Hyprland");
std::this_thread::sleep_for(std::chrono::seconds(1));
// Ensure that sleep did not become a zombie
EXPECT(Tests::processAlive(sleepPid), false);
// kill all
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);
return !ret;
}
REGISTER_TEST_FN(test)

View file

@ -18,6 +18,20 @@ using namespace Hyprutils::Memory;
#define UP CUniquePointer
#define SP CSharedPointer
static bool waitForWindowCount(int expectedWindowCnt, std::string_view expectation, int waitMillis = 100, int maxWaitCnt = 50) {
int counter = 0;
while (Tests::windowCount() != expectedWindowCnt) {
counter++;
std::this_thread::sleep_for(std::chrono::milliseconds(waitMillis));
if (counter > maxWaitCnt) {
NLog::log("{}Unmet expectation: {}", Colors::RED, expectation);
return false;
}
}
return true;
}
static bool test() {
NLog::log("{}Testing gestures", Colors::GREEN);
@ -27,19 +41,25 @@ static bool test() {
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
getFromSocket("/dispatch workspace 1"); // no OK: we might be on 1 already
Tests::spawnKitty();
EXPECT(Tests::windowCount(), 1);
// 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"));
EXPECT(waitForWindowCount(0, "Gesture sent paste exit + enter to kitty"), true);
EXPECT(Tests::windowCount(), 0);
OK(getFromSocket("/dispatch plugin:test:gesture left,3"));
// wait while kitty spawns
int counter = 0;
while (Tests::windowCount() != 1) {
counter++;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (counter > 50) {
NLog::log("{}Gesture didnt spawn kitty", Colors::RED);
return false;
}
}
EXPECT(waitForWindowCount(1, "Gesture spawned kitty"), true);
EXPECT(Tests::windowCount(), 1);
@ -126,19 +146,47 @@ static bool test() {
OK(getFromSocket("/dispatch plugin:test:gesture up,3"));
counter = 0;
while (Tests::windowCount() != 0) {
counter++;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (counter > 50) {
NLog::log("{}Gesture didnt close kitty", Colors::RED);
return false;
}
}
EXPECT(waitForWindowCount(0, "Gesture closed kitty"), true);
EXPECT(Tests::windowCount(), 0);
// This test ensures that `movecursortocorner`, which expects
// a single-character direction argument, is parsed correctly.
Tests::spawnKitty();
OK(getFromSocket("/dispatch movecursortocorner 0"));
const std::string cursorPos1 = getFromSocket("/cursorpos");
OK(getFromSocket("/dispatch 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"));
// Come to workspace 5 from workspace 3: 5 will remember that.
OK(getFromSocket("/dispatch 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"));
EXPECT_CONTAINS(getFromSocket("/activeworkspace"), "ID 5 (5)");
// Must return to 1 rather than 3
OK(getFromSocket("/dispatch workspace previous"));
EXPECT_CONTAINS(getFromSocket("/activeworkspace"), "ID 1 (1)");
OK(getFromSocket("/dispatch workspace previous"));
EXPECT_CONTAINS(getFromSocket("/activeworkspace"), "ID 5 (5)");
OK(getFromSocket("/dispatch workspace 1"));
}
// kill all
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();

View file

@ -56,82 +56,82 @@ static bool testGetprop() {
return false;
}
// animationstyle
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle"), "(unset)");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle -j"), R"({"animationstyle": ""})");
getFromSocket("/dispatch setprop class:kitty animationstyle teststyle");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle"), "teststyle");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle -j"), R"({"animationstyle": "teststyle"})");
// 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");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation"), "teststyle");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation -j"), R"({"animation": "teststyle"})");
// maxsize
EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize"), "inf inf");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize -j"), R"({"maxsize": [null,null]})");
getFromSocket("/dispatch setprop class:kitty maxsize 200 150");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize"), "200 150");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize -j"), R"({"maxsize": [200,150]})");
// 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");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "200 150");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [200,150]})");
// minsize
EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize"), "20 20");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize -j"), R"({"minsize": [20,20]})");
getFromSocket("/dispatch setprop class:kitty minsize 100 50");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize"), "100 50");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize -j"), R"({"minsize": [100,50]})");
// 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");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "100 50");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [100,50]})");
// alpha
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha"), "1");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha -j"), R"({"alpha": 1})");
getFromSocket("/dispatch setprop class:kitty alpha 0.3");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha"), "0.3");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha -j"), R"({"alpha": 0.3})");
// 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");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "0.3");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 0.3})");
// alphainactive
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive"), "1");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive -j"), R"({"alphainactive": 1})");
getFromSocket("/dispatch setprop class:kitty alphainactive 0.5");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive"), "0.5");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive -j"), R"({"alphainactive": 0.5})");
// 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");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive"), "0.5");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive -j"), R"({"opacity_inactive": 0.5})");
// alphafullscreen
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen"), "1");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen -j"), R"({"alphafullscreen": 1})");
getFromSocket("/dispatch setprop class:kitty alphafullscreen 0.75");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen"), "0.75");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen -j"), R"({"alphafullscreen": 0.75})");
// 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");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen"), "0.75");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen -j"), R"({"opacity_fullscreen": 0.75})");
// alphaoverride
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride -j"), R"({"alphaoverride": false})");
getFromSocket("/dispatch setprop class:kitty alphaoverride true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride -j"), R"({"alphaoverride": true})");
// 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");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override -j"), R"({"opacity_override": true})");
// alphainactiveoverride
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride -j"), R"({"alphainactiveoverride": false})");
getFromSocket("/dispatch setprop class:kitty alphainactiveoverride true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride -j"), R"({"alphainactiveoverride": 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");
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})");
// alphafullscreenoverride
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride -j"), R"({"alphafullscreenoverride": false})");
getFromSocket("/dispatch setprop class:kitty alphafullscreenoverride true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride -j"), R"({"alphafullscreenoverride": 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");
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})");
// activebordercolor
EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor"), "ee33ccff ee00ff99 45deg");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor -j"), R"({"activebordercolor": "ee33ccff ee00ff99 45deg"})");
getFromSocket("/dispatch setprop class:kitty activebordercolor rgb(abcdef)");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor"), "ffabcdef 0deg");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor -j"), R"({"activebordercolor": "ffabcdef 0deg"})");
// 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)");
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 allowsinput"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allowsinput -j"), R"({"allowsinput": false})");
getFromSocket("/dispatch setprop class:kitty allowsinput true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allowsinput"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allowsinput -j"), R"({"allowsinput": true})");
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");
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");
@ -141,16 +141,16 @@ static bool testGetprop() {
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding -j"), R"({"rounding": 4})");
// float window properties
EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower"), "2");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower -j"), R"({"roundingpower": 2})");
getFromSocket("/dispatch setprop class:kitty roundingpower 1.25");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower"), "1.25");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower -j"), R"({"roundingpower": 1.25})");
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");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power"), "1.25");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power -j"), R"({"rounding_power": 1.25})");
// errors
EXPECT(getCommandStdOut("hyprctl getprop"), "not enough args");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty"), "not enough args");
EXPECT(getCommandStdOut("hyprctl getprop class:nonexistantclass animationstyle"), "window not found");
EXPECT(getCommandStdOut("hyprctl getprop class:nonexistantclass animation"), "window not found");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty nonexistantprop"), "prop not found");
// kill all

View file

@ -0,0 +1,515 @@
#include <filesystem>
#include <linux/input-event-codes.h>
#include <thread>
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include "../shared.hpp"
#include "tests.hpp"
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
static int ret = 0;
static std::string flagFile = "/tmp/hyprtester-keybinds.txt";
// Because i don't feel like changing someone elses code.
enum eKeyboardModifierIndex : uint8_t {
MOD_SHIFT = 1,
MOD_CAPS,
MOD_CTRL,
MOD_ALT,
MOD_MOD2,
MOD_MOD3,
MOD_META,
MOD_MOD5
};
static void clearFlag() {
std::filesystem::remove(flagFile);
}
static bool checkFlag() {
bool exists = std::filesystem::exists(flagFile);
clearFlag();
return exists;
}
static bool attemptCheckFlag(int attempts, int intervalMs) {
for (int i = 0; i < attempts; i++) {
if (checkFlag())
return true;
std::this_thread::sleep_for(std::chrono::milliseconds(intervalMs));
}
return false;
}
static std::string readKittyOutput() {
std::string output = Tests::execAndGet("kitten @ --to unix:/tmp/hyprtester-kitty.sock get-text --extent all");
// chop off shell prompt
std::size_t pos = output.rfind("$");
if (pos != std::string::npos) {
pos += 1;
if (pos < output.size())
output.erase(0, pos);
}
// NLog::log("Kitty output: '{}'", output);
return output;
}
static void awaitKittyPrompt() {
// wait until we see the shell prompt, meaning it's ready for test inputs
for (int i = 0; i < 10; i++) {
std::string output = Tests::execAndGet("kitten @ --to unix:/tmp/hyprtester-kitty.sock get-text --extent all");
if (output.rfind("$") == std::string::npos) {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
continue;
}
return;
}
NLog::log("{}Error: timed out waiting for kitty prompt", Colors::RED);
}
static CUniquePointer<CProcess> spawnRemoteControlKitty() {
auto kittyProc = Tests::spawnKitty("keybinds_test", {"-o", "allow_remote_control=yes", "--listen-on", "unix:/tmp/hyprtester-kitty.sock", "--config", "NONE", "/bin/sh"});
// wait a bit to ensure shell prompt is sent, we are going to read the text after it
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (kittyProc)
awaitKittyPrompt();
return kittyProc;
}
static void testBind() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword bind SUPER,Y,exec,touch " + flagFile), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,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");
}
static void testBindKey() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword bind ,Y,exec,touch " + flagFile), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,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");
}
static void testLongPress() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword bindo SUPER,Y,exec,touch " + flagFile), "ok");
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
// check no flag on short press
std::this_thread::sleep_for(std::chrono::milliseconds(50));
EXPECT(checkFlag(), false);
// await repeat delay
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");
}
static void testKeyLongPress() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword bindo ,Y,exec,touch " + flagFile), "ok");
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,0,29"));
// check no flag on short press
std::this_thread::sleep_for(std::chrono::milliseconds(50));
EXPECT(checkFlag(), false);
// await repeat delay
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");
}
static void testLongPressRelease() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword bindo SUPER,Y,exec,touch " + flagFile), "ok");
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,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"));
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword 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");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,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"));
// 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");
}
static void testRepeat() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword binde SUPER,Y,exec,touch " + flagFile), "ok");
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
// await flag
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), true);
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), true);
// check that it continues repeating
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");
}
static void testKeyRepeat() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword binde ,Y,exec,touch " + flagFile), "ok");
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,0,29"));
// await flag
std::this_thread::sleep_for(std::chrono::milliseconds(50));
EXPECT(checkFlag(), true);
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), true);
// check that it continues repeating
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");
}
static void testRepeatRelease() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword binde SUPER,Y,exec,touch " + flagFile), "ok");
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,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"));
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
clearFlag();
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), false);
// 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");
}
static void testRepeatOnlyKeyRelease() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword binde SUPER,Y,exec,touch " + flagFile), "ok");
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,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"));
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
clearFlag();
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), false);
// 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");
}
static void testShortcutBind() {
auto kittyProc = spawnRemoteControlKitty();
if (!kittyProc) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
ret = 1;
return;
}
EXPECT(getFromSocket("/dispatch focuswindow class:keybinds_test"), "ok");
EXPECT(getFromSocket("/keyword bind SUPER,Y,sendshortcut,,q,"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
// release keybind
std::this_thread::sleep_for(std::chrono::milliseconds(50));
OK(getFromSocket("/dispatch plugin:test:keybind 0,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");
Tests::killAllWindows();
}
static void testShortcutBindKey() {
auto kittyProc = spawnRemoteControlKitty();
if (!kittyProc) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
ret = 1;
return;
}
EXPECT(getFromSocket("/dispatch focuswindow class:keybinds_test"), "ok");
EXPECT(getFromSocket("/keyword bind ,Y,sendshortcut,,q,"), "ok");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,0,29"));
// release keybind
std::this_thread::sleep_for(std::chrono::milliseconds(50));
OK(getFromSocket("/dispatch plugin:test:keybind 0,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");
Tests::killAllWindows();
}
static void testShortcutLongPress() {
auto kittyProc = spawnRemoteControlKitty();
if (!kittyProc) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
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");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
std::this_thread::sleep_for(std::chrono::milliseconds(200));
const std::string output = readKittyOutput();
int yCount = Tests::countOccurrences(output, "y");
// sometimes 1, sometimes 2, not sure why
// keybind press sends 1 y immediately
// then repeat triggers, sending 1 y
// 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");
Tests::killAllWindows();
}
static void testShortcutLongPressKeyRelease() {
auto kittyProc = spawnRemoteControlKitty();
if (!kittyProc) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
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");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
std::this_thread::sleep_for(std::chrono::milliseconds(10));
// release key, keep modifier
OK(getFromSocket("/dispatch plugin:test:keybind 0,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");
Tests::killAllWindows();
}
static void testShortcutRepeat() {
auto kittyProc = spawnRemoteControlKitty();
if (!kittyProc) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
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");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
// await repeat
std::this_thread::sleep_for(std::chrono::milliseconds(210));
// release keybind
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
std::this_thread::sleep_for(std::chrono::milliseconds(450));
const std::string output = readKittyOutput();
EXPECT_COUNT_STRING(output, "y", 0);
int qCount = Tests::countOccurrences(output, "q");
// sometimes 2, sometimes 3, not sure why
// keybind press sends 1 q immediately
// 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");
Tests::killAllWindows();
}
static void testShortcutRepeatKeyRelease() {
auto kittyProc = spawnRemoteControlKitty();
if (!kittyProc) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
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");
// press keybind
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
std::this_thread::sleep_for(std::chrono::milliseconds(210));
// release key, keep modifier
OK(getFromSocket("/dispatch plugin:test:keybind 0,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
const std::string output = readKittyOutput();
EXPECT_COUNT_STRING(output, "y", 0);
int qCount = Tests::countOccurrences(output, "q");
// sometimes 2, sometimes 3, not sure why
// keybind press sends 1 q immediately
// 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");
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));
};
NLog::log("{}Testing submaps", Colors::GREEN);
// submap 1 no resets
press(KEY_U, MOD_META);
EXPECT_CONTAINS(getFromSocket("/submap"), "submap1");
press(KEY_O);
Tests::waitUntilWindowsN(1);
EXPECT_CONTAINS(getFromSocket("/submap"), "submap1");
// submap 2 resets to submap 1
press(KEY_U);
EXPECT_CONTAINS(getFromSocket("/submap"), "submap2");
press(KEY_O);
Tests::waitUntilWindowsN(2);
EXPECT_CONTAINS(getFromSocket("/submap"), "submap1");
// submap 3 resets to default
press(KEY_I);
EXPECT_CONTAINS(getFromSocket("/submap"), "submap3");
press(KEY_O);
Tests::waitUntilWindowsN(3);
EXPECT_CONTAINS(getFromSocket("/submap"), "default");
// submap 1 reset via keybind
press(KEY_U, MOD_META);
EXPECT_CONTAINS(getFromSocket("/submap"), "submap1");
press(KEY_P);
EXPECT_CONTAINS(getFromSocket("/submap"), "default");
Tests::killAllWindows();
}
static void testSubmapUniversal() {
NLog::log("{}Testing submap universal", Colors::GREEN);
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/keyword bindu SUPER,Y,exec,touch " + flagFile), "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"));
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");
EXPECT_CONTAINS(getFromSocket("/submap"), "submap1");
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
OK(getFromSocket("/dispatch plugin:test:keybind 0,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");
EXPECT_CONTAINS(getFromSocket("/submap"), "default");
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
}
static bool test() {
NLog::log("{}Testing keybinds", Colors::GREEN);
clearFlag();
testBind();
testBindKey();
testLongPress();
testKeyLongPress();
testLongPressRelease();
testLongPressOnlyKeyRelease();
testRepeat();
testKeyRepeat();
testRepeatRelease();
testRepeatOnlyKeyRelease();
testShortcutBind();
testShortcutBindKey();
testShortcutLongPress();
testShortcutLongPressKeyRelease();
testShortcutRepeat();
testShortcutRepeatKeyRelease();
testSubmap();
testSubmapUniversal();
clearFlag();
return !ret;
}
REGISTER_TEST_FN(test)

View file

@ -53,7 +53,7 @@ static bool test() {
NLog::log("{}Testing new_window_takes_over_fullscreen", Colors::YELLOW);
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 0"));
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0"));
Tests::spawnKitty("kitty_A");
@ -73,7 +73,16 @@ static bool test() {
EXPECT_CONTAINS(str, "kitty_A");
}
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 1"));
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
{
// should be ignored as per focus_under_fullscreen 0
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 2");
EXPECT_CONTAINS(str, "kitty_A");
}
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 1"));
Tests::spawnKitty("kitty_C");
@ -83,7 +92,7 @@ static bool test() {
EXPECT_CONTAINS(str, "kitty_C");
}
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 2"));
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2"));
Tests::spawnKitty("kitty_D");
@ -93,7 +102,7 @@ static bool test() {
EXPECT_CONTAINS(str, "kitty_D");
}
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 0"));
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0"));
Tests::killAllWindows();
@ -131,6 +140,74 @@ static bool test() {
EXPECT_CONTAINS(str, "fullscreen: 2");
}
Tests::killAllWindows();
NLog::log("{}Testing fullscreen and fullscreenstate dispatcher", Colors::YELLOW);
Tests::spawnKitty("kitty_A");
Tests::spawnKitty("kitty_B");
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch fullscreen 0 set"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 2");
}
OK(getFromSocket("/dispatch fullscreen 0 unset"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 0");
}
OK(getFromSocket("/dispatch fullscreen 1 toggle"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 1");
}
OK(getFromSocket("/dispatch fullscreen 1 toggle"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 0");
}
OK(getFromSocket("/dispatch fullscreenstate 2 2 set"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 2");
}
OK(getFromSocket("/dispatch fullscreenstate 2 2 set"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 2");
}
OK(getFromSocket("/dispatch fullscreenstate 2 2 toggle"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 0");
}
OK(getFromSocket("/dispatch fullscreenstate 2 2 toggle"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 2");
}
// Ensure that the process autostarted in the config does not
// become a zombie even if it terminates very quickly.
EXPECT(Tests::execAndGet("pgrep -f 'sleep 0'").empty(), true);
// kill all
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();

View file

@ -0,0 +1,51 @@
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include "../shared.hpp"
#include "tests.hpp"
static int ret = 0;
static bool testTags() {
NLog::log("{}Testing tags", Colors::GREEN);
EXPECT(Tests::windowCount(), 0);
NLog::log("{}Spawning kittyProcA&B on ws 1", Colors::YELLOW);
auto kittyProcA = Tests::spawnKitty("tagged");
auto kittyProcB = Tests::spawnKitty("untagged");
if (!kittyProcA || !kittyProcB) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
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"));
EXPECT(Tests::windowCount(), 2);
OK(getFromSocket("/dispatch focuswindow 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"));
EXPECT_NOT_CONTAINS(getFromSocket("/activewindow"), "testTag");
EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_shadow"), "true");
EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_dim"), "false");
Tests::killAllWindows();
EXPECT(Tests::windowCount(), 0);
OK(getFromSocket("/reload"));
return ret == 0;
}
REGISTER_TEST_FN(testTags)

View file

@ -1,8 +1,12 @@
#include <cmath>
#include <thread>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <thread>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/string/VarList2.hpp>
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
@ -11,9 +15,9 @@
static int ret = 0;
static bool spawnKitty(const std::string& class_) {
static bool spawnKitty(const std::string& class_, const std::vector<std::string>& args = {}) {
NLog::log("{}Spawning {}", Colors::YELLOW, class_);
if (!Tests::spawnKitty(class_)) {
if (!Tests::spawnKitty(class_, args)) {
NLog::log("{}Error: {} did not spawn", Colors::RED, class_);
return false;
}
@ -131,6 +135,244 @@ static void testSwapWindow() {
EXPECT(Tests::windowCount(), 0);
}
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"));
if (!Tests::spawnKitty("kitty_A")) {
ret = 1;
return;
}
{
auto str = getFromSocket("/getprop active border_size");
EXPECT_CONTAINS(str, "0");
}
if (!Tests::spawnKitty("kitty_B")) {
ret = 1;
return;
}
{
auto str = getFromSocket("/getprop active border_size");
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"));
{
auto str = getFromSocket("/getprop active border_size");
EXPECT_CONTAINS(str, "0");
}
OK(getFromSocket("/dispatch changegroupactive f"));
{
auto str = getFromSocket("/getprop active border_size");
EXPECT_CONTAINS(str, "0");
}
if (!Tests::spawnKitty("kitty_C")) {
ret = 1;
return;
}
OK(getFromSocket("/dispatch moveoutofgroup r"));
{
auto str = getFromSocket("/getprop active border_size");
EXPECT_CONTAINS(str, "8");
}
OK(getFromSocket("/reload"));
Tests::killAllWindows();
}
static bool isActiveWindow(const std::string& class_, char fullscreen, bool log = true) {
std::string activeWin = getFromSocket("/activewindow");
auto winClass = getWindowAttribute(activeWin, "class:");
auto winFullscreen = getWindowAttribute(activeWin, "fullscreen:").back();
if (winClass.substr(strlen("class: ")) == class_ && winFullscreen == fullscreen)
return true;
else {
if (log)
NLog::log("{}Wrong active window: expected class {} fullscreen '{}', found class {}, fullscreen '{}'", Colors::RED, class_, fullscreen, winClass, winFullscreen);
return false;
}
}
static bool waitForActiveWindow(const std::string& class_, char fullscreen, int maxTries = 50) {
int cnt = 0;
while (!isActiveWindow(class_, fullscreen, false)) {
++cnt;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (cnt > maxTries) {
return isActiveWindow(class_, fullscreen, true);
}
}
return true;
}
/// Tests behavior of a window being focused when on that window's workspace
/// another fullscreen window exists.
static bool testWindowFocusOnFullscreenConflict() {
if (!spawnKitty("kitty_A"))
return false;
if (!spawnKitty("kitty_B"))
return false;
OK(getFromSocket("/keyword misc:focus_on_activate true"));
auto spawnKittyActivating = [] -> std::string {
// `XXXXXX` is what `mkstemp` expects to find in the string
std::string tmpFilename = (std::filesystem::temp_directory_path() / "XXXXXX").string();
int fd = mkstemp(tmpFilename.data());
if (fd < 0) {
NLog::log("{}Error: could not create tmp file: errno {}", Colors::RED, errno);
return "";
}
(void)close(fd);
bool ok = spawnKitty("kitty_activating",
{"-o", "allow_remote_control=yes", "--", "/bin/sh", "-c", "while [ -f \"" + tmpFilename + "\" ]; do :; done; kitten @ focus-window; sleep infinity"});
if (!ok) {
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
return "";
}
return tmpFilename;
};
// Unfullscreen on conflict
{
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2"));
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch fullscreen 0 set"));
EXPECT(isActiveWindow("kitty_A", '2'), true);
// Dispatch-focus the same window
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
EXPECT(isActiveWindow("kitty_A", '2'), true);
// Dispatch-focus a different window
OK(getFromSocket("/dispatch focuswindow 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"));
EXPECT(isActiveWindow("kitty_A", '2'), true);
std::filesystem::remove(removeToActivate);
EXPECT(waitForActiveWindow("kitty_activating", '0'), true);
OK(getFromSocket("/dispatch forcekillactive"));
Tests::waitUntilWindowsN(2);
}
// Take over on conflict
{
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 1"));
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch fullscreen 0 set"));
EXPECT(isActiveWindow("kitty_A", '2'), true);
// Dispatch-focus the same window
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
EXPECT(isActiveWindow("kitty_A", '2'), true);
// Dispatch-focus a different window
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
EXPECT(isActiveWindow("kitty_B", '2'), true);
OK(getFromSocket("/dispatch fullscreenstate 0 0"));
// 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"));
EXPECT(isActiveWindow("kitty_A", '2'), true);
std::filesystem::remove(removeToActivate);
EXPECT(waitForActiveWindow("kitty_activating", '2'), true);
OK(getFromSocket("/dispatch forcekillactive"));
Tests::waitUntilWindowsN(2);
}
// Keep the old focus on conflict
{
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0"));
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch fullscreen 0 set"));
EXPECT(isActiveWindow("kitty_A", '2'), true);
// Dispatch-focus the same window
OK(getFromSocket("/dispatch focuswindow 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"));
EXPECT(isActiveWindow("kitty_A", '2'), true);
std::filesystem::remove(removeToActivate);
EXPECT(waitForActiveWindow("kitty_A", '2'), true);
}
NLog::log("{}Reloading config", Colors::YELLOW);
OK(getFromSocket("/reload"));
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);
return true;
}
static void testMaximizeSize() {
NLog::log("{}Testing maximize size", Colors::GREEN);
EXPECT(spawnKitty("kitty_A"), true);
// check kitty properties. Maximizing shouldnt change its size
{
auto str = getFromSocket("/clients");
EXPECT(str.contains("at: 22,22"), true);
EXPECT(str.contains("size: 1876,1036"), true);
EXPECT(str.contains("fullscreen: 0"), true);
}
OK(getFromSocket("/dispatch fullscreen 1"));
{
auto str = getFromSocket("/clients");
EXPECT(str.contains("at: 22,22"), true);
EXPECT(str.contains("size: 1876,1036"), true);
EXPECT(str.contains("fullscreen: 1"), true);
}
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);
}
static bool test() {
NLog::log("{}Testing windows", Colors::GREEN);
@ -152,22 +394,40 @@ static bool test() {
NLog::log("{}Testing window split ratios", Colors::YELLOW);
{
const double RATIO = 1.25;
const double PERCENT = RATIO / 2.0 * 100.0;
const int GAPSIN = 5;
const int GAPSOUT = 20;
const int BORDERS = 2 * 2;
const int WTRIM = BORDERS + GAPSIN + GAPSOUT;
const int HEIGHT = 1080 - (BORDERS + (GAPSOUT * 2));
const int WIDTH1 = std::round(1920.0 / 2.0 * (2 - RATIO)) - WTRIM;
const int WIDTH2 = std::round(1920.0 / 2.0 * RATIO) - WTRIM;
const double INITIAL_RATIO = 1.25;
const int GAPSIN = 5;
const int GAPSOUT = 20;
const int BORDERSIZE = 2;
const int BORDERS = BORDERSIZE * 2;
const int MONITOR_W = 1920;
const int MONITOR_H = 1080;
const float totalAvailableHeight = MONITOR_H - (GAPSOUT * 2);
const int HEIGHT = std::floor(totalAvailableHeight) - BORDERS;
const float availableWidthForSplit = MONITOR_W - (GAPSOUT * 2) - GAPSIN;
auto calculateFinalWidth = [&](double boxWidth, bool isLeftWindow) {
double gapLeft = isLeftWindow ? GAPSOUT : GAPSIN;
double gapRight = isLeftWindow ? GAPSIN : GAPSOUT;
return std::floor(boxWidth - gapLeft - gapRight - BORDERS);
};
double geomBoxWidthA_R1 = (availableWidthForSplit * INITIAL_RATIO / 2.0) + GAPSOUT + (GAPSIN / 2.0);
double geomBoxWidthB_R1 = MONITOR_W - geomBoxWidthA_R1;
const int WIDTH1 = calculateFinalWidth(geomBoxWidthB_R1, false);
const double INVERTED_RATIO = 0.75;
double geomBoxWidthA_R2 = (availableWidthForSplit * INVERTED_RATIO / 2.0) + GAPSOUT + (GAPSIN / 2.0);
double geomBoxWidthB_R2 = MONITOR_W - geomBoxWidthA_R2;
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"));
if (!spawnKitty("kitty_B"))
return false;
NLog::log("{}Expecting kitty_B to take up roughly {}% of screen width", Colors::YELLOW, 100 - PERCENT);
NLog::log("{}Expecting kitty_B size: {},{}", Colors::YELLOW, WIDTH1, HEIGHT);
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH1, HEIGHT));
OK(getFromSocket("/dispatch killwindow activewindow"));
@ -179,12 +439,38 @@ static bool test() {
if (!spawnKitty("kitty_B"))
return false;
NLog::log("{}Expecting kitty_B to take up roughly {}% of screen width", Colors::YELLOW, PERCENT);
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH2, HEIGHT));
try {
NLog::log("{}Expecting kitty_B size: {},{}", Colors::YELLOW, WIDTH2, HEIGHT);
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
NLog::log("{}Expecting kitty_A to have the same width as the previous kitty_B", Colors::YELLOW);
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH1, HEIGHT));
{
auto data = getFromSocket("/activewindow");
data = data.substr(data.find("size:") + 5);
data = data.substr(0, data.find('\n'));
Hyprutils::String::CVarList2 sizes(std::move(data), 0, ',');
EXPECT_MAX_DELTA(std::stoi(std::string{sizes[0]}), WIDTH2, 2);
EXPECT_MAX_DELTA(std::stoi(std::string{sizes[1]}), HEIGHT, 2);
}
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
NLog::log("{}Expecting kitty_A size: {},{}", Colors::YELLOW, WIDTH_A_FINAL, HEIGHT);
{
auto data = getFromSocket("/activewindow");
data = data.substr(data.find("size:") + 5);
data = data.substr(0, data.find('\n'));
Hyprutils::String::CVarList2 sizes(std::move(data), 0, ',');
EXPECT_MAX_DELTA(std::stoi(std::string{sizes[0]}), WIDTH_A_FINAL, 2);
EXPECT_MAX_DELTA(std::stoi(std::string{sizes[1]}), HEIGHT, 2);
}
} catch (...) {
NLog::log("{}Exception thrown", Colors::RED);
EXPECT(false, true);
}
OK(getFromSocket("/keyword dwindle:default_split_ratio 1"));
}
@ -194,16 +480,7 @@ static bool test() {
getFromSocket("/dispatch exec xeyes");
NLog::log("{}Keep checking if xeyes spawned", Colors::YELLOW);
int counter = 0;
while (Tests::windowCount() != 3) {
counter++;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (counter > 50) {
EXPECT(Tests::windowCount(), 3);
return !ret;
}
}
Tests::waitUntilWindowsN(3);
NLog::log("{}Expecting 3 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 3);
@ -228,6 +505,255 @@ static bool test() {
testSwapWindow();
getFromSocket("/dispatch workspace 1");
if (!testWindowFocusOnFullscreenConflict()) {
ret = 1;
return false;
}
NLog::log("{}Testing spawning a floating window over a fullscreen window", Colors::YELLOW);
{
if (!spawnKitty("kitty_A"))
return false;
OK(getFromSocket("/dispatch fullscreen 0 set"));
EXPECT(Tests::windowCount(), 1);
OK(getFromSocket("/dispatch exec [float] kitty"));
Tests::waitUntilWindowsN(2);
OK(getFromSocket("/dispatch focuswindow class:^kitty$"));
const auto focused1 = getFromSocket("/activewindow");
EXPECT_CONTAINS(focused1, "class: kitty\n");
OK(getFromSocket("/dispatch killwindow activewindow"));
Tests::waitUntilWindowsN(1);
// The old window should be focused again
const auto focused2 = getFromSocket("/activewindow");
EXPECT_CONTAINS(focused2, "class: kitty_A\n");
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
}
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"));
if (!spawnKitty("kitty_maxsize"))
return false;
auto dwindle = getFromSocket("/activewindow");
EXPECT_CONTAINS(dwindle, "size: 1500,500");
EXPECT_CONTAINS(dwindle, "at: 210,290");
if (!spawnKitty("kitty_maxsize"))
return false;
EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 1200,500");
Tests::killAllWindows();
EXPECT(Tests::windowCount(), 0);
OK(getFromSocket("/keyword general:layout master"));
if (!spawnKitty("kitty_maxsize"))
return false;
auto master = getFromSocket("/activewindow");
EXPECT_CONTAINS(master, "size: 1500,500");
EXPECT_CONTAINS(master, "at: 210,290");
if (!spawnKitty("kitty_maxsize"))
return false;
OK(getFromSocket("/dispatch focuswindow class:kitty_maxsize"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 1200,500")
NLog::log("{}Reloading config", Colors::YELLOW);
OK(getFromSocket("/reload"));
Tests::killAllWindows();
EXPECT(Tests::windowCount(), 0);
}
NLog::log("{}Testing window rules", Colors::YELLOW);
if (!spawnKitty("wr_kitty"))
return false;
{
auto str = getFromSocket("/activewindow");
const int SIZE = 200;
EXPECT_CONTAINS(str, "floating: 1");
EXPECT_CONTAINS(str, std::format("size: {},{}", SIZE, SIZE));
EXPECT_NOT_CONTAINS(str, "pinned: 1");
}
OK(getFromSocket("/keyword windowrule[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"));
if (!spawnKitty("magic_kitty"))
return false;
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "special:magic");
EXPECT_NOT_CONTAINS(str, "workspace: 9");
}
if (auto str = getFromSocket("/monitors"); str.contains("magic)")) {
OK(getFromSocket("/dispatch togglespecialworkspace magic"));
}
Tests::killAllWindows();
if (!spawnKitty("tag_kitty"))
return false;
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "floating: 1");
}
OK(getFromSocket("/reload"));
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"));
if (!spawnKitty("overlap_kitty"))
return false;
{
auto str = getFromSocket("/getprop active border_size");
EXPECT_CONTAINS(str, "10");
}
OK(getFromSocket("/reload"));
Tests::killAllWindows();
// test persistent_size between floating window launches
OK(getFromSocket("/keyword windowrule match:class persistent_size_kitty, persistent_size true, float true"));
if (!spawnKitty("persistent_size_kitty"))
return false;
OK(getFromSocket("/dispatch resizeactive exact 600 400"))
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "size: 600,400");
EXPECT_CONTAINS(str, "floating: 1");
}
Tests::killAllWindows();
if (!spawnKitty("persistent_size_kitty"))
return false;
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "size: 600,400");
EXPECT_CONTAINS(str, "floating: 1");
}
OK(getFromSocket("/reload"));
Tests::killAllWindows();
OK(getFromSocket("/keyword general:border_size 0"));
OK(getFromSocket("/keyword windowrule match:float true, border_size 10"));
if (!spawnKitty("border_kitty"))
return false;
{
auto str = getFromSocket("/getprop active border_size");
EXPECT_CONTAINS(str, "0");
}
OK(getFromSocket("/dispatch togglefloating"));
{
auto str = getFromSocket("/getprop active border_size");
EXPECT_CONTAINS(str, "10");
}
OK(getFromSocket("/dispatch togglefloating"));
{
auto str = getFromSocket("/getprop active border_size");
EXPECT_CONTAINS(str, "0");
}
OK(getFromSocket("/reload"));
Tests::killAllWindows();
// test expression rules
OK(getFromSocket("/keyword windowrule match:class expr_kitty, float yes, size monitor_w*0.5 monitor_h*0.5, move 20+(monitor_w*0.1) monitor_h*0.5"));
if (!spawnKitty("expr_kitty"))
return false;
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "floating: 1");
EXPECT_CONTAINS(str, "at: 212,540");
EXPECT_CONTAINS(str, "size: 960,540");
}
OK(getFromSocket("/reload"));
Tests::killAllWindows();
OK(getFromSocket("/dispatch plugin:test:add_rule"));
OK(getFromSocket("/reload"));
OK(getFromSocket("/keyword windowrule match:class plugin_kitty, plugin_rule effect"));
if (!spawnKitty("plugin_kitty"))
return false;
OK(getFromSocket("/dispatch plugin:test:check_rule"));
OK(getFromSocket("/reload"));
Tests::killAllWindows();
OK(getFromSocket("/dispatch plugin:test:add_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"));
if (!spawnKitty("plugin_kitty"))
return false;
OK(getFromSocket("/dispatch plugin:test:check_rule"));
OK(getFromSocket("/reload"));
Tests::killAllWindows();
testGroupRules();
testMaximizeSize();
NLog::log("{}Reloading config", Colors::YELLOW);
OK(getFromSocket("/reload"));
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);
return !ret;
}

View file

@ -6,6 +6,7 @@
#include <chrono>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/utils/ScopeGuard.hpp>
#include <csignal>
#include <cerrno>
#include "../shared.hpp"
@ -14,10 +15,99 @@ static int ret = 0;
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
using namespace Hyprutils::Utils;
#define UP CUniquePointer
#define SP CSharedPointer
static bool testAsymmetricGaps() {
NLog::log("{}Testing asymmetric gap splits", Colors::YELLOW);
{
CScopeGuard guard = {[&]() {
NLog::log("{}Cleaning up asymmetric gap test", Colors::YELLOW);
Tests::killAllWindows();
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"));
NLog::log("{}Testing default split (force_split = 0)", Colors::YELLOW);
OK(getFromSocket("r/keyword 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"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
OK(getFromSocket("/dispatch focuswindow 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"));
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"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
OK(getFromSocket("/dispatch focuswindow 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"));
if (!Tests::spawnKitty("gaps_kitty_C"))
return false;
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_C"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
OK(getFromSocket("/dispatch focuswindow 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"));
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"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
OK(getFromSocket("/dispatch focuswindow 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"));
if (!Tests::spawnKitty("gaps_kitty_C"))
return false;
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_C"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 460,0");
}
// kill all
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
return true;
}
static bool test() {
NLog::log("{}Testing workspaces", Colors::GREEN);
@ -25,7 +115,17 @@ static bool test() {
// test on workspace "window"
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
getFromSocket("/dispatch workspace 1");
NLog::log("{}Checking persistent no-mon", Colors::YELLOW);
OK(getFromSocket("r/keyword workspace 966,persistent:1"));
{
auto str = getFromSocket("/workspaces");
EXPECT_CONTAINS(str, "workspace ID 966 (966)");
}
OK(getFromSocket("/reload"));
NLog::log("{}Spawning kittyProc on ws 1", Colors::YELLOW);
auto kittyProcA = Tests::spawnKitty();
@ -349,6 +449,8 @@ static bool test() {
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
testAsymmetricGaps();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);

View file

@ -9,10 +9,15 @@
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
CUniquePointer<CProcess> Tests::spawnKitty(const std::string& class_) {
CUniquePointer<CProcess> Tests::spawnKitty(const std::string& class_, const std::vector<std::string> args) {
const auto COUNT_BEFORE = windowCount();
CUniquePointer<CProcess> kitty = makeUnique<CProcess>("kitty", class_.empty() ? std::vector<std::string>{} : std::vector<std::string>{"--class", class_});
std::vector<std::string> programArgs = args;
if (!class_.empty()) {
programArgs.insert(programArgs.begin(), "--class");
programArgs.insert(programArgs.begin() + 1, class_);
}
CUniquePointer<CProcess> kitty = makeUnique<CProcess>("kitty", programArgs);
kitty->addEnv("WAYLAND_DISPLAY", WLDISPLAY);
kitty->runAsync();
@ -49,7 +54,7 @@ int Tests::countOccurrences(const std::string& in, const std::string& what) {
auto pos = in.find(what);
while (pos != std::string::npos) {
cnt++;
pos = in.find(what, pos + what.length() - 1);
pos = in.find(what, pos + what.length());
}
return cnt;
@ -90,3 +95,13 @@ void Tests::waitUntilWindowsN(int n) {
}
}
}
std::string Tests::execAndGet(const std::string& cmd) {
CProcess proc("/bin/sh", {"-c", cmd});
if (!proc.runSync()) {
return "error";
}
return proc.stdOut();
}

View file

@ -8,10 +8,11 @@
//NOLINTNEXTLINE
namespace Tests {
Hyprutils::Memory::CUniquePointer<Hyprutils::OS::CProcess> spawnKitty(const std::string& class_ = "");
Hyprutils::Memory::CUniquePointer<Hyprutils::OS::CProcess> spawnKitty(const std::string& class_ = "", const std::vector<std::string> args = {});
bool processAlive(pid_t pid);
int windowCount();
int countOccurrences(const std::string& in, const std::string& what);
bool killAllWindows();
void waitUntilWindowsN(int n);
std::string execAndGet(const std::string& cmd);
};

View file

@ -46,6 +46,7 @@ $menu = wofi --show drun
# 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
@ -296,32 +297,91 @@ 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 ###
##############################
# See https://wiki.hyprland.org/Configuring/Window-Rules/ for more
# See https://wiki.hyprland.org/Configuring/Workspace-Rules/ for workspace rules
windowrule {
# Ignore maximize requests from apps. You'll probably like this.
name = suppress-maximize-events
match:class = .*
# Example windowrule v1
# windowrule = float, ^(kitty)$
suppress_event = maximize
}
# Example windowrule v2
# windowrulev2 = float,class:^(kitty)$,title:^(kitty)$
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
# Ignore maximize requests from apps. You'll probably like this.
windowrulev2 = suppressevent maximize, class:.*
no_focus = true
}
# Fix some dragging issues with XWayland
windowrulev2 = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0
# Workspace "windows" is a smart gaps one
workspace = n[s:window] w[tv1], gapsout:0, gapsin:0
workspace = n[s:window] f[1], gapsout:0, gapsin:0
windowrulev2 = bordersize 0, floating:0, onworkspace:n[s:window] w[tv1]
windowrulev2 = rounding 0, floating:0, onworkspace:n[s:window] w[tv1]
windowrulev2 = bordersize 0, floating:0, onworkspace:n[s:window] f[1]
windowrulev2 = rounding 0, floating:0, onworkspace:n[s:window] f[1]
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
@ -332,3 +392,9 @@ 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

View file

@ -1,125 +0,0 @@
project(
'Hyprland',
'cpp',
'c',
version: run_command('cat', join_paths(meson.project_source_root(), 'VERSION'), check: true).stdout().strip(),
default_options: [
'warning_level=2',
'default_library=static',
'optimization=3',
'buildtype=release',
'debug=false',
'cpp_std=c++26',
],
)
datarootdir = '-DDATAROOTDIR="' + get_option('prefix') / get_option('datadir') + '"'
add_project_arguments(
[
'-Wno-unused-parameter',
'-Wno-unused-value',
'-Wno-missing-field-initializers',
'-Wno-narrowing',
'-Wno-pointer-arith', datarootdir,
'-DHYPRLAND_VERSION="' + meson.project_version() + '"',
],
language: 'cpp',
)
cpp_compiler = meson.get_compiler('cpp')
if cpp_compiler.check_header('execinfo.h')
add_project_arguments('-DHAS_EXECINFO', language: 'cpp')
endif
aquamarine = dependency('aquamarine', version: '>=0.9.3')
hyprcursor = dependency('hyprcursor', version: '>=0.1.7')
hyprgraphics = dependency('hyprgraphics', version: '>= 0.1.3')
hyprlang = dependency('hyprlang', version: '>= 0.3.2')
hyprutils = dependency('hyprutils', version: '>= 0.8.2')
aquamarine_version_list = aquamarine.version().split('.')
add_project_arguments(['-DAQUAMARINE_VERSION="@0@"'.format(aquamarine.version())], language: 'cpp')
add_project_arguments(['-DAQUAMARINE_VERSION_MAJOR=@0@'.format(aquamarine_version_list.get(0))], language: 'cpp')
add_project_arguments(['-DAQUAMARINE_VERSION_MINOR=@0@'.format(aquamarine_version_list.get(1))], language: 'cpp')
add_project_arguments(['-DAQUAMARINE_VERSION_PATCH=@0@'.format(aquamarine_version_list.get(2))], language: 'cpp')
add_project_arguments(['-DHYPRCURSOR_VERSION="@0@"'.format(hyprcursor.version())], language: 'cpp')
add_project_arguments(['-DHYPRGRAPHICS_VERSION="@0@"'.format(hyprgraphics.version())], language: 'cpp')
add_project_arguments(['-DHYPRLANG_VERSION="@0@"'.format(hyprlang.version())], language: 'cpp')
add_project_arguments(['-DHYPRUTILS_VERSION="@0@"'.format(hyprutils.version())], language: 'cpp')
xcb_dep = dependency('xcb', required: get_option('xwayland'))
xcb_composite_dep = dependency('xcb-composite', required: get_option('xwayland'))
xcb_errors_dep = dependency('xcb-errors', required: get_option('xwayland'))
xcb_icccm_dep = dependency('xcb-icccm', required: get_option('xwayland'))
xcb_render_dep = dependency('xcb-render', required: get_option('xwayland'))
xcb_res_dep = dependency('xcb-res', required: get_option('xwayland'))
xcb_xfixes_dep = dependency('xcb-xfixes', required: get_option('xwayland'))
gio_dep = dependency('gio-2.0', required: true)
if not xcb_dep.found()
add_project_arguments('-DNO_XWAYLAND', language: 'cpp')
endif
backtrace_dep = cpp_compiler.find_library('execinfo', required: false)
epoll_dep = dependency('epoll-shim', required: false) # timerfd on BSDs
inotify_dep = dependency('libinotify', required: false) # inotify on BSDs
re2 = dependency('re2', required: true)
# Handle options
systemd_option = get_option('systemd')
systemd = dependency('systemd', required: systemd_option)
systemd_option.enable_auto_if(systemd.found())
if (systemd_option.enabled())
message('Enabling systemd integration')
add_project_arguments('-DUSES_SYSTEMD', language: 'cpp')
subdir('systemd')
endif
if get_option('buildtype') == 'debug'
add_project_arguments('-DHYPRLAND_DEBUG', language: 'cpp')
endif
# Generate hyprland version and populate version.h
run_command('sh', '-c', 'scripts/generateVersion.sh', check: true)
# Make shader files includable
run_command('sh', '-c', 'scripts/generateShaderIncludes.sh', check: true)
# Install headers
globber = run_command('find', 'src', '-name', '*.h*', '-o', '-name', '*.inc', check: true)
headers = globber.stdout().strip().split('\n')
foreach file : headers
install_headers(file, subdir: 'hyprland', preserve_path: true)
endforeach
tracy = dependency('tracy', static: true, required: get_option('tracy_enable'))
if get_option('tracy_enable') and get_option('buildtype') != 'debugoptimized'
warning('Profiling builds should set -- buildtype = debugoptimized')
endif
subdir('protocols')
subdir('src')
subdir('hyprctl')
subdir('assets')
subdir('example')
subdir('docs')
if get_option('hyprpm').enabled()
subdir('hyprpm/src')
endif
# Generate hyprland.pc
pkg_install_dir = join_paths(get_option('datadir'), 'pkgconfig')
import('pkgconfig').generate(
name: 'Hyprland',
filebase: 'hyprland',
url: 'https://github.com/hyprwm/Hyprland',
description: 'Hyprland header files',
install_dir: pkg_install_dir,
subdirs: ['', 'hyprland/protocols', 'hyprland'],
)

View file

@ -1,5 +0,0 @@
option('xwayland', type: 'feature', value: 'auto', description: 'Enable support for X11 applications')
option('systemd', type: 'feature', value: 'auto', description: 'Enable systemd integration')
option('uwsm', type: 'feature', value: 'enabled', description: 'Enable uwsm integration (only if systemd is enabled)')
option('hyprpm', type: 'feature', value: 'enabled', description: 'Enable hyprpm')
option('tracy_enable', type: 'boolean', value: false , description: 'Enable profiling')

View file

@ -6,21 +6,21 @@
pkgconf,
makeWrapper,
cmake,
meson,
ninja,
aquamarine,
binutils,
cairo,
epoll-shim,
git,
glaze,
glaze-hyprland,
gtest,
hyprcursor,
hyprgraphics,
hyprland-protocols,
hyprland-qtutils,
hyprland-guiutils,
hyprlang,
hyprutils,
hyprwayland-scanner,
hyprwire,
libGL,
libdrm,
libexecinfo,
@ -28,6 +28,7 @@
libxkbcommon,
libuuid,
libgbm,
muparser,
pango,
pciutils,
re2,
@ -40,6 +41,7 @@
xorg,
xwayland,
debug ? false,
withTests ? false,
enableXWayland ? true,
withSystemd ? lib.meta.availableOn stdenv.hostPlatform systemd,
wrapRuntimeDeps ? true,
@ -52,12 +54,13 @@
nvidiaPatches ? false,
hidpiXWayland ? false,
legacyRenderer ? false,
withHyprtester ? false,
}: let
inherit (builtins) foldl' readFile;
inherit (lib.asserts) assertMsg;
inherit (lib.attrsets) mapAttrsToList;
inherit (lib.lists) flatten concatLists optional optionals;
inherit (lib.strings) makeBinPath optionalString mesonBool mesonEnable trim;
inherit (lib.strings) makeBinPath optionalString cmakeBool trim;
fs = lib.fileset;
adapters = flatten [
@ -71,9 +74,10 @@ in
assert assertMsg (!enableNvidiaPatches) "The option `enableNvidiaPatches` has been removed.";
assert assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been removed. Please refer https://wiki.hypr.land/Configuring/XWayland";
assert assertMsg (!legacyRenderer) "The option `legacyRenderer` has been removed. Legacy renderer is no longer supported.";
assert assertMsg (!withHyprtester) "The option `withHyprtester` has been removed. Hyprtester is always built now.";
customStdenv.mkDerivation (finalAttrs: {
pname = "hyprland${optionalString debug "-debug"}";
inherit version;
inherit version withTests;
src = fs.toSource {
root = ../.;
@ -81,22 +85,23 @@ in
fs.intersection
# allows non-flake builds to only include files tracked by git
(fs.gitTracked ../.)
(fs.unions [
(fs.unions (flatten [
../assets/hyprland-portals.conf
../assets/install
../hyprctl
../hyprland.pc.in
../LICENSE
../meson_options.txt
../protocols
../src
../start
../systemd
../VERSION
(fs.fileFilter (file: file.hasExt "1") ../docs)
(fs.fileFilter (file: file.hasExt "conf" || file.hasExt "desktop") ../example)
(fs.fileFilter (file: file.hasExt "sh") ../scripts)
(fs.fileFilter (file: file.name == "meson.build") ../.)
]);
(fs.fileFilter (file: file.name == "CMakeLists.txt") ../.)
(optional withTests [../tests ../hyprtester])
]));
};
postPatch = ''
@ -107,11 +112,13 @@ in
sed -i "s#@PREFIX@/##g" hyprland.pc.in
'';
COMMITS = revCount;
DATE = date;
DIRTY = optionalString (commit == "") "dirty";
HASH = commit;
TAG = "v${trim (readFile "${finalAttrs.src}/VERSION")}";
env = {
GIT_COMMITS = revCount;
GIT_COMMIT_DATE = date;
GIT_COMMIT_HASH = commit;
GIT_DIRTY = if (commit == "") then "clean" else "dirty";
GIT_TAG = "v${trim (readFile "${finalAttrs.src}/VERSION")}";
};
depsBuildBuild = [
pkg-config
@ -119,10 +126,9 @@ in
nativeBuildInputs = [
hyprwayland-scanner
hyprwire
makeWrapper
meson
ninja
cmake # needed for glaze
cmake
pkg-config
];
@ -137,18 +143,21 @@ in
aquamarine
cairo
git
glaze
glaze-hyprland
gtest
hyprcursor
hyprgraphics
hyprland-protocols
hyprlang
hyprutils
hyprwire
libdrm
libGL
libinput
libuuid
libxkbcommon
libgbm
muparser
pango
pciutils
re2
@ -174,34 +183,48 @@ in
strictDeps = true;
mesonBuildType =
cmakeBuildType =
if debug
then "debug"
else "release";
then "Debug"
else "RelWithDebInfo";
mesonFlags = flatten [
(mapAttrsToList mesonEnable {
"xwayland" = enableXWayland;
"systemd" = withSystemd;
"uwsm" = false;
"hyprpm" = false;
})
(mapAttrsToList mesonBool {
"b_pch" = false;
"tracy_enable" = false;
})
];
# we want as much debug info as possible
dontStrip = debug;
cmakeFlags = mapAttrsToList cmakeBool {
"BUILT_WITH_NIX" = true;
"NO_XWAYLAND" = !enableXWayland;
"LEGACY_RENDERER" = legacyRenderer;
"NO_SYSTEMD" = !withSystemd;
"CMAKE_DISABLE_PRECOMPILE_HEADERS" = true;
"NO_UWSM" = true;
"NO_HYPRPM" = true;
"TRACY_ENABLE" = false;
"WITH_TESTS" = withTests;
};
preConfigure = ''
substituteInPlace hyprtester/CMakeLists.txt --replace-fail \
"\''${CMAKE_CURRENT_BINARY_DIR}" \
"${placeholder "out"}/bin"
'';
postInstall = ''
${optionalString wrapRuntimeDeps ''
wrapProgram $out/bin/Hyprland \
--suffix PATH : ${makeBinPath [
binutils
hyprland-qtutils
hyprland-guiutils
pciutils
pkgconf
]}
''}
${optionalString withTests ''
install hyprtester/pointer-warp -t $out/bin
install hyprtester/pointer-scroll -t $out/bin
install hyprland_gtests -t $out/bin
''}
'';
passthru.providedSessions = ["hyprland"];

View file

@ -1,69 +0,0 @@
{
lib,
stdenv,
stdenvAdapters,
cmake,
pkg-config,
hyprland,
hyprwayland-scanner,
version ? "git",
}: let
inherit (lib.lists) flatten foldl';
inherit (lib.sources) cleanSourceWith cleanSource;
inherit (lib.strings) hasSuffix cmakeBool;
adapters = flatten [
stdenvAdapters.useMoldLinker
stdenvAdapters.keepDebugInfo
];
customStdenv = foldl' (acc: adapter: adapter acc) stdenv adapters;
in
customStdenv.mkDerivation (finalAttrs: {
pname = "hyprtester";
inherit version;
src = cleanSourceWith {
filter = name: _type: let
baseName = baseNameOf (toString name);
in
! (hasSuffix ".nix" baseName);
src = cleanSource ../.;
};
nativeBuildInputs = [
cmake
pkg-config
hyprwayland-scanner
];
buildInputs = hyprland.buildInputs;
preConfigure = ''
substituteInPlace hyprtester/CMakeLists.txt --replace-fail \
"\''${CMAKE_CURRENT_BINARY_DIR}" \
"${placeholder "out"}/bin"
cmake -S . -B .
cmake --build . --target generate-protocol-headers -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
cd hyprtester
'';
postInstall = ''
install pointer-warp -t $out/bin
install pointer-scroll -t $out/bin
'';
cmakeBuildType = "Debug";
cmakeFlags = [(cmakeBool "TESTS" true)];
meta = {
homepage = "https://github.com/hyprwm/Hyprland";
description = "Hyprland testing framework";
license = lib.licenses.bsd3;
platforms = hyprland.meta.platforms;
mainProgram = "hyprtester";
};
})

View file

@ -24,11 +24,13 @@ in {
inputs.hyprcursor.overlays.default
inputs.hyprgraphics.overlays.default
inputs.hyprland-protocols.overlays.default
inputs.hyprland-qtutils.overlays.default
inputs.hyprland-guiutils.overlays.default
inputs.hyprlang.overlays.default
inputs.hyprutils.overlays.default
inputs.hyprwayland-scanner.overlays.default
inputs.hyprwire.overlays.default
self.overlays.udis86
self.overlays.glaze
# Hyprland packages themselves
(final: _prev: let
@ -43,9 +45,14 @@ in {
};
hyprland-unwrapped = final.hyprland.override {wrapRuntimeDeps = false;};
hyprtester = final.callPackage ./hyprtester.nix {
inherit version;
};
hyprland-with-tests = final.hyprland.override {withTests = true;};
hyprland-with-hyprtester =
builtins.trace ''
hyprland-with-hyprtester was removed. Please use the hyprland package.
Hyprtester is always built now.
''
final.hyprland;
# deprecated packages
hyprland-legacy-renderer =
@ -104,4 +111,13 @@ in {
patches = [];
});
};
# Even though glaze itself disables it by default, nixpkgs sets ENABLE_SSL set to true.
# Since we don't include openssl, the build failes without the `enableSSL = false;` override
glaze = final: prev: {
glaze-hyprland = prev.glaze.override {
enableSSL = false;
enableInterop = false;
};
};
}

View file

@ -1,17 +1,16 @@
inputs: pkgs: let
flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system};
hyprland = flake.hyprland;
hyprland = flake.hyprland-with-tests;
in {
tests = pkgs.testers.runNixOSTest {
name = "hyprland-tests";
nodes.machine = {pkgs, ...}: {
environment.systemPackages = with pkgs; [
flake.hyprtester
# Programs needed for tests
jq
kitty
wl-clipboard
xorg.xeyes
];
@ -37,7 +36,7 @@ in {
};
# Test configuration
environment.etc."test.conf".source = "${flake.hyprtester}/share/hypr/test.conf";
environment.etc."test.conf".source = "${hyprland}/share/hypr/test.conf";
# Disable portals
xdg.portal.enable = pkgs.lib.mkForce false;
@ -69,14 +68,21 @@ in {
# Wait for tty to be up
machine.wait_for_unit("multi-user.target")
# Run gtests
print("Running gtests")
exit_status, _out = machine.execute("su - alice -c 'hyprland_gtests 2>&1 | tee /tmp/gtestslog; exit ''${PIPESTATUS[0]}'")
machine.execute(f'echo {exit_status} > /tmp/exit_status_gtests')
# 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 ${flake.hyprtester}/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.conf -p ${hyprland}/lib/hyprtestplugin.so 2>&1 | tee /tmp/testerlog; exit ''${PIPESTATUS[0]}'")
print(f"Hyprtester exited with {exit_status}")
# Copy logs to host
machine.execute('cp "$(find /tmp/hypr -name *.log | head -1)" /tmp/hyprlog')
machine.execute(f'echo {exit_status} > /tmp/exit_status')
machine.copy_from_vm("/tmp/gtestslog")
machine.copy_from_vm("/tmp/testerlog")
machine.copy_from_vm("/tmp/hyprlog")
machine.copy_from_vm("/tmp/exit_status")

View file

@ -1,117 +0,0 @@
wayland_protos = dependency(
'wayland-protocols',
version: '>=1.45',
fallback: 'wayland-protocols',
default_options: ['tests=false'],
)
hyprland_protos = dependency(
'hyprland-protocols',
version: '>=0.6.4',
fallback: 'hyprland-protocols',
)
wayland_protocol_dir = wayland_protos.get_variable('pkgdatadir')
hyprland_protocol_dir = hyprland_protos.get_variable('pkgdatadir')
hyprwayland_scanner_dep = dependency('hyprwayland-scanner', version: '>=0.3.10', native: true)
hyprwayland_scanner = find_program(
hyprwayland_scanner_dep.get_variable('hyprwayland_scanner'),
native: true,
)
protocols = [
'wlr-gamma-control-unstable-v1.xml',
'wlr-foreign-toplevel-management-unstable-v1.xml',
'wlr-output-power-management-unstable-v1.xml',
'input-method-unstable-v2.xml',
'virtual-keyboard-unstable-v1.xml',
'wlr-virtual-pointer-unstable-v1.xml',
'wlr-output-management-unstable-v1.xml',
'kde-server-decoration.xml',
'wlr-layer-shell-unstable-v1.xml',
'wayland-drm.xml',
'wlr-data-control-unstable-v1.xml',
'wlr-screencopy-unstable-v1.xml',
'xx-color-management-v4.xml',
'frog-color-management-v1.xml',
hyprland_protocol_dir / 'protocols/hyprland-global-shortcuts-v1.xml',
hyprland_protocol_dir / 'protocols/hyprland-toplevel-export-v1.xml',
hyprland_protocol_dir / 'protocols/hyprland-toplevel-mapping-v1.xml',
hyprland_protocol_dir / 'protocols/hyprland-focus-grab-v1.xml',
hyprland_protocol_dir / 'protocols/hyprland-ctm-control-v1.xml',
hyprland_protocol_dir / 'protocols/hyprland-surface-v1.xml',
hyprland_protocol_dir / 'protocols/hyprland-lock-notify-v1.xml',
wayland_protocol_dir / 'staging/tearing-control/tearing-control-v1.xml',
wayland_protocol_dir / 'staging/fractional-scale/fractional-scale-v1.xml',
wayland_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml',
wayland_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml',
wayland_protocol_dir / 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml',
wayland_protocol_dir / 'unstable/relative-pointer/relative-pointer-unstable-v1.xml',
wayland_protocol_dir / 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml',
wayland_protocol_dir / 'staging/alpha-modifier/alpha-modifier-v1.xml',
wayland_protocol_dir / 'staging/ext-foreign-toplevel-list/ext-foreign-toplevel-list-v1.xml',
wayland_protocol_dir / 'unstable/pointer-gestures/pointer-gestures-unstable-v1.xml',
wayland_protocol_dir / 'unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml',
wayland_protocol_dir / 'unstable/text-input/text-input-unstable-v3.xml',
wayland_protocol_dir / 'unstable/text-input/text-input-unstable-v1.xml',
wayland_protocol_dir / 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml',
wayland_protocol_dir / 'staging/xdg-activation/xdg-activation-v1.xml',
wayland_protocol_dir / 'staging/ext-idle-notify/ext-idle-notify-v1.xml',
wayland_protocol_dir / 'staging/ext-session-lock/ext-session-lock-v1.xml',
wayland_protocol_dir / 'stable/tablet/tablet-v2.xml',
wayland_protocol_dir / 'stable/presentation-time/presentation-time.xml',
wayland_protocol_dir / 'stable/xdg-shell/xdg-shell.xml',
wayland_protocol_dir / 'unstable/primary-selection/primary-selection-unstable-v1.xml',
wayland_protocol_dir / 'staging/xwayland-shell/xwayland-shell-v1.xml',
wayland_protocol_dir / 'stable/viewporter/viewporter.xml',
wayland_protocol_dir / 'stable/linux-dmabuf/linux-dmabuf-v1.xml',
wayland_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml',
wayland_protocol_dir / 'staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml',
wayland_protocol_dir / 'staging/xdg-dialog/xdg-dialog-v1.xml',
wayland_protocol_dir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml',
wayland_protocol_dir / 'staging/security-context/security-context-v1.xml',
wayland_protocol_dir / 'staging/content-type/content-type-v1.xml',
wayland_protocol_dir / 'staging/color-management/color-management-v1.xml',
wayland_protocol_dir / 'staging/xdg-toplevel-tag/xdg-toplevel-tag-v1.xml',
wayland_protocol_dir / 'staging/xdg-system-bell/xdg-system-bell-v1.xml',
wayland_protocol_dir / 'staging/ext-workspace/ext-workspace-v1.xml',
wayland_protocol_dir / 'staging/ext-data-control/ext-data-control-v1.xml',
wayland_protocol_dir / 'staging/pointer-warp/pointer-warp-v1.xml',
]
wl_protocols = []
foreach protocol : protocols
wl_protocols += custom_target(
protocol.underscorify(),
input: protocol,
install: true,
install_dir: [false, join_paths(get_option('includedir'), 'hyprland/protocols')],
output: ['@BASENAME@.cpp', '@BASENAME@.hpp'],
command: [hyprwayland_scanner, '@INPUT@', '@OUTDIR@'],
)
endforeach
# wayland.xml generation
wayland_scanner = dependency('wayland-scanner', native: true)
wayland_scanner_datadir = wayland_scanner.get_variable('pkgdatadir')
wayland_xml = wayland_scanner_datadir / 'wayland.xml'
wayland_protocol = custom_target(
wayland_xml.underscorify(),
input: wayland_xml,
install: true,
install_dir: [false, join_paths(get_option('includedir'), 'hyprland/protocols')],
output: ['@BASENAME@.cpp', '@BASENAME@.hpp'],
command: [hyprwayland_scanner, '--wayland-enums', '@INPUT@', '@OUTDIR@'],
)
lib_server_protos = static_library(
'server_protos',
wl_protocols + wayland_protocol,
)
server_protos = declare_dependency(
link_with: lib_server_protos,
sources: wl_protocols + wayland_protocol,
)

View file

@ -1,27 +0,0 @@
#!/bin/sh
# if the git directory doesn't exist, don't gather data to avoid overwriting, unless
# the version file is missing altogether (otherwise compiling will fail)
if [ ! -d ./.git ]; then
if [ -f ./src/version.h ]; then
exit 0
fi
fi
cp -fr ./src/version.h.in ./src/version.h
HASH=${HASH-$(git rev-parse HEAD)}
BRANCH=${BRANCH-$(git branch --show-current)}
MESSAGE=${MESSAGE-$(git show | head -n 5 | tail -n 1 | sed -e 's/#//g' -e 's/\"//g')}
DATE=${DATE-$(git show --no-patch --format=%cd --date=local)}
DIRTY=${DIRTY-$(git diff-index --quiet HEAD -- || echo dirty)}
TAG=${TAG-$(git describe --tags)}
COMMITS=${COMMITS-$(git rev-list --count HEAD)}
sed -i -e "s#@HASH@#${HASH}#" ./src/version.h
sed -i -e "s#@BRANCH@#${BRANCH}#" ./src/version.h
sed -i -e "s#@MESSAGE@#${MESSAGE}#" ./src/version.h
sed -i -e "s#@DATE@#${DATE}#" ./src/version.h
sed -i -e "s#@DIRTY@#${DIRTY}#" ./src/version.h
sed -i -e "s#@TAG@#${TAG}#" ./src/version.h
sed -i -e "s#@COMMITS@#${COMMITS}#" ./src/version.h

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,7 @@
#include "managers/XWaylandManager.hpp"
#include "managers/KeybindManager.hpp"
#include "managers/SessionLockManager.hpp"
#include "desktop/Window.hpp"
#include "desktop/view/Window.hpp"
#include "protocols/types/ColorManagement.hpp"
#include <aquamarine/backend/Backend.hpp>
@ -40,6 +40,7 @@ class CCompositor {
} m_drmRenderNode;
bool m_initialized = false;
bool m_safeMode = false;
SP<Aquamarine::CBackend> m_aqBackend;
std::string m_hyprTempDataRoot = "";
@ -55,6 +56,7 @@ class CCompositor {
std::vector<PHLLS> m_layers;
std::vector<PHLWINDOWREF> m_windowsFadingOut;
std::vector<PHLLSREF> m_surfacesFadingOut;
std::vector<SP<Desktop::View::IView>> m_otherViews;
std::unordered_map<std::string, MONITORID> m_monitorIDMap;
std::unordered_map<std::string, WORKSPACEID> m_seenMonitorWorkspaceMap; // map of seen monitor names to workspace IDs
@ -65,12 +67,7 @@ class CCompositor {
void cleanup();
void bumpNofile();
void restoreNofile();
WP<CWLSurfaceResource> m_lastFocus;
PHLWINDOWREF m_lastWindow;
PHLMONITORREF m_lastMonitor;
std::vector<PHLWINDOWREF> m_windowFocusHistory; // first element is the most recently focused
bool setWatchdogFd(int fd);
bool m_readyToProcess = false;
bool m_sessionActive = true;
@ -99,8 +96,6 @@ class CCompositor {
PHLMONITOR getMonitorFromCursor();
PHLMONITOR getMonitorFromVector(const Vector2D&);
void removeWindowFromVectorSafe(PHLWINDOW);
void focusWindow(PHLWINDOW, SP<CWLSurfaceResource> pSurface = nullptr, bool preserveFocusHistory = false);
void focusSurface(SP<CWLSurfaceResource>, PHLWINDOW pWindowOwner = nullptr);
bool monitorExists(PHLMONITOR);
PHLWINDOW vectorToWindowUnified(const Vector2D&, uint8_t properties, PHLWINDOW pIgnoreWindow = nullptr);
SP<CWLSurfaceResource> vectorToLayerSurface(const Vector2D&, std::vector<PHLLSREF>*, Vector2D*, PHLLS*, bool aboveLockscreen = false);
@ -125,10 +120,10 @@ class CCompositor {
WORKSPACEID getNextAvailableNamedWorkspace();
bool isPointOnAnyMonitor(const Vector2D&);
bool isPointOnReservedArea(const Vector2D& point, const PHLMONITOR monitor = nullptr);
CBox calculateX11WorkArea();
PHLMONITOR getMonitorInDirection(const char&);
PHLMONITOR getMonitorInDirection(PHLMONITOR, const char&);
void updateAllWindowsAnimatedDecorationValues();
void updateWindowAnimatedDecorationValues(PHLWINDOW);
MONITORID getNextAvailableMonitorID(std::string const& name);
void moveWorkspaceToMonitor(PHLWORKSPACE, PHLMONITOR, bool noWarpCursor = false);
void swapActiveWorkspaces(PHLMONITOR, PHLMONITOR);
@ -136,7 +131,7 @@ class CCompositor {
bool workspaceIDOutOfBounds(const WORKSPACEID&);
void setWindowFullscreenInternal(const PHLWINDOW PWINDOW, const eFullscreenMode MODE);
void setWindowFullscreenClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE);
void setWindowFullscreenState(const PHLWINDOW PWINDOW, const SFullscreenState state);
void setWindowFullscreenState(const PHLWINDOW PWINDOW, const Desktop::View::SFullscreenState state);
void changeWindowFullscreenModeClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE, const bool ON);
PHLWINDOW getX11Parent(PHLWINDOW);
void scheduleFrameForMonitor(PHLMONITOR, Aquamarine::IOutput::scheduleFrameReason reason = Aquamarine::IOutput::AQ_SCHEDULE_CLIENT_UNKNOWN);
@ -150,13 +145,14 @@ class CCompositor {
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!
void setActiveMonitor(PHLMONITOR);
bool isWorkspaceSpecial(const WORKSPACEID&);
WORKSPACEID getNewSpecialID();
void performUserChecks();
void moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWorkspace);
PHLWINDOW getForceFocus();
void scheduleMonitorStateRecheck();
void arrangeMonitors();
void checkMonitorOverlaps();
void enterUnsafeState();
void leaveUnsafeState();
void setPreferredScaleForSurface(SP<CWLSurfaceResource> pSurface, double scale);
@ -167,27 +163,30 @@ class CCompositor {
std::optional<unsigned int> getVTNr();
NColorManagement::SImageDescription getPreferredImageDescription();
NColorManagement::SImageDescription getHDRImageDescription();
bool shouldChangePreferredImageDescription();
bool supportsDrmSyncobjTimeline() const;
std::string m_explicitConfigPath;
private:
void initAllSignals();
void removeAllSignals();
void cleanEnvironment();
void setRandomSplash();
void initManagers(eManagersInitStage stage);
void prepareFallbackOutput();
void createLockFile();
void removeLockFile();
void setMallocThreshold();
void initAllSignals();
void removeAllSignals();
void cleanEnvironment();
void setRandomSplash();
void initManagers(eManagersInitStage stage);
void prepareFallbackOutput();
void createLockFile();
void removeLockFile();
void setMallocThreshold();
void openSafeModeBox();
uint64_t m_hyprlandPID = 0;
wl_event_source* m_critSigSource = nullptr;
rlimit m_originalNofile = {};
uint64_t m_hyprlandPID = 0;
wl_event_source* m_critSigSource = nullptr;
rlimit m_originalNofile = {};
Hyprutils::OS::CFileDescriptor m_watchdogWriteFd;
std::vector<PHLWORKSPACEREF> m_workspaces;
std::vector<PHLWORKSPACEREF> m_workspaces;
};
inline UP<CCompositor> g_pCompositor;

View file

@ -97,27 +97,29 @@ class CCssGapData : public ICustomConfigValueData {
int64_t m_bottom;
int64_t m_left;
void parseGapData(CVarList varlist) {
void parseGapData(CVarList2 varlist) {
const auto toInt = [](std::string_view string) -> int { return std::stoi(std::string(string)); };
switch (varlist.size()) {
case 1: {
*this = CCssGapData(std::stoi(varlist[0]));
*this = CCssGapData(toInt(varlist[0]));
break;
}
case 2: {
*this = CCssGapData(std::stoi(varlist[0]), std::stoi(varlist[1]));
*this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]));
break;
}
case 3: {
*this = CCssGapData(std::stoi(varlist[0]), std::stoi(varlist[1]), std::stoi(varlist[2]));
*this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]), toInt(varlist[2]));
break;
}
case 4: {
*this = CCssGapData(std::stoi(varlist[0]), std::stoi(varlist[1]), std::stoi(varlist[2]), std::stoi(varlist[3]));
*this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]), toInt(varlist[2]), toInt(varlist[3]));
break;
}
default: {
Debug::log(WARN, "Too many arguments provided for gaps.");
*this = CCssGapData(std::stoi(varlist[0]), std::stoi(varlist[1]), std::stoi(varlist[2]), std::stoi(varlist[3]));
Log::logger->log(Log::WARN, "Too many arguments provided for gaps.");
*this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]), toInt(varlist[2]), toInt(varlist[3]));
break;
}
}

View file

@ -15,12 +15,6 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{1, 0, 20},
},
SConfigOptionDescription{
.value = "general:no_border_on_floating",
.description = "disable borders for floating windows",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "general:gaps_in",
.description = "gaps between windows\n\nsupports css style gaps (top, right, bottom, left -> 5 10 15 20)",
@ -142,6 +136,18 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "general:modal_parent_blocking",
.description = "if true, parent windows of modals will not be interactive.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "general:locale",
.description = "overrides the system locale",
.type = CONFIG_OPTION_STRING_SHORT,
.data = SConfigOptionDescription::SStringData{""},
},
/*
* decoration:
@ -429,7 +435,7 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
},
SConfigOptionDescription{
.value = "input:kb_file",
.description = "Appropriate XKB keymap parameter",
.description = "Appropriate XKB keymap file",
.type = CONFIG_OPTION_STRING_LONG,
.data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET?
},
@ -478,6 +484,12 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "input:rotation",
.description = "Sets the rotation of a device in degrees clockwise off the logical neutral position. Value is clamped to the range 0 to 359.",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{0, 0, 359},
},
SConfigOptionDescription{
.value = "input:left_handed",
.description = "Switches RMB and LMB",
@ -683,9 +695,9 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
SConfigOptionDescription{
.value = "input:virtualkeyboard:share_states",
.description = "Unify key down states and modifier states with other keyboards",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
.description = "Unify key down states and modifier states with other keyboards. 0 -> no, 1 -> yes, 2 -> yes unless IME client",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{2, 0, 2},
},
SConfigOptionDescription{
.value = "input:virtualkeyboard:release_pressed_on_close",
@ -1103,6 +1115,12 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SRangeData{0, -20, 20},
},
SConfigOptionDescription{
.value = "group:groupbar:blur",
.description = "enable background blur for groupbars",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
/*
* misc:
@ -1261,11 +1279,11 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "misc:new_window_takes_over_fullscreen",
.description = "if there is a fullscreen or maximized window, decide whether a new tiled window opened should replace it, stay behind or disable the fullscreen/maximized "
"state. 0 - behind, 1 - takes over, 2 - unfullscreen/unmaxize [0/1/2]",
.value = "misc:on_focus_under_fullscreen",
.description = "if there is a fullscreen or maximized window, decide whether a tiled window requested to focus should replace it, stay behind or disable the "
"fullscreen/maximized state. 0 - ignore focus request (keep focus on fullscreen window), 1 - takes over, 2 - unfullscreen/unmaximize [0/1/2]",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{0, 0, 2},
.data = SConfigOptionDescription::SRangeData{2, 0, 2},
},
SConfigOptionDescription{
.value = "misc:exit_window_retains_fullscreen",
@ -1298,8 +1316,14 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "misc:disable_hyprland_qtutils_check",
.description = "disable the warning if hyprland-qtutils is missing",
.value = "misc:disable_hyprland_guiutils_check",
.description = "disable the warning if hyprland-guiutils is missing",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "misc:disable_watchdog_warning",
.description = "whether to disable the warning about not using start-hyprland.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
@ -1319,7 +1343,7 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.value = "misc:anr_missed_pings",
.description = "number of missed pings before showing the ANR dialog",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{1, 1, 10},
.data = SConfigOptionDescription::SRangeData{5, 1, 20},
},
SConfigOptionDescription{
.value = "misc:screencopy_force_8b",
@ -1327,6 +1351,18 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "misc:disable_scale_notification",
.description = "disables notification popup when a monitor fails to set a suitable scale and falls back to suggested",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "misc:size_limits_tiled",
.description = "whether to apply minsize and maxsize rules to tiled windows",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
/*
* binds:
@ -1518,6 +1554,19 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "render:non_shader_cm",
.description = "Enable CM without shader. 0 - disable, 1 - whenever possible, 2 - DS and passthrough only, 3 - disable and ignore CM issues",
.type = CONFIG_OPTION_CHOICE,
.data = SConfigOptionDescription::SChoiceData{0, "disable,always,ondemand,ignore"},
},
SConfigOptionDescription{
.value = "render:cm_sdr_eotf",
.description = "Default transfer function for displaying SDR apps. 0 - Treat unspecified as sRGB, 1 - Treat unspecified as Gamma 2.2, 2 - Treat "
"unspecified and sRGB as Gamma 2.2",
.type = CONFIG_OPTION_CHOICE,
.data = SConfigOptionDescription::SChoiceData{0, "srgb,gamma22,gamma22force"},
},
/*
* cursor:
@ -1603,6 +1652,18 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "cursor:zoom_disable_aa",
.description = "If enabled, when zooming, no antialiasing will be used (zoom will be pixelated)",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "cursor:zoom_detached_camera",
.description = "Detaches the camera from the mouse when zoomed in, only ever moving to keep the mouse in view",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "cursor:enable_hyprcursor",
.description = "whether to enable hyprcursor support",
@ -1621,6 +1682,12 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "cursor:hide_on_tablet",
.description = "Hides the cursor when the last input was a tablet input until a mouse input is done.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "cursor:use_cpu_buffer",
.description = "Makes HW cursors use a CPU buffer. Required on Nvidia to have HW cursors. Experimental",
@ -1897,12 +1964,6 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_STRING_SHORT,
.data = SConfigOptionDescription::SStringData{"left"},
},
SConfigOptionDescription{
.value = "master:inherit_fullscreen",
.description = "inherit fullscreen status when cycling/swapping to another window (e.g. monocle layout)",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "master:slave_count_for_center_master",
.description = "when using orientation=center, make the master window centered only when at least this many slave windows are open. (Set 0 to always_center_master)",
@ -1950,4 +2011,16 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
/*
* Quirks
*/
SConfigOptionDescription{
.value = "quirks:prefer_hdr",
.description = "Prefer HDR mode. 0 - off, 1 - always, 2 - gamescope only",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{.value = 0, .min = 0, .max = 2},
},
};

File diff suppressed because it is too large Load diff

View file

@ -12,16 +12,16 @@
#include <functional>
#include <xf86drmMode.h>
#include "../helpers/Monitor.hpp"
#include "../desktop/Window.hpp"
#include "../desktop/LayerRule.hpp"
#include "../desktop/view/Window.hpp"
#include "ConfigDataValues.hpp"
#include "../SharedDefs.hpp"
#include "../helpers/Color.hpp"
#include "../desktop/DesktopTypes.hpp"
#include "../desktop/reserved/ReservedArea.hpp"
#include "../helpers/memory/Memory.hpp"
#include "../desktop/WindowRule.hpp"
#include "../managers/XWaylandManager.hpp"
#include "../managers/KeybindManager.hpp"
#include <hyprlang.hpp>
@ -49,13 +49,6 @@ struct SWorkspaceRule {
std::map<std::string, std::string> layoutopts;
};
struct SMonitorAdditionalReservedArea {
int top = 0;
int bottom = 0;
int left = 0;
int right = 0;
};
struct SPluginKeyword {
HANDLE handle = nullptr;
std::string name = "";
@ -67,11 +60,6 @@ struct SPluginVariable {
std::string name = "";
};
struct SExecRequestedRule {
std::string szRule = "";
uint64_t iPid = 0;
};
enum eConfigOptionType : uint8_t {
CONFIG_OPTION_BOOL = 0,
CONFIG_OPTION_INT = 1, /* e.g. 0/1/2*/
@ -191,7 +179,7 @@ class CMonitorRuleParser {
void setDisabled();
void setMirror(const std::string& value);
bool setReserved(const SMonitorAdditionalReservedArea& value);
bool setReserved(const Desktop::CReservedArea& value);
private:
SMonitorRule m_rule;
@ -202,39 +190,34 @@ class CConfigManager {
public:
CConfigManager();
void init();
void reload();
std::string verify();
void init();
void reload();
std::string verify();
int getDeviceInt(const std::string&, const std::string&, const std::string& fallback = "");
float getDeviceFloat(const std::string&, const std::string&, const std::string& fallback = "");
Vector2D getDeviceVec(const std::string&, const std::string&, const std::string& fallback = "");
std::string getDeviceString(const std::string&, const std::string&, const std::string& fallback = "");
bool deviceConfigExplicitlySet(const std::string&, const std::string&);
bool deviceConfigExists(const std::string&);
Hyprlang::CConfigValue* getConfigValueSafeDevice(const std::string& dev, const std::string& val, const std::string& fallback);
bool shouldBlurLS(const std::string&);
int getDeviceInt(const std::string&, const std::string&, const std::string& fallback = "");
float getDeviceFloat(const std::string&, const std::string&, const std::string& fallback = "");
Vector2D getDeviceVec(const std::string&, const std::string&, const std::string& fallback = "");
std::string getDeviceString(const std::string&, const std::string&, const std::string& fallback = "");
bool deviceConfigExplicitlySet(const std::string&, const std::string&);
bool deviceConfigExists(const std::string&);
Hyprlang::CConfigValue* getConfigValueSafeDevice(const std::string& dev, const std::string& val, const std::string& fallback);
void* const* getConfigValuePtr(const std::string&);
Hyprlang::CConfigValue* getHyprlangConfigValuePtr(const std::string& name, const std::string& specialCat = "");
std::string getMainConfigPath();
std::string getConfigString();
void* const* getConfigValuePtr(const std::string&);
Hyprlang::CConfigValue* getHyprlangConfigValuePtr(const std::string& name, const std::string& specialCat = "");
std::string getMainConfigPath();
std::string getConfigString();
SMonitorRule getMonitorRuleFor(const PHLMONITOR);
SWorkspaceRule getWorkspaceRuleFor(PHLWORKSPACE workspace);
std::string getDefaultWorkspaceFor(const std::string&);
SMonitorRule getMonitorRuleFor(const PHLMONITOR);
SWorkspaceRule getWorkspaceRuleFor(PHLWORKSPACE workspace);
std::string getDefaultWorkspaceFor(const std::string&);
PHLMONITOR getBoundMonitorForWS(const std::string&);
std::string getBoundMonitorStringForWS(const std::string&);
const std::vector<SWorkspaceRule>& getAllWorkspaceRules();
PHLMONITOR getBoundMonitorForWS(const std::string&);
std::string getBoundMonitorStringForWS(const std::string&);
const std::vector<SWorkspaceRule>& getAllWorkspaceRules();
std::vector<SP<CWindowRule>> getMatchingRules(PHLWINDOW, bool dynamic = true, bool shadowExec = false);
std::vector<SP<CLayerRule>> getMatchingRules(PHLLS);
void ensurePersistentWorkspacesPresent();
void ensurePersistentWorkspacesPresent();
const std::vector<SConfigOptionDescription>& getAllDescriptions();
std::unordered_map<std::string, SMonitorAdditionalReservedArea> m_mAdditionalReservedAreas;
const std::vector<SConfigOptionDescription>& getAllDescriptions();
const std::unordered_map<std::string, SP<Hyprutils::Animation::SAnimationPropertyConfig>>& getAnimationConfig();
@ -259,8 +242,6 @@ class CConfigManager {
SP<Hyprutils::Animation::SAnimationPropertyConfig> getAnimationPropertyConfig(const std::string&);
void addExecRule(const SExecRequestedRule&);
void handlePluginLoads();
std::string getErrors();
@ -273,22 +254,24 @@ class CConfigManager {
std::optional<std::string> handleMonitor(const std::string&, const std::string&);
std::optional<std::string> handleBind(const std::string&, const std::string&);
std::optional<std::string> handleUnbind(const std::string&, const std::string&);
std::optional<std::string> handleWindowRule(const std::string&, const std::string&);
std::optional<std::string> handleLayerRule(const std::string&, const std::string&);
std::optional<std::string> handleWorkspaceRules(const std::string&, const std::string&);
std::optional<std::string> handleBezier(const std::string&, const std::string&);
std::optional<std::string> handleAnimation(const std::string&, const std::string&);
std::optional<std::string> handleSource(const std::string&, const std::string&);
std::optional<std::string> handleSubmap(const std::string&, const std::string&);
std::optional<std::string> handleBlurLS(const std::string&, const std::string&);
std::optional<std::string> handleBindWS(const std::string&, const std::string&);
std::optional<std::string> handleEnv(const std::string&, const std::string&);
std::optional<std::string> handlePlugin(const std::string&, const std::string&);
std::optional<std::string> handlePermission(const std::string&, const std::string&);
std::optional<std::string> handleGesture(const std::string&, const std::string&);
std::optional<std::string> handleWindowrule(const std::string&, const std::string&);
std::optional<std::string> handleLayerrule(const std::string&, const std::string&);
std::optional<std::string> handleMonitorv2(const std::string& output);
Hyprlang::CParseResult handleMonitorv2();
std::optional<std::string> addRuleFromConfigKey(const std::string& name);
std::optional<std::string> addLayerRuleFromConfigKey(const std::string& name);
Hyprlang::CParseResult reloadRules();
std::string m_configCurrentPath;
@ -307,21 +290,18 @@ class CConfigManager {
Hyprutils::Animation::CAnimationConfigTree m_animationTree;
std::string m_currentSubmap = ""; // For storing the current keybind submap
std::vector<SExecRequestedRule> m_execRequestedRules; // rules requested with exec, e.g. [workspace 2] kitty
SSubmap m_currentSubmap;
std::vector<std::string> m_declaredPlugins;
std::vector<SPluginKeyword> m_pluginKeywords;
std::vector<SPluginVariable> m_pluginVariables;
std::vector<SP<Desktop::Rule::IRule>> m_keywordRules;
bool m_isFirstLaunch = true; // For exec-once
std::vector<SMonitorRule> m_monitorRules;
std::vector<SWorkspaceRule> m_workspaceRules;
std::vector<SP<CWindowRule>> m_windowRules;
std::vector<SP<CLayerRule>> m_layerRules;
std::vector<std::string> m_blurLSNamespaces;
bool m_firstExecDispatched = false;
bool m_manualCrashInitiated = false;
@ -335,11 +315,11 @@ class CConfigManager {
uint32_t m_configValueNumber = 0;
// internal methods
void updateBlurredLS(const std::string&, const bool);
void setDefaultAnimationVars();
std::optional<std::string> resetHLConfig();
std::optional<std::string> generateConfig(std::string configPath);
std::optional<std::string> generateConfig(std::string configPath, bool safeMode = false);
std::optional<std::string> verifyConfigExists();
void reloadRuleConfigs();
void postConfigReload(const Hyprlang::CParseResult& result);
SWorkspaceRule mergeWorkspaceRules(const SWorkspaceRule&, const SWorkspaceRule&);

View file

@ -1,7 +1,9 @@
#include "ConfigWatcher.hpp"
#if defined(__linux__)
#include <linux/limits.h>
#endif
#include <sys/inotify.h>
#include "../debug/Log.hpp"
#include "../debug/log/Logger.hpp"
#include <ranges>
#include <fcntl.h>
#include <unistd.h>
@ -11,14 +13,14 @@ using namespace Hyprutils::OS;
CConfigWatcher::CConfigWatcher() : m_inotifyFd(inotify_init()) {
if (!m_inotifyFd.isValid()) {
Debug::log(ERR, "CConfigWatcher couldn't open an inotify node. Config will not be automatically reloaded");
Log::logger->log(Log::ERR, "CConfigWatcher couldn't open an inotify node. Config will not be automatically reloaded");
return;
}
// TODO: make CFileDescriptor take F_GETFL, F_SETFL
const int FLAGS = fcntl(m_inotifyFd.get(), F_GETFL, 0);
if (fcntl(m_inotifyFd.get(), F_SETFL, FLAGS | O_NONBLOCK) < 0) {
Debug::log(ERR, "CConfigWatcher couldn't non-block inotify node. Config will not be automatically reloaded");
Log::logger->log(Log::ERR, "CConfigWatcher couldn't non-block inotify node. Config will not be automatically reloaded");
m_inotifyFd.reset();
return;
}
@ -76,19 +78,19 @@ void CConfigWatcher::onInotifyEvent() {
const auto* ev = rc<const inotify_event*>(buffer.data() + offset);
if (offset + sizeof(inotify_event) > sc<size_t>(bytesRead)) {
Debug::log(ERR, "CConfigWatcher: malformed inotify event, truncated header");
Log::logger->log(Log::ERR, "CConfigWatcher: malformed inotify event, truncated header");
break;
}
if (offset + sizeof(inotify_event) + ev->len > sc<size_t>(bytesRead)) {
Debug::log(ERR, "CConfigWatcher: malformed inotify event, truncated name field");
Log::logger->log(Log::ERR, "CConfigWatcher: malformed inotify event, truncated name field");
break;
}
const auto WD = std::ranges::find_if(m_watches, [wd = ev->wd](const auto& e) { return e.wd == wd; });
if (WD == m_watches.end())
Debug::log(ERR, "CConfigWatcher: got an event for wd {} which we don't have?!", ev->wd);
Log::logger->log(Log::ERR, "CConfigWatcher: got an event for wd {} which we don't have?!", ev->wd);
else
m_watchCallback(SConfigWatchEvent{
.file = WD->file,

View file

@ -1,7 +0,0 @@
#pragma once
#include "../defines.hpp"
namespace NCrashReporter {
void createAndSaveCrash(int sig);
};

View file

@ -40,10 +40,12 @@ using namespace Hyprutils::OS;
#include "../devices/ITouch.hpp"
#include "../devices/Tablet.hpp"
#include "../protocols/GlobalShortcuts.hpp"
#include "debug/RollingLogFollow.hpp"
#include "debug/log/RollingLogFollow.hpp"
#include "config/ConfigManager.hpp"
#include "helpers/MiscFunctions.hpp"
#include "../desktop/LayerSurface.hpp"
#include "../desktop/view/LayerSurface.hpp"
#include "../desktop/rule/Engine.hpp"
#include "../desktop/state/FocusState.hpp"
#include "../version.h"
#include "../Compositor.hpp"
@ -111,13 +113,14 @@ static std::string availableModesForOutput(PHLMONITOR pMonitor, eHyprCtlOutputFo
}
const std::array<const char*, CMonitor::SC_CHECKS_COUNT> SOLITARY_REASONS_JSON = {
"\"UNKNOWN\"", "\"NOTIFICATION\"", "\"LOCK\"", "\"WORKSPACE\"", "\"WINDOWED\"", "\"DND\"", "\"SPECIAL\"", "\"ALPHA\"",
"\"OFFSET\"", "\"CANDIDATE\"", "\"OPAQUE\"", "\"TRANSFORM\"", "\"OVERLAYS\"", "\"FLOAT\"", "\"WORKSPACES\"", "\"SURFACES\"",
"\"UNKNOWN\"", "\"NOTIFICATION\"", "\"LOCK\"", "\"WORKSPACE\"", "\"WINDOWED\"", "\"DND\"", "\"SPECIAL\"", "\"ALPHA\"", "\"OFFSET\"",
"\"CANDIDATE\"", "\"OPAQUE\"", "\"TRANSFORM\"", "\"OVERLAYS\"", "\"FLOAT\"", "\"WORKSPACES\"", "\"SURFACES\"", "\"CONFIGERROR\"",
};
const std::array<const char*, CMonitor::SC_CHECKS_COUNT> SOLITARY_REASONS_TEXT = {
"unknown reason", "notification", "session lock", "invalid workspace", "windowed mode", "dnd active", "special workspace", "alpha channel",
"workspace offset", "missing candidate", "not opaque", "surface transformations", "other overlays", "floating windows", "other workspaces", "subsurfaces",
"unknown reason", "notification", "session lock", "invalid workspace", "windowed mode", "dnd active",
"special workspace", "alpha channel", "workspace offset", "missing candidate", "not opaque", "surface transformations",
"other overlays", "floating windows", "other workspaces", "subsurfaces", "config error",
};
std::string CHyprCtl::getSolitaryBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format) {
@ -128,7 +131,7 @@ std::string CHyprCtl::getSolitaryBlockedReason(Hyprutils::Memory::CSharedPointer
std::string reasonStr = "";
const auto TEXTS = format == eHyprCtlOutputFormat::FORMAT_JSON ? SOLITARY_REASONS_JSON : SOLITARY_REASONS_TEXT;
for (int i = 0; i < CMonitor::SC_CHECKS_COUNT; i++) {
for (uint32_t i = 0; i < CMonitor::SC_CHECKS_COUNT; i++) {
if (reasons & (1 << i)) {
if (reasonStr != "")
reasonStr += ",";
@ -240,19 +243,25 @@ std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer<CMonitor>
"disabled": {},
"currentFormat": "{}",
"mirrorOf": "{}",
"availableModes": [{}]
"availableModes": [{}],
"colorManagementPreset": "{}",
"sdrBrightness": {:.2f},
"sdrSaturation": {:.2f},
"sdrMinLuminance": {:.2f},
"sdrMaxLuminance": {}
}},)#",
m->m_id, escapeJSONStrings(m->m_name), escapeJSONStrings(m->m_shortDescription), escapeJSONStrings(m->m_output->make), escapeJSONStrings(m->m_output->model),
escapeJSONStrings(m->m_output->serial), sc<int>(m->m_pixelSize.x), sc<int>(m->m_pixelSize.y), sc<int>(m->m_output->physicalSize.x),
sc<int>(m->m_output->physicalSize.y), m->m_refreshRate, sc<int>(m->m_position.x), sc<int>(m->m_position.y), m->activeWorkspaceID(),
(!m->m_activeWorkspace ? "" : escapeJSONStrings(m->m_activeWorkspace->m_name)), m->activeSpecialWorkspaceID(),
escapeJSONStrings(m->m_activeSpecialWorkspace ? m->m_activeSpecialWorkspace->m_name : ""), sc<int>(m->m_reservedTopLeft.x), sc<int>(m->m_reservedTopLeft.y),
sc<int>(m->m_reservedBottomRight.x), sc<int>(m->m_reservedBottomRight.y), m->m_scale, sc<int>(m->m_transform), (m == g_pCompositor->m_lastMonitor ? "true" : "false"),
(m->m_dpmsStatus ? "true" : "false"), (m->m_output->state->state().adaptiveSync ? "true" : "false"), rc<uint64_t>(m->m_solitaryClient.get()),
getSolitaryBlockedReason(m, format), (m->m_tearingState.activelyTearing ? "true" : "false"), getTearingBlockedReason(m, format), rc<uint64_t>(m->m_lastScanout.get()),
getDSBlockedReason(m, format), (m->m_enabled ? "false" : "true"), formatToString(m->m_output->state->state().drmFormat),
m->m_mirrorOf ? std::format("{}", m->m_mirrorOf->m_id) : "none", availableModesForOutput(m, format));
escapeJSONStrings(m->m_activeSpecialWorkspace ? m->m_activeSpecialWorkspace->m_name : ""), sc<int>(m->m_reservedArea.left()), sc<int>(m->m_reservedArea.top()),
sc<int>(m->m_reservedArea.right()), sc<int>(m->m_reservedArea.bottom()), m->m_scale, sc<int>(m->m_transform),
(m == Desktop::focusState()->monitor() ? "true" : "false"), (m->m_dpmsStatus ? "true" : "false"), (m->m_output->state->state().adaptiveSync ? "true" : "false"),
rc<uint64_t>(m->m_solitaryClient.get()), getSolitaryBlockedReason(m, format), (m->m_tearingState.activelyTearing ? "true" : "false"),
getTearingBlockedReason(m, format), rc<uint64_t>(m->m_lastScanout.get()), getDSBlockedReason(m, format), (m->m_enabled ? "false" : "true"),
formatToString(m->m_output->state->state().drmFormat), m->m_mirrorOf ? std::format("{}", m->m_mirrorOf->m_id) : "none", availableModesForOutput(m, format),
(NCMType::toString(m->m_cmType)), (m->m_sdrBrightness), (m->m_sdrSaturation), (m->m_sdrMinLuminance), (m->m_sdrMaxLuminance));
} else {
result += std::format(
@ -261,15 +270,16 @@ std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer<CMonitor>
"dpmsStatus: {}\n\tvrr: {}\n\tsolitary: {:x}\n\tsolitaryBlockedBy: {}\n\tactivelyTearing: {}\n\ttearingBlockedBy: {}\n\tdirectScanoutTo: "
"{:x}\n\tdirectScanoutBlockedBy: {}\n\tdisabled: "
"{}\n\tcurrentFormat: {}\n\tmirrorOf: "
"{}\n\tavailableModes: {}\n\n",
"{}\n\tavailableModes: {}\n\tcolorManagementPreset: {}\n\tsdrBrightness: {:.2f}\n\tsdrSaturation: {:.2f}\n\tsdrMinLuminance: {:.2f}\n\tsdrMaxLuminance: {}\n\n",
m->m_name, m->m_id, sc<int>(m->m_pixelSize.x), sc<int>(m->m_pixelSize.y), m->m_refreshRate, sc<int>(m->m_position.x), sc<int>(m->m_position.y), m->m_shortDescription,
m->m_output->make, m->m_output->model, sc<int>(m->m_output->physicalSize.x), sc<int>(m->m_output->physicalSize.y), m->m_output->serial, m->activeWorkspaceID(),
(!m->m_activeWorkspace ? "" : m->m_activeWorkspace->m_name), m->activeSpecialWorkspaceID(), (m->m_activeSpecialWorkspace ? m->m_activeSpecialWorkspace->m_name : ""),
sc<int>(m->m_reservedTopLeft.x), sc<int>(m->m_reservedTopLeft.y), sc<int>(m->m_reservedBottomRight.x), sc<int>(m->m_reservedBottomRight.y), m->m_scale,
sc<int>(m->m_transform), (m == g_pCompositor->m_lastMonitor ? "yes" : "no"), sc<int>(m->m_dpmsStatus), m->m_output->state->state().adaptiveSync,
sc<int>(m->m_reservedArea.left()), sc<int>(m->m_reservedArea.top()), sc<int>(m->m_reservedArea.right()), sc<int>(m->m_reservedArea.bottom()), m->m_scale,
sc<int>(m->m_transform), (m == Desktop::focusState()->monitor() ? "yes" : "no"), sc<int>(m->m_dpmsStatus), m->m_output->state->state().adaptiveSync,
rc<uint64_t>(m->m_solitaryClient.get()), getSolitaryBlockedReason(m, format), m->m_tearingState.activelyTearing, getTearingBlockedReason(m, format),
rc<uint64_t>(m->m_lastScanout.get()), getDSBlockedReason(m, format), !m->m_enabled, formatToString(m->m_output->state->state().drmFormat),
m->m_mirrorOf ? std::format("{}", m->m_mirrorOf->m_id) : "none", availableModesForOutput(m, format));
m->m_mirrorOf ? std::format("{}", m->m_mirrorOf->m_id) : "none", availableModesForOutput(m, format), (NCMType::toString(m->m_cmType)), (m->m_sdrBrightness),
(m->m_sdrSaturation), (m->m_sdrMinLuminance), (m->m_sdrMaxLuminance));
}
return result;
@ -309,7 +319,7 @@ static std::string monitorsRequest(eHyprCtlOutputFormat format, std::string requ
}
static std::string getTagsData(PHLWINDOW w, eHyprCtlOutputFormat format) {
const auto tags = w->m_tags.getTags();
const auto tags = w->m_ruleApplicator->m_tagKeeper.getTags();
if (format == eHyprCtlOutputFormat::FORMAT_JSON)
return std::ranges::fold_left(tags, std::string(),
@ -344,8 +354,8 @@ static std::string getGroupedData(PHLWINDOW w, eHyprCtlOutputFormat format) {
std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) {
auto getFocusHistoryID = [](PHLWINDOW wnd) -> int {
for (size_t i = 0; i < g_pCompositor->m_windowFocusHistory.size(); ++i) {
if (g_pCompositor->m_windowFocusHistory[i].lock() == wnd)
for (size_t i = 0; i < Desktop::focusState()->windowHistory().size(); ++i) {
if (Desktop::focusState()->windowHistory()[i].lock() == wnd)
return i;
}
return -1;
@ -381,7 +391,8 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) {
"focusHistoryID": {},
"inhibitingIdle": {},
"xdgTag": "{}",
"xdgDescription": "{}"
"xdgDescription": "{}",
"contentType": "{}"
}},)#",
rc<uintptr_t>(w.get()), (w->m_isMapped ? "true" : "false"), (w->isHidden() ? "true" : "false"), sc<int>(w->m_realPosition->goal().x),
sc<int>(w->m_realPosition->goal().y), sc<int>(w->m_realSize->goal().x), sc<int>(w->m_realSize->goal().y), w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID,
@ -389,20 +400,21 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) {
w->monitorID(), escapeJSONStrings(w->m_class), escapeJSONStrings(w->m_title), escapeJSONStrings(w->m_initialClass), escapeJSONStrings(w->m_initialTitle), w->getPID(),
(sc<int>(w->m_isX11) == 1 ? "true" : "false"), (w->m_pinned ? "true" : "false"), sc<uint8_t>(w->m_fullscreenState.internal), sc<uint8_t>(w->m_fullscreenState.client),
getGroupedData(w, format), getTagsData(w, format), rc<uintptr_t>(w->m_swallowed.get()), getFocusHistoryID(w),
(g_pInputManager->isWindowInhibiting(w, false) ? "true" : "false"), escapeJSONStrings(w->xdgTag().value_or("")), escapeJSONStrings(w->xdgDescription().value_or("")));
(g_pInputManager->isWindowInhibiting(w, false) ? "true" : "false"), escapeJSONStrings(w->xdgTag().value_or("")), escapeJSONStrings(w->xdgDescription().value_or("")),
escapeJSONStrings(NContentType::toString(w->getContentType())));
} else {
return std::format(
"Window {:x} -> {}:\n\tmapped: {}\n\thidden: {}\n\tat: {},{}\n\tsize: {},{}\n\tworkspace: {} ({})\n\tfloating: {}\n\tpseudo: {}\n\tmonitor: {}\n\tclass: {}\n\ttitle: "
"{}\n\tinitialClass: {}\n\tinitialTitle: {}\n\tpid: "
"{}\n\txwayland: {}\n\tpinned: "
"{}\n\tfullscreen: {}\n\tfullscreenClient: {}\n\tgrouped: {}\n\ttags: {}\n\tswallowing: {:x}\n\tfocusHistoryID: {}\n\tinhibitingIdle: {}\n\txdgTag: "
"{}\n\txdgDescription: {}\n\n",
"{}\n\txdgDescription: {}\n\tcontentType: {}\n\n",
rc<uintptr_t>(w.get()), w->m_title, sc<int>(w->m_isMapped), sc<int>(w->isHidden()), sc<int>(w->m_realPosition->goal().x), sc<int>(w->m_realPosition->goal().y),
sc<int>(w->m_realSize->goal().x), sc<int>(w->m_realSize->goal().y), w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID,
(!w->m_workspace ? "" : w->m_workspace->m_name), sc<int>(w->m_isFloating), sc<int>(w->m_isPseudotiled), w->monitorID(), w->m_class, w->m_title, w->m_initialClass,
w->m_initialTitle, w->getPID(), sc<int>(w->m_isX11), sc<int>(w->m_pinned), sc<uint8_t>(w->m_fullscreenState.internal), sc<uint8_t>(w->m_fullscreenState.client),
getGroupedData(w, format), getTagsData(w, format), rc<uintptr_t>(w->m_swallowed.get()), getFocusHistoryID(w), sc<int>(g_pInputManager->isWindowInhibiting(w, false)),
w->xdgTag().value_or(""), w->xdgDescription().value_or(""));
w->xdgTag().value_or(""), w->xdgDescription().value_or(""), NContentType::toString(w->getContentType()));
}
}
@ -513,11 +525,11 @@ static std::string getWorkspaceRuleData(const SWorkspaceRule& r, eHyprCtlOutputF
}
static std::string activeWorkspaceRequest(eHyprCtlOutputFormat format, std::string request) {
if (!g_pCompositor->m_lastMonitor)
if (!Desktop::focusState()->monitor())
return "unsafe state";
std::string result = "";
auto w = g_pCompositor->m_lastMonitor->m_activeWorkspace;
auto w = Desktop::focusState()->monitor()->m_activeWorkspace;
if (!valid(w))
return "internal error";
@ -567,7 +579,7 @@ static std::string workspaceRulesRequest(eHyprCtlOutputFormat format, std::strin
}
static std::string activeWindowRequest(eHyprCtlOutputFormat format, std::string request) {
const auto PWINDOW = g_pCompositor->m_lastWindow.lock();
const auto PWINDOW = Desktop::focusState()->window();
if (!validMapped(PWINDOW))
return format == eHyprCtlOutputFormat::FORMAT_JSON ? "{}" : "Invalid";
@ -945,11 +957,10 @@ static std::string rollinglogRequest(eHyprCtlOutputFormat format, std::string re
if (format == eHyprCtlOutputFormat::FORMAT_JSON) {
result += "[\n\"log\":\"";
result += escapeJSONStrings(Debug::m_rollingLog);
result += escapeJSONStrings(Log::logger->rolling());
result += "\"]";
} else {
result = Debug::m_rollingLog;
}
} else
result = Log::logger->rolling();
return result;
}
@ -999,7 +1010,7 @@ static std::string bindsRequest(eHyprCtlOutputFormat format, std::string request
ret += "d";
ret += std::format("\n\tmodmask: {}\n\tsubmap: {}\n\tkey: {}\n\tkeycode: {}\n\tcatchall: {}\n\tdescription: {}\n\tdispatcher: {}\n\targ: {}\n\n", kb->modmask,
kb->submap, kb->key, kb->keycode, kb->catchAll, kb->description, kb->handler, kb->arg);
kb->submap.name, kb->key, kb->keycode, kb->catchAll, kb->description, kb->handler, kb->arg);
}
} else {
// json
@ -1017,6 +1028,7 @@ static std::string bindsRequest(eHyprCtlOutputFormat format, std::string request
"has_description": {},
"modmask": {},
"submap": "{}",
"submap_universal": "{}",
"key": "{}",
"keycode": {},
"catch_all": {},
@ -1025,8 +1037,9 @@ static std::string bindsRequest(eHyprCtlOutputFormat format, std::string request
"arg": "{}"
}},)#",
kb->locked ? "true" : "false", kb->mouse ? "true" : "false", kb->release ? "true" : "false", kb->repeat ? "true" : "false", kb->longPress ? "true" : "false",
kb->nonConsuming ? "true" : "false", kb->hasDescription ? "true" : "false", kb->modmask, escapeJSONStrings(kb->submap), escapeJSONStrings(kb->key), kb->keycode,
kb->catchAll ? "true" : "false", escapeJSONStrings(kb->description), escapeJSONStrings(kb->handler), escapeJSONStrings(kb->arg));
kb->nonConsuming ? "true" : "false", kb->hasDescription ? "true" : "false", kb->modmask, escapeJSONStrings(kb->submap.name), kb->submapUniversal,
escapeJSONStrings(kb->key), kb->keycode, kb->catchAll ? "true" : "false", escapeJSONStrings(kb->description), escapeJSONStrings(kb->handler),
escapeJSONStrings(kb->arg));
}
trimTrailingComma(ret);
ret += "]";
@ -1043,12 +1056,17 @@ std::string versionRequest(eHyprCtlOutputFormat format, std::string request) {
if (format == eHyprCtlOutputFormat::FORMAT_NORMAL) {
std::string result = std::format("Hyprland {} built from branch {} at commit {} {} ({}).\n"
"Date: {}\n"
"Tag: {}, commits: {}\n"
"built against:\n aquamarine {}\n hyprlang {}\n hyprutils {}\n hyprcursor {}\n hyprgraphics {}\n\n\n",
HYPRLAND_VERSION, GIT_BRANCH, GIT_COMMIT_HASH, GIT_DIRTY, commitMsg, GIT_COMMIT_DATE, GIT_TAG, GIT_COMMITS, AQUAMARINE_VERSION,
HYPRLANG_VERSION, HYPRUTILS_VERSION, HYPRCURSOR_VERSION, HYPRGRAPHICS_VERSION);
"Tag: {}, commits: {}\n",
HYPRLAND_VERSION, GIT_BRANCH, GIT_COMMIT_HASH, GIT_DIRTY, commitMsg, GIT_COMMIT_DATE, GIT_TAG, GIT_COMMITS);
#if (!ISDEBUG && !defined(NO_XWAYLAND))
result += "\n";
result += getBuiltSystemLibraryNames();
result += "\n";
result += "Version ABI string: ";
result += __hyprland_api_get_hash();
result += "\n";
#if (!ISDEBUG && !defined(NO_XWAYLAND) && !defined(BUILT_WITH_NIX))
result += "no flags were set\n";
#else
result += "flags set:\n";
@ -1058,6 +1076,9 @@ std::string versionRequest(eHyprCtlOutputFormat format, std::string request) {
#ifdef NO_XWAYLAND
result += "no xwayland\n";
#endif
#ifdef BUILT_WITH_NIX
result += "nix\n";
#endif
#endif
return result;
} else {
@ -1076,9 +1097,17 @@ std::string versionRequest(eHyprCtlOutputFormat format, std::string request) {
"buildHyprutils": "{}",
"buildHyprcursor": "{}",
"buildHyprgraphics": "{}",
"systemAquamarine": "{}",
"systemHyprlang": "{}",
"systemHyprutils": "{}",
"systemHyprcursor": "{}",
"systemHyprgraphics": "{}",
"abiHash": "{}",
"flags": [)#",
GIT_BRANCH, GIT_COMMIT_HASH, HYPRLAND_VERSION, (strcmp(GIT_DIRTY, "dirty") == 0 ? "true" : "false"), escapeJSONStrings(commitMsg), GIT_COMMIT_DATE, GIT_TAG,
GIT_COMMITS, AQUAMARINE_VERSION, HYPRLANG_VERSION, HYPRUTILS_VERSION, HYPRCURSOR_VERSION, HYPRGRAPHICS_VERSION);
GIT_COMMITS, AQUAMARINE_VERSION, HYPRLANG_VERSION, HYPRUTILS_VERSION, HYPRCURSOR_VERSION, HYPRGRAPHICS_VERSION, getSystemLibraryVersion("aquamarine"),
getSystemLibraryVersion("hyprlang"), getSystemLibraryVersion("hyprutils"), getSystemLibraryVersion("hyprcursor"), getSystemLibraryVersion("hyprgraphics"),
__hyprland_api_get_hash());
#if ISDEBUG
result += "\"debug\",";
@ -1086,6 +1115,9 @@ std::string versionRequest(eHyprCtlOutputFormat format, std::string request) {
#ifdef NO_XWAYLAND
result += "\"no xwayland\",";
#endif
#ifdef BUILT_WITH_NIX
result += "\"nix\",";
#endif
trimTrailingComma(result);
@ -1121,6 +1153,9 @@ std::string systemInfoRequest(eHyprCtlOutputFormat format, std::string request)
result += "Node name: " + std::string{unameInfo.nodename} + "\n";
result += "Release: " + std::string{unameInfo.release} + "\n";
result += "Version: " + std::string{unameInfo.version} + "\n";
result += "\n";
result += getBuiltSystemLibraryNames();
result += "\n";
result += "\n\n";
@ -1179,19 +1214,24 @@ std::string systemInfoRequest(eHyprCtlOutputFormat format, std::string request)
} else
result += "\tunknown: not runtime\n";
result += std::format("\nExplicit sync: {}", g_pHyprOpenGL->m_exts.EGL_ANDROID_native_fence_sync_ext ? "supported" : "missing");
result += std::format("\nGL ver: {}", g_pHyprOpenGL->m_eglContextVersion == CHyprOpenGLImpl::EGL_CONTEXT_GLES_3_2 ? "3.2" : "3.0");
result += std::format("\nBackend: {}", g_pCompositor->m_aqBackend->hasSession() ? "drm" : "sessionless");
if (g_pHyprOpenGL) {
result += std::format("\nExplicit sync: {}", g_pHyprOpenGL->m_exts.EGL_ANDROID_native_fence_sync_ext ? "supported" : "missing");
result += std::format("\nGL ver: {}", g_pHyprOpenGL->m_eglContextVersion == CHyprOpenGLImpl::EGL_CONTEXT_GLES_3_2 ? "3.2" : "3.0");
}
result += "\n\nMonitor info:";
if (g_pCompositor) {
result += std::format("\nBackend: {}", g_pCompositor->m_aqBackend->hasSession() ? "drm" : "sessionless");
for (const auto& m : g_pCompositor->m_monitors) {
result += std::format("\n\tPanel {}: {}x{}, {} {} {} {} -> backend {}\n\t\texplicit {}\n\t\tedid:\n\t\t\thdr {}\n\t\t\tchroma {}\n\t\t\tbt2020 {}\n\t\tvrr capable "
"{}\n\t\tnon-desktop {}\n\t\t",
m->m_name, sc<int>(m->m_pixelSize.x), sc<int>(m->m_pixelSize.y), m->m_output->name, m->m_output->make, m->m_output->model, m->m_output->serial,
backend(m->m_output->getBackend()->type()), check(m->m_output->supportsExplicit), check(m->m_output->parsedEDID.hdrMetadata.has_value()),
check(m->m_output->parsedEDID.chromaticityCoords.has_value()), check(m->m_output->parsedEDID.supportsBT2020), check(m->m_output->vrrCapable),
check(m->m_output->nonDesktop));
result += "\n\nMonitor info:";
for (const auto& m : g_pCompositor->m_monitors) {
result += std::format("\n\tPanel {}: {}x{}, {} {} {} {} -> backend {}\n\t\texplicit {}\n\t\tedid:\n\t\t\thdr {}\n\t\t\tchroma {}\n\t\t\tbt2020 {}\n\t\tvrr capable "
"{}\n\t\tnon-desktop {}\n\t\t",
m->m_name, sc<int>(m->m_pixelSize.x), sc<int>(m->m_pixelSize.y), m->m_output->name, m->m_output->make, m->m_output->model, m->m_output->serial,
backend(m->m_output->getBackend()->type()), check(m->m_output->supportsExplicit), check(m->m_output->parsedEDID.hdrMetadata.has_value()),
check(m->m_output->parsedEDID.chromaticityCoords.has_value()), check(m->m_output->parsedEDID.supportsBT2020), check(m->m_output->vrrCapable),
check(m->m_output->nonDesktop));
}
}
if (g_pHyprCtl && g_pHyprCtl->m_currentRequestParams.sysInfoConfig) {
@ -1219,7 +1259,7 @@ static std::string dispatchRequest(eHyprCtlOutputFormat format, std::string in)
SDispatchResult res = DISPATCHER->second(DISPATCHARG);
Debug::log(LOG, "Hyprctl: dispatcher {} : {}{}", DISPATCHSTR, DISPATCHARG, res.success ? "" : " -> " + res.error);
Log::logger->log(Log::DEBUG, "Hyprctl: dispatcher {} : {}{}", DISPATCHSTR, DISPATCHARG, res.success ? "" : " -> " + res.error);
return res.success ? "ok" : res.error;
}
@ -1246,8 +1286,12 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in)
if (COMMAND.empty())
return "Invalid input: command is empty";
g_pHyprCtl->m_currentRequestParams.isDynamicKeyword = true;
std::string retval = g_pConfigManager->parseKeyword(COMMAND, VALUE);
g_pHyprCtl->m_currentRequestParams.isDynamicKeyword = false;
// if we are executing a dynamic source we have to reload everything, so every if will have a check for source.
if (COMMAND == "monitor" || COMMAND == "source")
g_pConfigManager->m_wantsMonitorReload = true; // for monitor keywords
@ -1280,8 +1324,7 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in)
g_pConfigManager->updateWatcher();
// decorations will probably need a repaint
if (COMMAND.contains("decoration:") || COMMAND.contains("border") || COMMAND == "workspace" || COMMAND.contains("zoom_factor") || COMMAND == "source" ||
COMMAND.starts_with("windowrule")) {
if (COMMAND.contains("decoration:") || COMMAND.contains("border") || COMMAND == "workspace" || COMMAND.contains("zoom_factor") || COMMAND == "source") {
static auto PZOOMFACTOR = CConfigValue<Hyprlang::FLOAT>("cursor:zoom_factor");
for (auto const& m : g_pCompositor->m_monitors) {
*(m->m_cursorZoom) = *PZOOMFACTOR;
@ -1290,10 +1333,13 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in)
}
}
if (COMMAND.contains("windowrule ") || COMMAND.contains("windowrule["))
g_pConfigManager->reloadRules();
if (COMMAND.contains("workspace"))
g_pConfigManager->ensurePersistentWorkspacesPresent();
Debug::log(LOG, "Hyprctl: keyword {} : {}", COMMAND, VALUE);
Log::logger->log(Log::DEBUG, "Hyprctl: keyword {} : {}", COMMAND, VALUE);
if (retval.empty())
return "ok";
@ -1495,11 +1541,6 @@ static std::string dispatchSeterror(eHyprCtlOutputFormat format, std::string req
return "ok";
}
static std::string dispatchSetProp(eHyprCtlOutputFormat format, std::string request) {
auto result = g_pKeybindManager->m_dispatchers["setprop"](request.substr(request.find_first_of(' ') + 1));
return "DEPRECATED: use hyprctl dispatch setprop instead" + (result.success ? "" : "\n" + result.error);
}
static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string request) {
CVarList vars(request, 0, ' ');
@ -1517,9 +1558,9 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ
const bool FORMNORM = format == FORMAT_NORMAL;
auto sizeToString = [&](bool max) -> std::string {
auto sizeValue = PWINDOW->m_windowData.minSize.valueOr(Vector2D(MIN_WINDOW_SIZE, MIN_WINDOW_SIZE));
auto sizeValue = PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D(MIN_WINDOW_SIZE, MIN_WINDOW_SIZE));
if (max)
sizeValue = PWINDOW->m_windowData.maxSize.valueOr(Vector2D(INFINITY, INFINITY));
sizeValue = PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D(INFINITY, INFINITY));
if (FORMNORM)
return std::format("{} {}", sizeValue.x, sizeValue.y);
@ -1530,7 +1571,7 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ
}
};
auto alphaToString = [&](CWindowOverridableVar<SAlphaValue>& alpha, bool getAlpha) -> std::string {
auto alphaToString = [&](Desktop::Types::COverridableVar<Desktop::Types::SAlphaValue>& alpha, bool getAlpha) -> std::string {
if (FORMNORM) {
if (getAlpha)
return std::format("{}", alpha.valueOrDefault().alpha);
@ -1564,7 +1605,7 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ
const auto* const ACTIVECOLOR =
!PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL);
std::string borderColorString = PWINDOW->m_windowData.activeBorderColor.valueOr(*ACTIVECOLOR).toString();
std::string borderColorString = PWINDOW->m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR).toString();
if (FORMNORM)
return borderColorString;
else
@ -1577,7 +1618,7 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ
const auto* const INACTIVECOLOR = !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) :
(GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL);
std::string borderColorString = PWINDOW->m_windowData.inactiveBorderColor.valueOr(*INACTIVECOLOR).toString();
std::string borderColorString = PWINDOW->m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR).toString();
if (FORMNORM)
return borderColorString;
else
@ -1592,38 +1633,92 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ
return std::format(R"({{"{}": {}}})", PROP, prop.valueOrDefault());
};
if (PROP == "animationstyle") {
auto& animationStyle = PWINDOW->m_windowData.animationStyle;
if (PROP == "animation") {
auto& animationStyle = PWINDOW->m_ruleApplicator->animationStyle();
if (FORMNORM)
return animationStyle.valueOr("(unset)");
else
return std::format(R"({{"{}": "{}"}})", PROP, animationStyle.valueOr(""));
} else if (PROP == "maxsize")
} else if (PROP == "max_size")
return sizeToString(true);
else if (PROP == "minsize")
else if (PROP == "min_size")
return sizeToString(false);
else if (PROP == "alpha")
return alphaToString(PWINDOW->m_windowData.alpha, true);
else if (PROP == "alphainactive")
return alphaToString(PWINDOW->m_windowData.alphaInactive, true);
else if (PROP == "alphafullscreen")
return alphaToString(PWINDOW->m_windowData.alphaFullscreen, true);
else if (PROP == "alphaoverride")
return alphaToString(PWINDOW->m_windowData.alpha, false);
else if (PROP == "alphainactiveoverride")
return alphaToString(PWINDOW->m_windowData.alphaInactive, false);
else if (PROP == "alphafullscreenoverride")
return alphaToString(PWINDOW->m_windowData.alphaFullscreen, false);
else if (PROP == "activebordercolor")
else if (PROP == "opacity")
return alphaToString(PWINDOW->m_ruleApplicator->alpha(), true);
else if (PROP == "opacity_inactive")
return alphaToString(PWINDOW->m_ruleApplicator->alphaInactive(), true);
else if (PROP == "opacity_fullscreen")
return alphaToString(PWINDOW->m_ruleApplicator->alphaFullscreen(), true);
else if (PROP == "opacity_override")
return alphaToString(PWINDOW->m_ruleApplicator->alpha(), false);
else if (PROP == "opacity_inactive_override")
return alphaToString(PWINDOW->m_ruleApplicator->alphaInactive(), false);
else if (PROP == "opacity_fullscreen_override")
return alphaToString(PWINDOW->m_ruleApplicator->alphaFullscreen(), false);
else if (PROP == "active_border_color")
return borderColorToString(true);
else if (PROP == "inactivebordercolor")
else if (PROP == "inactive_border_color")
return borderColorToString(false);
else if (auto search = NWindowProperties::boolWindowProperties.find(PROP); search != NWindowProperties::boolWindowProperties.end())
return windowPropToString(*search->second(PWINDOW));
else if (auto search = NWindowProperties::intWindowProperties.find(PROP); search != NWindowProperties::intWindowProperties.end())
return windowPropToString(*search->second(PWINDOW));
else if (auto search = NWindowProperties::floatWindowProperties.find(PROP); search != NWindowProperties::floatWindowProperties.end())
return windowPropToString(*search->second(PWINDOW));
else if (PROP == "allows_input")
return windowPropToString(PWINDOW->m_ruleApplicator->allowsInput());
else if (PROP == "decorate")
return windowPropToString(PWINDOW->m_ruleApplicator->decorate());
else if (PROP == "focus_on_activate")
return windowPropToString(PWINDOW->m_ruleApplicator->focusOnActivate());
else if (PROP == "keep_aspect_ratio")
return windowPropToString(PWINDOW->m_ruleApplicator->keepAspectRatio());
else if (PROP == "nearest_neighbor")
return windowPropToString(PWINDOW->m_ruleApplicator->nearestNeighbor());
else if (PROP == "no_anim")
return windowPropToString(PWINDOW->m_ruleApplicator->noAnim());
else if (PROP == "no_blur")
return windowPropToString(PWINDOW->m_ruleApplicator->noBlur());
else if (PROP == "no_dim")
return windowPropToString(PWINDOW->m_ruleApplicator->noDim());
else if (PROP == "no_focus")
return windowPropToString(PWINDOW->m_ruleApplicator->noFocus());
else if (PROP == "no_max_size")
return windowPropToString(PWINDOW->m_ruleApplicator->noMaxSize());
else if (PROP == "no_shadow")
return windowPropToString(PWINDOW->m_ruleApplicator->noShadow());
else if (PROP == "no_shortcuts_inhibit")
return windowPropToString(PWINDOW->m_ruleApplicator->noShortcutsInhibit());
else if (PROP == "opaque")
return windowPropToString(PWINDOW->m_ruleApplicator->opaque());
else if (PROP == "dim_around")
return windowPropToString(PWINDOW->m_ruleApplicator->dimAround());
else if (PROP == "force_rgbx")
return windowPropToString(PWINDOW->m_ruleApplicator->RGBX());
else if (PROP == "sync_fullscreen")
return windowPropToString(PWINDOW->m_ruleApplicator->syncFullscreen());
else if (PROP == "immediate")
return windowPropToString(PWINDOW->m_ruleApplicator->tearing());
else if (PROP == "xray")
return windowPropToString(PWINDOW->m_ruleApplicator->xray());
else if (PROP == "render_unfocused")
return windowPropToString(PWINDOW->m_ruleApplicator->renderUnfocused());
else if (PROP == "no_follow_mouse")
return windowPropToString(PWINDOW->m_ruleApplicator->noFollowMouse());
else if (PROP == "no_screen_share")
return windowPropToString(PWINDOW->m_ruleApplicator->noScreenShare());
else if (PROP == "no_vrr")
return windowPropToString(PWINDOW->m_ruleApplicator->noVRR());
else if (PROP == "persistent_size")
return windowPropToString(PWINDOW->m_ruleApplicator->persistentSize());
else if (PROP == "stay_focused")
return windowPropToString(PWINDOW->m_ruleApplicator->stayFocused());
else if (PROP == "idle_inhibit")
return windowPropToString(PWINDOW->m_ruleApplicator->idleInhibitMode());
else if (PROP == "border_size")
return windowPropToString(PWINDOW->m_ruleApplicator->borderSize());
else if (PROP == "rounding")
return windowPropToString(PWINDOW->m_ruleApplicator->rounding());
else if (PROP == "rounding_power")
return windowPropToString(PWINDOW->m_ruleApplicator->roundingPower());
else if (PROP == "scroll_mouse")
return windowPropToString(PWINDOW->m_ruleApplicator->scrollMouse());
else if (PROP == "scroll_touchpad")
return windowPropToString(PWINDOW->m_ruleApplicator->scrollTouchpad());
return "prop not found";
}
@ -1945,7 +2040,7 @@ static std::string getDescriptions(eHyprCtlOutputFormat format, std::string requ
}
static std::string submapRequest(eHyprCtlOutputFormat format, std::string request) {
std::string submap = g_pKeybindManager->getCurrentSubmap();
std::string submap = g_pKeybindManager->getCurrentSubmap().name;
if (submap.empty())
submap = "default";
@ -1988,7 +2083,6 @@ CHyprCtl::CHyprCtl() {
registerCommand(SHyprCtlCommand{"plugin", false, dispatchPlugin});
registerCommand(SHyprCtlCommand{"notify", false, dispatchNotify});
registerCommand(SHyprCtlCommand{"dismissnotify", false, dispatchDismissNotify});
registerCommand(SHyprCtlCommand{"setprop", false, dispatchSetProp});
registerCommand(SHyprCtlCommand{"getprop", false, dispatchGetProp});
registerCommand(SHyprCtlCommand{"seterror", false, dispatchSeterror});
registerCommand(SHyprCtlCommand{"switchxkblayout", false, switchXKBLayoutRequest});
@ -2104,8 +2198,7 @@ std::string CHyprCtl::getReply(std::string request) {
if (!w->m_isMapped || !w->m_workspace || !w->m_workspace->isVisible())
continue;
w->updateDynamicRules();
g_pCompositor->updateWindowAnimatedDecorationValues(w);
Desktop::Rule::ruleEngine()->updateAllRules();
}
for (auto const& m : g_pCompositor->m_monitors) {
@ -2129,23 +2222,23 @@ static bool successWrite(int fd, const std::string& data, bool needLog = true) {
return true;
if (needLog)
Debug::log(ERR, "Couldn't write to socket. Error: " + std::string(strerror(errno)));
Log::logger->log(Log::ERR, "Couldn't write to socket. Error: " + std::string(strerror(errno)));
return false;
}
static void runWritingDebugLogThread(const int conn) {
using namespace std::chrono_literals;
Debug::log(LOG, "In followlog thread, got connection, start writing: {}", conn);
Log::logger->log(Log::DEBUG, "In followlog thread, got connection, start writing: {}", conn);
//will be finished, when reading side close connection
std::thread([conn]() {
while (Debug::SRollingLogFollow::get().isRunning()) {
if (Debug::SRollingLogFollow::get().isEmpty(conn)) {
while (Log::SRollingLogFollow::get().isRunning()) {
if (Log::SRollingLogFollow::get().isEmpty(conn)) {
std::this_thread::sleep_for(1000ms);
continue;
}
auto line = Debug::SRollingLogFollow::get().getLog(conn);
auto line = Log::SRollingLogFollow::get().getLog(conn);
if (!successWrite(conn, line))
// We cannot write, when connection is closed. So thread will successfully exit by itself
break;
@ -2153,7 +2246,7 @@ static void runWritingDebugLogThread(const int conn) {
std::this_thread::sleep_for(100ms);
}
close(conn);
Debug::SRollingLogFollow::get().stopFor(conn);
Log::SRollingLogFollow::get().stopFor(conn);
}).detach();
}
@ -2179,10 +2272,10 @@ static int hyprCtlFDTick(int fd, uint32_t mask, void* data) {
CRED_T creds;
uint32_t len = sizeof(creds);
if (getsockopt(ACCEPTEDCONNECTION, CRED_LVL, CRED_OPT, &creds, &len) == -1)
Debug::log(ERR, "Hyprctl: failed to get peer creds");
Log::logger->log(Log::ERR, "Hyprctl: failed to get peer creds");
else {
g_pHyprCtl->m_currentRequestParams.pid = creds.CRED_PID;
Debug::log(LOG, "Hyprctl: new connection from pid {}", creds.CRED_PID);
Log::logger->log(Log::DEBUG, "Hyprctl: new connection from pid {}", creds.CRED_PID);
}
//
@ -2217,7 +2310,7 @@ static int hyprCtlFDTick(int fd, uint32_t mask, void* data) {
try {
reply = g_pHyprCtl->getReply(request);
} catch (std::exception& e) {
Debug::log(ERR, "Error in request: {}", e.what());
Log::logger->log(Log::ERR, "Error in request: {}", e.what());
reply = "Err: " + std::string(e.what());
}
@ -2237,10 +2330,10 @@ static int hyprCtlFDTick(int fd, uint32_t mask, void* data) {
successWrite(ACCEPTEDCONNECTION, reply);
if (isFollowUpRollingLogRequest(request)) {
Debug::log(LOG, "Followup rollinglog request received. Starting thread to write to socket.");
Debug::SRollingLogFollow::get().startFor(ACCEPTEDCONNECTION);
Log::logger->log(Log::DEBUG, "Followup rollinglog request received. Starting thread to write to socket.");
Log::SRollingLogFollow::get().startFor(ACCEPTEDCONNECTION);
runWritingDebugLogThread(ACCEPTEDCONNECTION);
Debug::log(LOG, Debug::SRollingLogFollow::get().debugInfo());
Log::logger->log(Log::DEBUG, Log::SRollingLogFollow::get().debugInfo());
} else
close(ACCEPTEDCONNECTION);
@ -2257,7 +2350,7 @@ void CHyprCtl::startHyprCtlSocket() {
m_socketFD = CFileDescriptor{socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)};
if (!m_socketFD.isValid()) {
Debug::log(ERR, "Couldn't start the Hyprland Socket. (1) IPC will not work.");
Log::logger->log(Log::ERR, "Couldn't start the Hyprland Socket. (1) IPC will not work.");
return;
}
@ -2268,14 +2361,14 @@ void CHyprCtl::startHyprCtlSocket() {
snprintf(SERVERADDRESS.sun_path, sizeof(SERVERADDRESS.sun_path), "%s", m_socketPath.c_str());
if (bind(m_socketFD.get(), rc<sockaddr*>(&SERVERADDRESS), SUN_LEN(&SERVERADDRESS)) < 0) {
Debug::log(ERR, "Couldn't start the Hyprland Socket. (2) IPC will not work.");
Log::logger->log(Log::ERR, "Couldn't start the Hyprland Socket. (2) IPC will not work.");
return;
}
// 10 max queued.
listen(m_socketFD.get(), 10);
Debug::log(LOG, "Hypr socket started at {}", m_socketPath);
Log::logger->log(Log::DEBUG, "Hypr socket started at {}", m_socketPath);
m_eventSource = wl_event_loop_add_fd(g_pCompositor->m_wlEventLoop, m_socketFD.get(), WL_EVENT_READABLE, hyprCtlFDTick, nullptr);
}

View file

@ -3,7 +3,7 @@
#include <fstream>
#include "../helpers/MiscFunctions.hpp"
#include "../helpers/defer/Promise.hpp"
#include "../desktop/Window.hpp"
#include "../desktop/view/Window.hpp"
#include <functional>
#include <sys/types.h>
#include <hyprutils/os/FileDescriptor.hpp>
@ -25,9 +25,10 @@ class CHyprCtl {
Hyprutils::OS::CFileDescriptor m_socketFD;
struct {
bool all = false;
bool sysInfoConfig = false;
pid_t pid = 0;
bool all = false;
bool sysInfoConfig = false;
bool isDynamicKeyword = false;
pid_t pid = 0;
SP<CPromise<std::string>> pendingPromise;
} m_currentRequestParams;

View file

@ -5,6 +5,7 @@
#include "../render/pass/TexPassElement.hpp"
#include "../render/Renderer.hpp"
#include "../managers/animation/AnimationManager.hpp"
#include "../desktop/state/FocusState.hpp"
CHyprDebugOverlay::CHyprDebugOverlay() {
m_texture = makeShared<CTexture>();
@ -57,7 +58,7 @@ void CHyprMonitorDebugOverlay::frameData(PHLMONITOR pMonitor) {
m_monitor = pMonitor;
// anim data too
const auto PMONITORFORTICKS = g_pHyprRenderer->m_mostHzMonitor ? g_pHyprRenderer->m_mostHzMonitor.lock() : g_pCompositor->m_lastMonitor.lock();
const auto PMONITORFORTICKS = g_pHyprRenderer->m_mostHzMonitor ? g_pHyprRenderer->m_mostHzMonitor.lock() : Desktop::focusState()->monitor();
if (PMONITORFORTICKS == pMonitor) {
if (m_lastAnimationTicks.size() > sc<long unsigned int>(PMONITORFORTICKS->m_refreshRate))
m_lastAnimationTicks.pop_front();

View file

@ -1,74 +0,0 @@
#include "Log.hpp"
#include "../defines.hpp"
#include "RollingLogFollow.hpp"
#include <fstream>
#include <print>
#include <fcntl.h>
void Debug::init(const std::string& IS) {
m_logFile = IS + (ISDEBUG ? "/hyprlandd.log" : "/hyprland.log");
m_logOfs.open(m_logFile, std::ios::out | std::ios::app);
auto handle = m_logOfs.native_handle();
fcntl(handle, F_SETFD, FD_CLOEXEC);
}
void Debug::close() {
m_logOfs.close();
}
void Debug::log(eLogLevel level, std::string str) {
if (level == TRACE && !m_trace)
return;
if (m_shuttingDown)
return;
std::string coloredStr = str;
//NOLINTBEGIN
switch (level) {
case LOG:
str = "[LOG] " + str;
coloredStr = str;
break;
case WARN:
str = "[WARN] " + str;
coloredStr = "\033[1;33m" + str + "\033[0m"; // yellow
break;
case ERR:
str = "[ERR] " + str;
coloredStr = "\033[1;31m" + str + "\033[0m"; // red
break;
case CRIT:
str = "[CRITICAL] " + str;
coloredStr = "\033[1;35m" + str + "\033[0m"; // magenta
break;
case INFO:
str = "[INFO] " + str;
coloredStr = "\033[1;32m" + str + "\033[0m"; // green
break;
case TRACE:
str = "[TRACE] " + str;
coloredStr = "\033[1;34m" + str + "\033[0m"; // blue
break;
default: break;
}
//NOLINTEND
m_rollingLog += str + "\n";
if (m_rollingLog.size() > ROLLING_LOG_SIZE)
m_rollingLog = m_rollingLog.substr(m_rollingLog.size() - ROLLING_LOG_SIZE);
if (SRollingLogFollow::get().isRunning())
SRollingLogFollow::get().addLog(str);
if (!m_disableLogs || !**m_disableLogs) {
// log to a file
m_logOfs << str << "\n";
m_logOfs.flush();
}
// log it to the stdout too.
if (!m_disableStdout)
std::println("{}", ((m_coloredLogs && !**m_coloredLogs) ? str : coloredStr));
}

View file

@ -1,77 +0,0 @@
#pragma once
#include <string>
#include <format>
#include <iostream>
#include <fstream>
#include <chrono>
#include <mutex>
#define LOGMESSAGESIZE 1024
#define ROLLING_LOG_SIZE 4096
enum eLogLevel : int8_t {
NONE = -1,
LOG = 0,
WARN,
ERR,
CRIT,
INFO,
TRACE
};
// NOLINTNEXTLINE(readability-identifier-naming)
namespace Debug {
inline std::string m_logFile;
inline std::ofstream m_logOfs;
inline int64_t* const* m_disableLogs = nullptr;
inline int64_t* const* m_disableTime = nullptr;
inline bool m_disableStdout = false;
inline bool m_trace = false;
inline bool m_shuttingDown = false;
inline int64_t* const* m_coloredLogs = nullptr;
inline std::string m_rollingLog = ""; // rolling log contains the ROLLING_LOG_SIZE tail of the log
inline std::mutex m_logMutex;
void init(const std::string& IS);
void close();
//
void log(eLogLevel level, std::string str);
template <typename... Args>
//NOLINTNEXTLINE
void log(eLogLevel level, std::format_string<Args...> fmt, Args&&... args) {
std::lock_guard<std::mutex> guard(m_logMutex);
if (level == TRACE && !m_trace)
return;
if (m_shuttingDown)
return;
std::string logMsg = "";
// print date and time to the ofs
if (m_disableTime && !**m_disableTime) {
#ifndef _LIBCPP_VERSION
static auto current_zone = std::chrono::current_zone();
const auto zt = std::chrono::zoned_time{current_zone, std::chrono::system_clock::now()};
const auto hms = std::chrono::hh_mm_ss{zt.get_local_time() - std::chrono::floor<std::chrono::days>(zt.get_local_time())};
#else
// TODO: current clang 17 does not support `zoned_time`, remove this once clang 19 is ready
const auto hms = std::chrono::hh_mm_ss{std::chrono::system_clock::now() - std::chrono::floor<std::chrono::days>(std::chrono::system_clock::now())};
#endif
logMsg += std::format("[{}] ", hms);
}
// no need for try {} catch {} because std::format_string<Args...> ensures that vformat never throw std::format_error
// because
// 1. any faulty format specifier that sucks will cause a compilation error.
// 2. and `std::bad_alloc` is catastrophic, (Almost any operation in stdlib could throw this.)
// 3. this is actually what std::format in stdlib does
logMsg += std::vformat(fmt.get(), std::make_format_args(args...));
log(level, logMsg);
}
};

View file

@ -2,7 +2,7 @@
#ifdef USE_TRACY_GPU
#include "Log.hpp"
#include "log/Logger.hpp"
#include <GL/gl.h>
#include <GLES2/gl2ext.h>

View file

@ -6,36 +6,43 @@
#include <cerrno>
#include <sys/stat.h>
#include <filesystem>
#include "../helpers/MiscFunctions.hpp"
#include "../../helpers/MiscFunctions.hpp"
#include "../plugins/PluginSystem.hpp"
#include "../signal-safe.hpp"
#include "../../plugins/PluginSystem.hpp"
#include "SignalSafe.hpp"
#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__)
#include <sys/sysctl.h>
#endif
static char const* const MESSAGES[] = {"Sorry, didn't mean to...",
"This was an accident, I swear!",
"Calm down, it was a misinput! MISINPUT!",
"Oops",
"Vaxry is going to be upset.",
"Who tried dividing by zero?!",
"Maybe you should try dusting your PC in the meantime?",
"I tried so hard, and got so far...",
"I don't feel so good...",
"*thud*",
"Well this is awkward.",
"\"stable\"",
"I hope you didn't have any unsaved progress.",
"All these computers..."};
static char const* const MESSAGES[] = {
"Sorry, didn't mean to...",
"This was an accident, I swear!",
"Calm down, it was a misinput! MISINPUT!",
"Oops",
"Vaxry is going to be upset.",
"Who tried dividing by zero?!",
"Maybe you should try dusting your PC in the meantime?",
"I tried so hard, and got so far...",
"I don't feel so good...",
"*thud*",
"Well this is awkward.",
"\"stable\"",
"I hope you didn't have any unsaved progress.",
"All these computers...",
"The math isn't mathing...",
"We've got an imposter in the code!",
"Well, at least the crash reporter didn't crash!",
"Everything's just fi-",
"Have you tried asking Hyprland politely not to crash?",
};
// <random> is not async-signal-safe, fake it with time(NULL) instead
char const* getRandomMessage() {
static char const* getRandomMessage() {
return MESSAGES[time(nullptr) % (sizeof(MESSAGES) / sizeof(MESSAGES[0]))];
}
[[noreturn]] inline void exitWithError(char const* err) {
[[noreturn]] static inline void exitWithError(char const* err) {
write(STDERR_FILENO, err, strlen(err));
// perror() is not signal-safe, but we use it here
// because if the crash-handler already crashed, it can't get any worse.
@ -43,17 +50,17 @@ char const* getRandomMessage() {
abort();
}
void NCrashReporter::createAndSaveCrash(int sig) {
void CrashReporter::createAndSaveCrash(int sig) {
int reportFd = -1;
// We're in the signal handler, so we *only* have stack memory.
// To save as much stack memory as possible,
// destroy things as soon as possible.
{
CMaxLengthCString<255> reportPath;
SignalSafe::CMaxLengthCString<255> reportPath;
const auto HOME = sigGetenv("HOME");
const auto CACHE_HOME = sigGetenv("XDG_CACHE_HOME");
const auto HOME = SignalSafe::getenv("HOME");
const auto CACHE_HOME = SignalSafe::getenv("XDG_CACHE_HOME");
if (CACHE_HOME && CACHE_HOME[0] != '\0') {
reportPath += CACHE_HOME;
@ -67,32 +74,30 @@ void NCrashReporter::createAndSaveCrash(int sig) {
}
int ret = mkdir(reportPath.getStr(), S_IRWXU);
//__asm__("int $3");
if (ret < 0 && errno != EEXIST) {
if (ret < 0 && errno != EEXIST)
exitWithError("failed to mkdir() crash report directory\n");
}
reportPath += "/hyprlandCrashReport";
reportPath.writeNum(getpid());
reportPath += ".txt";
{
CBufFileWriter<64> stderr(2);
stderr += "Hyprland has crashed :( Consult the crash report at ";
if (!reportPath.boundsExceeded()) {
stderr += reportPath.getStr();
} else {
stderr += "[ERROR: Crash report path does not fit into memory! Check if your $CACHE_HOME/$HOME is too deeply nested. Max 255 characters.]";
}
stderr += " for more information.\n";
stderr.flush();
SignalSafe::CBufFileWriter<64> stderrOut(STDERR_FILENO);
stderrOut += "Hyprland has crashed :( Consult the crash report at ";
if (!reportPath.boundsExceeded())
stderrOut += reportPath.getStr();
else
stderrOut += "[ERROR: Crash report path does not fit into memory! Check if your $CACHE_HOME/$HOME is too deeply nested. Max 255 characters.]";
stderrOut += " for more information.\n";
stderrOut.flush();
}
reportFd = open(reportPath.getStr(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
if (reportFd < 0) {
if (reportFd < 0)
exitWithError("Failed to open crash report path for writing");
}
}
CBufFileWriter<512> finalCrashReport(reportFd);
SignalSafe::CBufFileWriter<512> finalCrashReport(reportFd);
finalCrashReport += "--------------------------------------------\n Hyprland Crash Report\n--------------------------------------------\n";
finalCrashReport += getRandomMessage();
@ -101,7 +106,7 @@ void NCrashReporter::createAndSaveCrash(int sig) {
finalCrashReport += "Hyprland received signal ";
finalCrashReport.writeNum(sig);
finalCrashReport += '(';
finalCrashReport += sigStrsignal(sig);
finalCrashReport += SignalSafe::strsignal(sig);
finalCrashReport += ")\nVersion: ";
finalCrashReport += GIT_COMMIT_HASH;
finalCrashReport += "\nTag: ";
@ -165,6 +170,10 @@ void NCrashReporter::createAndSaveCrash(int sig) {
finalCrashReport += "\n\nos-release:\n";
finalCrashReport.writeCmdOutput("cat /etc/os-release | sed 's/^/\t/'");
finalCrashReport += '\n';
finalCrashReport += getBuiltSystemLibraryNames();
finalCrashReport += '\n';
// dladdr1()/backtrace_symbols()/this entire section allocates, and hence is NOT async-signal-safe.
// Make sure that we save the current known crash report information,
// so that if we are caught in a deadlock during a call to malloc(),
@ -239,5 +248,5 @@ void NCrashReporter::createAndSaveCrash(int sig) {
finalCrashReport += "\n\nLog tail:\n";
finalCrashReport += std::string_view(Debug::m_rollingLog).substr(Debug::m_rollingLog.find('\n') + 1);
finalCrashReport += Log::logger->rolling();
}

View file

@ -0,0 +1,5 @@
#pragma once
namespace CrashReporter {
void createAndSaveCrash(int sig);
};

View file

@ -1,4 +1,4 @@
#include "signal-safe.hpp"
#include "SignalSafe.hpp"
#ifndef __GLIBC__
#include <signal.h>
@ -7,11 +7,13 @@
#include <unistd.h>
#include <cstring>
using namespace SignalSafe;
// NOLINTNEXTLINE
extern "C" char** environ;
//
char const* sigGetenv(char const* name) {
char const* SignalSafe::getenv(char const* name) {
const size_t len = strlen(name);
for (char** var = environ; *var != nullptr; var++) {
if (strncmp(*var, name, len) == 0 && (*var)[len] == '=') {
@ -21,7 +23,7 @@ char const* sigGetenv(char const* name) {
return nullptr;
}
char const* sigStrsignal(int sig) {
char const* SignalSafe::strsignal(int sig) {
#ifdef __GLIBC__
return sigabbrev_np(sig);
#elif defined(__DragonFly__) || defined(__FreeBSD__)

View file

@ -0,0 +1,203 @@
#pragma once
#include "defines.hpp"
#include <cstring>
namespace SignalSafe {
template <uint16_t N>
class CMaxLengthCString {
public:
CMaxLengthCString() {
m_str[0] = '\0';
}
void operator+=(char const* rhs) {
write(rhs, strlen(rhs));
}
void write(char const* data, size_t len) {
if (m_boundsExceeded || m_strPos + len >= N) {
m_boundsExceeded = true;
return;
}
memcpy(m_str + m_strPos, data, len);
m_strPos += len;
m_str[m_strPos] = '\0';
}
void write(char c) {
if (m_boundsExceeded || m_strPos + 1 >= N) {
m_boundsExceeded = true;
return;
}
m_str[m_strPos] = c;
m_strPos++;
}
void writeNum(size_t num) {
size_t d = 1;
while (num / 10 >= d) {
d *= 10;
}
while (num > 0) {
char c = '0' + (num / d);
write(c);
num %= d;
d /= 10;
}
}
char const* getStr() {
return m_str;
}
bool boundsExceeded() {
return m_boundsExceeded;
}
private:
char m_str[N];
size_t m_strPos = 0;
bool m_boundsExceeded = false;
};
template <uint16_t BUFSIZE>
class CBufFileWriter {
public:
CBufFileWriter(int fd_) : m_fd(fd_) {
;
}
~CBufFileWriter() {
flush();
}
void write(char const* data, size_t len) {
while (len > 0) {
size_t to_add = std::min(len, sc<size_t>(BUFSIZE) - m_writeBufPos);
memcpy(m_writeBuf + m_writeBufPos, data, to_add);
data += to_add;
len -= to_add;
m_writeBufPos += to_add;
if (m_writeBufPos == BUFSIZE)
flush();
}
}
void write(char c) {
if (m_writeBufPos == BUFSIZE)
flush();
m_writeBuf[m_writeBufPos] = c;
m_writeBufPos++;
}
void operator+=(char const* str) {
write(str, strlen(str));
}
void operator+=(std::string_view str) {
write(str.data(), str.size());
}
void operator+=(char c) {
write(c);
}
void writeNum(size_t num) {
size_t d = 1;
while (num / 10 >= d) {
d *= 10;
}
while (num > 0) {
char c = '0' + (num / d);
write(c);
num %= d;
d /= 10;
}
}
void writeCmdOutput(const char* cmd) {
int pipefd[2];
if (pipe(pipefd) < 0) {
*this += "<pipe(pipefd) failed with";
writeNum(errno);
*this += ">\n";
return;
}
// terminate child instead of waiting
{
struct sigaction act;
act.sa_handler = SIG_DFL;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_NOCLDWAIT;
#ifdef SA_RESTORER
act.sa_restorer = NULL;
#endif
sigaction(SIGCHLD, &act, nullptr);
}
const pid_t pid = fork();
if (pid < 0) {
*this += "<fork() failed with ";
writeNum(errno);
*this += ">\n";
return;
}
if (pid == 0) {
close(pipefd[0]);
dup2(pipefd[1], STDOUT_FILENO);
char const* const argv[] = {"/bin/sh", "-c", cmd, nullptr};
execv("/bin/sh", cc<char* const*>(argv));
CBufFileWriter<64> failmsg(pipefd[1]);
failmsg += "<execv(";
failmsg += cmd;
failmsg += ") resulted in errno ";
failmsg.write(errno);
failmsg += ">\n";
close(pipefd[1]);
abort();
} else {
close(pipefd[1]);
int64_t len = 0;
char readbuf[256];
while ((len = read(pipefd[0], readbuf, 256)) > 0) {
write(readbuf, len);
}
if (len < 0) {
*this += "<interrupted, read() resulted in errno ";
writeNum(errno);
*this += ">\n";
}
close(pipefd[0]);
}
}
void flush() {
size_t i = 0;
while (i < m_writeBufPos) {
auto written = ::write(m_fd, m_writeBuf + i, m_writeBufPos - i);
if (written <= 0) {
return;
}
i += written;
}
m_writeBufPos = 0;
}
private:
char m_writeBuf[BUFSIZE] = {0};
size_t m_writeBufPos = 0;
int m_fd = 0;
};
char const* getenv(const char* name);
char const* strsignal(int sig);
}

64
src/debug/log/Logger.cpp Normal file
View file

@ -0,0 +1,64 @@
#include "Logger.hpp"
#include "RollingLogFollow.hpp"
#include "../../defines.hpp"
#include "../../managers/HookSystemManager.hpp"
#include "../../config/ConfigValue.hpp"
using namespace Log;
CLogger::CLogger() {
const auto IS_TRACE = Env::isTrace();
m_logger.setLogLevel(IS_TRACE ? Hyprutils::CLI::LOG_TRACE : Hyprutils::CLI::LOG_DEBUG);
}
void CLogger::log(Hyprutils::CLI::eLogLevel level, const std::string_view& str) {
static bool TRACE = Env::isTrace();
if (!m_logsEnabled)
return;
if (level == Hyprutils::CLI::LOG_TRACE && !TRACE)
return;
if (SRollingLogFollow::get().isRunning())
SRollingLogFollow::get().addLog(str);
m_logger.log(level, str);
}
void CLogger::initIS(const std::string_view& IS) {
// NOLINTNEXTLINE
m_logger.setOutputFile(std::string{IS} + (ISDEBUG ? "/hyprlandd.log" : "/hyprland.log"));
m_logger.setEnableRolling(true);
m_logger.setEnableColor(false);
m_logger.setEnableStdout(true);
m_logger.setTime(false);
}
void CLogger::initCallbacks() {
static auto P = g_pHookSystem->hookDynamic("configReloaded", [this](void* hk, SCallbackInfo& info, std::any param) { recheckCfg(); });
recheckCfg();
}
void CLogger::recheckCfg() {
static auto PDISABLELOGS = CConfigValue<Hyprlang::INT>("debug:disable_logs");
static auto PDISABLETIME = CConfigValue<Hyprlang::INT>("debug:disable_time");
static auto PENABLESTDOUT = CConfigValue<Hyprlang::INT>("debug:enable_stdout_logs");
static auto PENABLECOLOR = CConfigValue<Hyprlang::INT>("debug:colored_stdout_logs");
m_logger.setEnableStdout(!*PDISABLELOGS && *PENABLESTDOUT);
m_logsEnabled = !*PDISABLELOGS;
m_logger.setTime(!*PDISABLETIME);
m_logger.setEnableColor(*PENABLECOLOR);
}
const std::string& CLogger::rolling() {
return m_logger.rollingLog();
}
Hyprutils::CLI::CLogger& CLogger::hu() {
return m_logger;
}

61
src/debug/log/Logger.hpp Normal file
View file

@ -0,0 +1,61 @@
#pragma once
#include <hyprutils/cli/Logger.hpp>
#include "../../helpers/memory/Memory.hpp"
#include "../../helpers/env/Env.hpp"
namespace Log {
class CLogger {
public:
CLogger();
~CLogger() = default;
void initIS(const std::string_view& IS);
void initCallbacks();
void log(Hyprutils::CLI::eLogLevel level, const std::string_view& str);
template <typename... Args>
//NOLINTNEXTLINE
void log(Hyprutils::CLI::eLogLevel level, std::format_string<Args...> fmt, Args&&... args) {
static bool TRACE = Env::isTrace();
if (!m_logsEnabled)
return;
if (level == Hyprutils::CLI::LOG_TRACE && !TRACE)
return;
std::string logMsg = "";
// no need for try {} catch {} because std::format_string<Args...> ensures that vformat never throw std::format_error
// because
// 1. any faulty format specifier that sucks will cause a compilation error.
// 2. and `std::bad_alloc` is catastrophic, (Almost any operation in stdlib could throw this.)
// 3. this is actually what std::format in stdlib does
logMsg += std::vformat(fmt.get(), std::make_format_args(args...));
log(level, logMsg);
}
const std::string& rolling();
Hyprutils::CLI::CLogger& hu();
private:
void recheckCfg();
Hyprutils::CLI::CLogger m_logger;
bool m_logsEnabled = true;
};
inline UP<CLogger> logger = makeUnique<CLogger>();
//
inline constexpr const Hyprutils::CLI::eLogLevel DEBUG = Hyprutils::CLI::LOG_DEBUG;
inline constexpr const Hyprutils::CLI::eLogLevel WARN = Hyprutils::CLI::LOG_WARN;
inline constexpr const Hyprutils::CLI::eLogLevel ERR = Hyprutils::CLI::LOG_ERR;
inline constexpr const Hyprutils::CLI::eLogLevel CRIT = Hyprutils::CLI::LOG_CRIT;
inline constexpr const Hyprutils::CLI::eLogLevel INFO = Hyprutils::CLI::LOG_DEBUG;
inline constexpr const Hyprutils::CLI::eLogLevel TRACE = Hyprutils::CLI::LOG_TRACE;
};

View file

@ -1,9 +1,11 @@
#pragma once
#include <shared_mutex>
#include <unordered_map>
#include <format>
#include <vector>
// NOLINTNEXTLINE(readability-identifier-naming)
namespace Debug {
namespace Log {
struct SRollingLogFollow {
std::unordered_map<int, std::string> m_socketToRollingLogFollowQueue;
std::shared_mutex m_mutex;
@ -30,12 +32,14 @@ namespace Debug {
return ret;
};
void addLog(const std::string& log) {
void addLog(const std::string_view& log) {
std::unique_lock<std::shared_mutex> w(m_mutex);
m_running = true;
std::vector<int> to_erase;
for (const auto& p : m_socketToRollingLogFollowQueue)
m_socketToRollingLogFollowQueue[p.first] += log + "\n";
for (const auto& p : m_socketToRollingLogFollowQueue) {
m_socketToRollingLogFollowQueue[p.first] += log;
m_socketToRollingLogFollowQueue[p.first] += "\n";
}
}
bool isRunning() {

View file

@ -1,7 +1,7 @@
#pragma once
#include "includes.hpp"
#include "debug/Log.hpp"
#include "debug/log/Logger.hpp"
#include "helpers/Color.hpp"
#include "macros.hpp"
#include "desktop/DesktopTypes.hpp"

View file

@ -1,26 +1,30 @@
#pragma once
#include "../helpers/memory/Memory.hpp"
class CWorkspace;
class CWindow;
class CLayerSurface;
class CMonitor;
namespace Desktop::View {
class CWindow;
class CLayerSurface;
}
/* Shared pointer to a workspace */
using PHLWORKSPACE = SP<CWorkspace>;
/* Weak pointer to a workspace */
using PHLWORKSPACEREF = WP<CWorkspace>;
/* Shared pointer to a window */
using PHLWINDOW = SP<CWindow>;
using PHLWINDOW = SP<Desktop::View::CWindow>;
/* Weak pointer to a window */
using PHLWINDOWREF = WP<CWindow>;
using PHLWINDOWREF = WP<Desktop::View::CWindow>;
/* Shared pointer to a layer surface */
using PHLLS = SP<CLayerSurface>;
using PHLLS = SP<Desktop::View::CLayerSurface>;
/* Weak pointer to a layer surface */
using PHLLSREF = WP<CLayerSurface>;
using PHLLSREF = WP<Desktop::View::CLayerSurface>;
/* Shared pointer to a monitor */
using PHLMONITOR = SP<CMonitor>;
/* Weak pointer to a monitor */
using PHLMONITORREF = WP<CMonitor>;
using PHLMONITORREF = WP<CMonitor>;

View file

@ -1,40 +0,0 @@
#include <re2/re2.h>
#include "LayerRule.hpp"
#include <unordered_set>
#include <algorithm>
#include "../debug/Log.hpp"
static const auto RULES = std::unordered_set<std::string>{"noanim", "blur", "blurpopups", "dimaround"};
static const auto RULES_PREFIX = std::unordered_set<std::string>{"ignorealpha", "ignorezero", "xray", "animation", "order", "abovelock"};
CLayerRule::CLayerRule(const std::string& rule_, const std::string& ns_) : m_targetNamespace(ns_), m_rule(rule_) {
const bool VALID = RULES.contains(m_rule) || std::ranges::any_of(RULES_PREFIX, [&rule_](const auto& prefix) { return rule_.starts_with(prefix); });
if (!VALID)
return;
if (m_rule == "noanim")
m_ruleType = RULE_NOANIM;
else if (m_rule == "blur")
m_ruleType = RULE_BLUR;
else if (m_rule == "blurpopups")
m_ruleType = RULE_BLURPOPUPS;
else if (m_rule == "dimaround")
m_ruleType = RULE_DIMAROUND;
else if (m_rule.starts_with("ignorealpha"))
m_ruleType = RULE_IGNOREALPHA;
else if (m_rule.starts_with("ignorezero"))
m_ruleType = RULE_IGNOREZERO;
else if (m_rule.starts_with("xray"))
m_ruleType = RULE_XRAY;
else if (m_rule.starts_with("animation"))
m_ruleType = RULE_ANIMATION;
else if (m_rule.starts_with("order"))
m_ruleType = RULE_ORDER;
else if (m_rule.starts_with("abovelock"))
m_ruleType = RULE_ABOVELOCK;
else {
Debug::log(ERR, "CLayerRule: didn't match a rule that was found valid?!");
m_ruleType = RULE_INVALID;
}
}

View file

@ -1,32 +0,0 @@
#pragma once
#include <string>
#include <cstdint>
#include "Rule.hpp"
class CLayerRule {
public:
CLayerRule(const std::string& rule, const std::string& targetNS);
enum eRuleType : uint8_t {
RULE_INVALID = 0,
RULE_NOANIM,
RULE_BLUR,
RULE_BLURPOPUPS,
RULE_DIMAROUND,
RULE_ABOVELOCK,
RULE_IGNOREALPHA,
RULE_IGNOREZERO,
RULE_XRAY,
RULE_ANIMATION,
RULE_ORDER,
RULE_ZUMBA,
};
eRuleType m_ruleType = RULE_INVALID;
const std::string m_targetNamespace;
const std::string m_rule;
CRuleRegexContainer m_targetNamespaceRegex;
};

View file

@ -1,106 +0,0 @@
#pragma once
#include <string>
#include "../defines.hpp"
#include "WLSurface.hpp"
#include "../helpers/AnimatedVariable.hpp"
class CLayerShellResource;
class CLayerSurface {
public:
static PHLLS create(SP<CLayerShellResource>);
private:
CLayerSurface(SP<CLayerShellResource>);
public:
~CLayerSurface();
void applyRules();
bool isFadedOut();
int popupsCount();
PHLANIMVAR<Vector2D> m_realPosition;
PHLANIMVAR<Vector2D> m_realSize;
PHLANIMVAR<float> m_alpha;
WP<CLayerShellResource> m_layerSurface;
// the header providing the enum type cannot be imported here
int m_interactivity = 0;
SP<CWLSurface> m_surface;
bool m_mapped = false;
uint32_t m_layer = 0;
PHLMONITORREF m_monitor;
bool m_fadingOut = false;
bool m_readyToDelete = false;
bool m_noProcess = false;
bool m_noAnimations = false;
bool m_forceBlur = false;
bool m_forceBlurPopups = false;
int64_t m_xray = -1;
bool m_ignoreAlpha = false;
float m_ignoreAlphaValue = 0.f;
bool m_dimAround = false;
int64_t m_order = 0;
bool m_aboveLockscreen = false;
bool m_aboveLockscreenInteractable = false;
std::optional<std::string> m_animationStyle;
PHLLSREF m_self;
CBox m_geometry = {0, 0, 0, 0};
Vector2D m_position;
std::string m_namespace = "";
UP<CPopup> m_popupHead;
pid_t getPID();
void onDestroy();
void onMap();
void onUnmap();
void onCommit();
MONITORID monitorID();
private:
struct {
CHyprSignalListener destroy;
CHyprSignalListener map;
CHyprSignalListener unmap;
CHyprSignalListener commit;
} m_listeners;
void registerCallbacks();
// For the list lookup
bool operator==(const CLayerSurface& rhs) const {
return m_layerSurface == rhs.m_layerSurface && m_monitor == rhs.m_monitor;
}
};
inline bool valid(PHLLS l) {
return l;
}
inline bool valid(PHLLSREF l) {
return l;
}
inline bool validMapped(PHLLS l) {
if (!valid(l))
return false;
return l->m_mapped;
}
inline bool validMapped(PHLLSREF l) {
if (!valid(l))
return false;
return l->m_mapped;
}

View file

@ -1,96 +0,0 @@
#pragma once
#include <vector>
#include "Subsurface.hpp"
#include "../helpers/signal/Signal.hpp"
#include "../helpers/memory/Memory.hpp"
#include "../helpers/AnimatedVariable.hpp"
class CXDGPopupResource;
class CPopup {
public:
// dummy head nodes
static UP<CPopup> create(PHLWINDOW pOwner);
static UP<CPopup> create(PHLLS pOwner);
// real nodes
static UP<CPopup> create(SP<CXDGPopupResource> popup, WP<CPopup> pOwner);
~CPopup();
SP<CWLSurface> getT1Owner();
Vector2D coordsRelativeToParent();
Vector2D coordsGlobal();
PHLMONITOR getMonitor();
Vector2D size();
void onNewPopup(SP<CXDGPopupResource> popup);
void onDestroy();
void onMap();
void onUnmap();
void onCommit(bool ignoreSiblings = false);
void onReposition();
void recheckTree();
bool visible();
bool inert() const;
// will also loop over this node
void breadthfirst(std::function<void(WP<CPopup>, void*)> fn, void* data);
WP<CPopup> at(const Vector2D& globalCoords, bool allowsInput = false);
//
SP<CWLSurface> m_wlSurface;
WP<CPopup> m_self;
bool m_mapped = false;
// fade in-out
PHLANIMVAR<float> m_alpha;
bool m_fadingOut = false;
private:
CPopup() = default;
// T1 owners, each popup has to have one of these
PHLWINDOWREF m_windowOwner;
PHLLSREF m_layerOwner;
// T2 owners
WP<CPopup> m_parent;
WP<CXDGPopupResource> m_resource;
Vector2D m_lastSize = {};
Vector2D m_lastPos = {};
bool m_requestedReposition = false;
bool m_inert = false;
//
std::vector<UP<CPopup>> m_children;
UP<CSubsurface> m_subsurfaceHead;
struct {
CHyprSignalListener newPopup;
CHyprSignalListener destroy;
CHyprSignalListener map;
CHyprSignalListener unmap;
CHyprSignalListener commit;
CHyprSignalListener dismissed;
CHyprSignalListener reposition;
} m_listeners;
void initAllSignals();
void reposition();
void recheckChildrenRecursive();
void sendScale();
void fullyDestroy();
Vector2D localToGlobal(const Vector2D& rel);
Vector2D t1ParentCoords();
static void bfHelper(std::vector<WP<CPopup>> const& nodes, std::function<void(WP<CPopup>, void*)> fn, void* data);
};

View file

@ -1,22 +0,0 @@
#include <re2/re2.h>
#include "../helpers/memory/Memory.hpp"
#include "Rule.hpp"
#include "../debug/Log.hpp"
CRuleRegexContainer::CRuleRegexContainer(const std::string& regex_) {
const bool NEGATIVE = regex_.starts_with("negative:");
m_negative = NEGATIVE;
m_regex = makeUnique<RE2>(NEGATIVE ? regex_.substr(9) : regex_);
// TODO: maybe pop an error?
if (!m_regex->ok())
Debug::log(ERR, "RuleRegexContainer: regex {} failed to parse!", regex_);
}
bool CRuleRegexContainer::passes(const std::string& str) const {
if (!m_regex)
return false;
return RE2::FullMatch(str, *m_regex) != m_negative;
}

View file

@ -1,21 +0,0 @@
#pragma once
#include <hyprutils/memory/UniquePtr.hpp>
//NOLINTNEXTLINE
namespace re2 {
class RE2;
};
class CRuleRegexContainer {
public:
CRuleRegexContainer() = default;
CRuleRegexContainer(const std::string& regex);
bool passes(const std::string& str) const;
private:
Hyprutils::Memory::CUniquePointer<re2::RE2> m_regex;
bool m_negative = false;
};

View file

@ -1,69 +0,0 @@
#pragma once
#include "../defines.hpp"
#include <vector>
#include "WLSurface.hpp"
class CPopup;
class CWLSubsurfaceResource;
class CSubsurface {
public:
// root dummy nodes
static UP<CSubsurface> create(PHLWINDOW pOwner);
static UP<CSubsurface> create(WP<CPopup> pOwner);
// real nodes
static UP<CSubsurface> create(SP<CWLSubsurfaceResource> pSubsurface, PHLWINDOW pOwner);
static UP<CSubsurface> create(SP<CWLSubsurfaceResource> pSubsurface, WP<CPopup> pOwner);
~CSubsurface() = default;
Vector2D coordsRelativeToParent();
Vector2D coordsGlobal();
Vector2D size();
void onCommit();
void onDestroy();
void onNewSubsurface(SP<CWLSubsurfaceResource> pSubsurface);
void onMap();
void onUnmap();
bool visible();
void recheckDamageForSubsurfaces();
WP<CSubsurface> m_self;
private:
CSubsurface() = default;
struct {
CHyprSignalListener destroySubsurface;
CHyprSignalListener commitSubsurface;
CHyprSignalListener mapSubsurface;
CHyprSignalListener unmapSubsurface;
CHyprSignalListener newSubsurface;
} m_listeners;
WP<CWLSubsurfaceResource> m_subsurface;
SP<CWLSurface> m_wlSurface;
Vector2D m_lastSize = {};
Vector2D m_lastPosition = {};
// if nullptr, means it's a dummy node
WP<CSubsurface> m_parent;
PHLWINDOWREF m_windowParent;
WP<CPopup> m_popupParent;
std::vector<UP<CSubsurface>> m_children;
bool m_inert = false;
void initSignals();
void initExistingSubsurfaces(SP<CWLSurfaceResource> pSurface);
void checkSiblingDamage();
void damageLastArea();
};

View file

@ -1,235 +0,0 @@
#include "WLSurface.hpp"
#include "LayerSurface.hpp"
#include "../desktop/Window.hpp"
#include "../protocols/core/Compositor.hpp"
#include "../protocols/LayerShell.hpp"
#include "../render/Renderer.hpp"
void CWLSurface::assign(SP<CWLSurfaceResource> pSurface) {
m_resource = pSurface;
init();
m_inert = false;
}
void CWLSurface::assign(SP<CWLSurfaceResource> pSurface, PHLWINDOW pOwner) {
m_windowOwner = pOwner;
m_resource = pSurface;
init();
m_inert = false;
}
void CWLSurface::assign(SP<CWLSurfaceResource> pSurface, PHLLS pOwner) {
m_layerOwner = pOwner;
m_resource = pSurface;
init();
m_inert = false;
}
void CWLSurface::assign(SP<CWLSurfaceResource> pSurface, CSubsurface* pOwner) {
m_subsurfaceOwner = pOwner;
m_resource = pSurface;
init();
m_inert = false;
}
void CWLSurface::assign(SP<CWLSurfaceResource> pSurface, CPopup* pOwner) {
m_popupOwner = pOwner;
m_resource = pSurface;
init();
m_inert = false;
}
void CWLSurface::unassign() {
destroy();
}
CWLSurface::~CWLSurface() {
destroy();
}
bool CWLSurface::exists() const {
return m_resource;
}
SP<CWLSurfaceResource> CWLSurface::resource() const {
return m_resource.lock();
}
bool CWLSurface::small() const {
if (!validMapped(m_windowOwner) || !exists())
return false;
if (!m_resource->m_current.texture)
return false;
const auto O = m_windowOwner.lock();
return O->m_reportedSize.x > m_resource->m_current.size.x + 1 || O->m_reportedSize.y > m_resource->m_current.size.y + 1;
}
Vector2D CWLSurface::correctSmallVec() const {
if (!validMapped(m_windowOwner) || !exists() || !small() || m_fillIgnoreSmall)
return {};
const auto SIZE = getViewporterCorrectedSize();
const auto O = m_windowOwner.lock();
return Vector2D{(O->m_reportedSize.x - SIZE.x) / 2, (O->m_reportedSize.y - SIZE.y) / 2}.clamp({}, {INFINITY, INFINITY}) * (O->m_realSize->value() / O->m_reportedSize);
}
Vector2D CWLSurface::correctSmallVecBuf() const {
if (!exists() || !small() || m_fillIgnoreSmall || !m_resource->m_current.texture)
return {};
const auto SIZE = getViewporterCorrectedSize();
const auto BS = m_resource->m_current.bufferSize;
return Vector2D{(BS.x - SIZE.x) / 2, (BS.y - SIZE.y) / 2}.clamp({}, {INFINITY, INFINITY});
}
Vector2D CWLSurface::getViewporterCorrectedSize() const {
if (!exists() || !m_resource->m_current.texture)
return {};
return m_resource->m_current.viewport.hasDestination ? m_resource->m_current.viewport.destination : m_resource->m_current.bufferSize;
}
CRegion CWLSurface::computeDamage() const {
if (!m_resource->m_current.texture)
return {};
CRegion damage = m_resource->m_current.accumulateBufferDamage();
damage.transform(wlTransformToHyprutils(m_resource->m_current.transform), m_resource->m_current.bufferSize.x, m_resource->m_current.bufferSize.y);
const auto BUFSIZE = m_resource->m_current.bufferSize;
const auto CORRECTVEC = correctSmallVecBuf();
if (m_resource->m_current.viewport.hasSource)
damage.intersect(m_resource->m_current.viewport.source);
const auto SCALEDSRCSIZE =
m_resource->m_current.viewport.hasSource ? m_resource->m_current.viewport.source.size() * m_resource->m_current.scale : m_resource->m_current.bufferSize;
damage.scale({BUFSIZE.x / SCALEDSRCSIZE.x, BUFSIZE.y / SCALEDSRCSIZE.y});
damage.translate(CORRECTVEC);
// go from buffer coords in the damage to hl logical
const auto BOX = getSurfaceBoxGlobal();
const Vector2D SCALE = BOX.has_value() ? BOX->size() / m_resource->m_current.bufferSize :
Vector2D{1.0 / m_resource->m_current.scale, 1.0 / m_resource->m_current.scale /* Wrong... but we can't really do better */};
damage.scale(SCALE);
if (m_windowOwner)
damage.scale(m_windowOwner->m_X11SurfaceScaledBy); // fix xwayland:force_zero_scaling stuff that will be fucked by the above a bit
return damage;
}
void CWLSurface::destroy() {
if (!m_resource)
return;
m_events.destroy.emit();
m_constraint.reset();
m_listeners.destroy.reset();
m_resource->m_hlSurface.reset();
m_windowOwner.reset();
m_layerOwner.reset();
m_popupOwner = nullptr;
m_subsurfaceOwner = nullptr;
m_inert = true;
if (g_pHyprRenderer && g_pHyprRenderer->m_lastCursorData.surf && g_pHyprRenderer->m_lastCursorData.surf->get() == this)
g_pHyprRenderer->m_lastCursorData.surf.reset();
m_resource.reset();
Debug::log(LOG, "CWLSurface {:x} called destroy()", rc<uintptr_t>(this));
}
void CWLSurface::init() {
if (!m_resource)
return;
RASSERT(!m_resource->m_hlSurface, "Attempted to duplicate CWLSurface ownership!");
m_resource->m_hlSurface = m_self.lock();
m_listeners.destroy = m_resource->m_events.destroy.listen([this] { destroy(); });
Debug::log(LOG, "CWLSurface {:x} called init()", rc<uintptr_t>(this));
}
PHLWINDOW CWLSurface::getWindow() const {
return m_windowOwner.lock();
}
PHLLS CWLSurface::getLayer() const {
return m_layerOwner.lock();
}
CPopup* CWLSurface::getPopup() const {
return m_popupOwner;
}
CSubsurface* CWLSurface::getSubsurface() const {
return m_subsurfaceOwner;
}
bool CWLSurface::desktopComponent() const {
return !m_layerOwner.expired() || !m_windowOwner.expired() || m_subsurfaceOwner || m_popupOwner;
}
std::optional<CBox> CWLSurface::getSurfaceBoxGlobal() const {
if (!desktopComponent())
return {};
if (!m_windowOwner.expired())
return m_windowOwner->getWindowMainSurfaceBox();
if (!m_layerOwner.expired())
return m_layerOwner->m_geometry;
if (m_popupOwner)
return CBox{m_popupOwner->coordsGlobal(), m_popupOwner->size()};
if (m_subsurfaceOwner)
return CBox{m_subsurfaceOwner->coordsGlobal(), m_subsurfaceOwner->size()};
return {};
}
void CWLSurface::appendConstraint(WP<CPointerConstraint> constraint) {
m_constraint = constraint;
}
SP<CPointerConstraint> CWLSurface::constraint() const {
return m_constraint.lock();
}
bool CWLSurface::visible() {
if (!m_windowOwner.expired())
return g_pHyprRenderer->shouldRenderWindow(m_windowOwner.lock());
if (!m_layerOwner.expired())
return true;
if (m_popupOwner)
return m_popupOwner->visible();
if (m_subsurfaceOwner)
return m_subsurfaceOwner->visible();
return true; // non-desktop, we don't know much.
}
SP<CWLSurface> CWLSurface::fromResource(SP<CWLSurfaceResource> pSurface) {
if (!pSurface)
return nullptr;
return pSurface->m_hlSurface.lock();
}
bool CWLSurface::keyboardFocusable() const {
if (m_windowOwner || m_popupOwner || m_subsurfaceOwner)
return true;
if (m_layerOwner && m_layerOwner->m_layerSurface)
return m_layerOwner->m_layerSurface->m_current.interactivity != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE;
return false;
}

View file

@ -1,124 +0,0 @@
#pragma once
#include "../defines.hpp"
#include "../helpers/math/Math.hpp"
#include "../helpers/signal/Signal.hpp"
class CSubsurface;
class CPopup;
class CPointerConstraint;
class CWLSurfaceResource;
class CWLSurface {
public:
static SP<CWLSurface> create() {
auto p = SP<CWLSurface>(new CWLSurface);
p->m_self = p;
return p;
}
~CWLSurface();
// anonymous surfaces are non-desktop components, e.g. a cursor surface or a DnD
void assign(SP<CWLSurfaceResource> pSurface);
void assign(SP<CWLSurfaceResource> pSurface, PHLWINDOW pOwner);
void assign(SP<CWLSurfaceResource> pSurface, PHLLS pOwner);
void assign(SP<CWLSurfaceResource> pSurface, CSubsurface* pOwner);
void assign(SP<CWLSurfaceResource> pSurface, CPopup* pOwner);
void unassign();
CWLSurface(const CWLSurface&) = delete;
CWLSurface(CWLSurface&&) = delete;
CWLSurface& operator=(const CWLSurface&) = delete;
CWLSurface& operator=(CWLSurface&&) = delete;
SP<CWLSurfaceResource> resource() const;
bool exists() const;
bool small() const; // means surface is smaller than the requested size
Vector2D correctSmallVec() const; // returns a corrective vector for small() surfaces
Vector2D correctSmallVecBuf() const; // returns a corrective vector for small() surfaces, in BL coords
Vector2D getViewporterCorrectedSize() const;
CRegion computeDamage() const; // logical coordinates. May be wrong if the surface is unassigned
bool visible();
bool keyboardFocusable() const;
// getters for owners.
PHLWINDOW getWindow() const;
PHLLS getLayer() const;
CPopup* getPopup() const;
CSubsurface* getSubsurface() const;
// desktop components misc utils
std::optional<CBox> getSurfaceBoxGlobal() const;
void appendConstraint(WP<CPointerConstraint> constraint);
SP<CPointerConstraint> constraint() const;
// allow stretching. Useful for plugins.
bool m_fillIgnoreSmall = false;
// track surface data and avoid dupes
float m_lastScaleFloat = 0;
int m_lastScaleInt = 0;
wl_output_transform m_lastTransform = sc<wl_output_transform>(-1);
//
CWLSurface& operator=(SP<CWLSurfaceResource> pSurface) {
destroy();
m_resource = pSurface;
init();
return *this;
}
bool operator==(const CWLSurface& other) const {
return other.resource() == resource();
}
bool operator==(const SP<CWLSurfaceResource> other) const {
return other == resource();
}
explicit operator bool() const {
return exists();
}
static SP<CWLSurface> fromResource(SP<CWLSurfaceResource> pSurface);
// used by the alpha-modifier protocol
float m_alphaModifier = 1.F;
// used by the hyprland-surface protocol
float m_overallOpacity = 1.F;
CRegion m_visibleRegion;
struct {
CSignalT<> destroy;
} m_events;
WP<CWLSurface> m_self;
private:
CWLSurface() = default;
bool m_inert = true;
WP<CWLSurfaceResource> m_resource;
PHLWINDOWREF m_windowOwner;
PHLLSREF m_layerOwner;
CPopup* m_popupOwner = nullptr;
CSubsurface* m_subsurfaceOwner = nullptr;
//
WP<CPointerConstraint> m_constraint;
void destroy();
void init();
bool desktopComponent() const;
struct {
CHyprSignalListener destroy;
} m_listeners;
friend class CPointerConstraint;
friend class CXxColorManagerV4;
};

View file

@ -1,553 +0,0 @@
#pragma once
#include <vector>
#include <string>
#include <optional>
#include "../config/ConfigDataValues.hpp"
#include "../helpers/AnimatedVariable.hpp"
#include "../helpers/TagKeeper.hpp"
#include "../macros.hpp"
#include "../managers/XWaylandManager.hpp"
#include "../render/decorations/IHyprWindowDecoration.hpp"
#include "../render/Transformer.hpp"
#include "DesktopTypes.hpp"
#include "Popup.hpp"
#include "Subsurface.hpp"
#include "WLSurface.hpp"
#include "Workspace.hpp"
#include "WindowRule.hpp"
#include "WindowOverridableVar.hpp"
#include "../protocols/types/ContentType.hpp"
class CXDGSurfaceResource;
class CXWaylandSurface;
enum eIdleInhibitMode : uint8_t {
IDLEINHIBIT_NONE = 0,
IDLEINHIBIT_ALWAYS,
IDLEINHIBIT_FULLSCREEN,
IDLEINHIBIT_FOCUS
};
enum eGroupRules : uint8_t {
// effective only during first map, except for _ALWAYS variant
GROUP_NONE = 0,
GROUP_SET = 1 << 0, // Open as new group or add to focused group
GROUP_SET_ALWAYS = 1 << 1,
GROUP_BARRED = 1 << 2, // Don't insert to focused group.
GROUP_LOCK = 1 << 3, // Lock m_sGroupData.lock
GROUP_LOCK_ALWAYS = 1 << 4,
GROUP_INVADE = 1 << 5, // Force enter a group, event if lock is engaged
GROUP_OVERRIDE = 1 << 6, // Override other rules
};
enum eGetWindowProperties : uint8_t {
WINDOW_ONLY = 0,
RESERVED_EXTENTS = 1 << 0,
INPUT_EXTENTS = 1 << 1,
FULL_EXTENTS = 1 << 2,
FLOATING_ONLY = 1 << 3,
ALLOW_FLOATING = 1 << 4,
USE_PROP_TILED = 1 << 5,
SKIP_FULLSCREEN_PRIORITY = 1 << 6,
FOCUS_PRIORITY = 1 << 7,
};
enum eSuppressEvents : uint8_t {
SUPPRESS_NONE = 0,
SUPPRESS_FULLSCREEN = 1 << 0,
SUPPRESS_MAXIMIZE = 1 << 1,
SUPPRESS_ACTIVATE = 1 << 2,
SUPPRESS_ACTIVATE_FOCUSONLY = 1 << 3,
SUPPRESS_FULLSCREEN_OUTPUT = 1 << 4,
};
class IWindowTransformer;
struct SAlphaValue {
float alpha;
bool overridden;
float applyAlpha(float a) const {
if (overridden)
return alpha;
else
return alpha * a;
};
};
struct SWindowData {
CWindowOverridableVar<SAlphaValue> alpha = SAlphaValue{.alpha = 1.f, .overridden = false};
CWindowOverridableVar<SAlphaValue> alphaInactive = SAlphaValue{.alpha = 1.f, .overridden = false};
CWindowOverridableVar<SAlphaValue> alphaFullscreen = SAlphaValue{.alpha = 1.f, .overridden = false};
CWindowOverridableVar<bool> allowsInput = false;
CWindowOverridableVar<bool> dimAround = false;
CWindowOverridableVar<bool> decorate = true;
CWindowOverridableVar<bool> focusOnActivate = false;
CWindowOverridableVar<bool> keepAspectRatio = false;
CWindowOverridableVar<bool> nearestNeighbor = false;
CWindowOverridableVar<bool> noAnim = false;
CWindowOverridableVar<bool> noBorder = false;
CWindowOverridableVar<bool> noBlur = false;
CWindowOverridableVar<bool> noDim = false;
CWindowOverridableVar<bool> noFocus = false;
CWindowOverridableVar<bool> noMaxSize = false;
CWindowOverridableVar<bool> noRounding = false;
CWindowOverridableVar<bool> noShadow = false;
CWindowOverridableVar<bool> noShortcutsInhibit = false;
CWindowOverridableVar<bool> opaque = false;
CWindowOverridableVar<bool> RGBX = false;
CWindowOverridableVar<bool> syncFullscreen = true;
CWindowOverridableVar<bool> tearing = false;
CWindowOverridableVar<bool> xray = false;
CWindowOverridableVar<bool> renderUnfocused = false;
CWindowOverridableVar<bool> noFollowMouse = false;
CWindowOverridableVar<bool> noScreenShare = false;
CWindowOverridableVar<bool> noVRR = false;
CWindowOverridableVar<Hyprlang::INT> borderSize = {std::string("general:border_size"), sc<Hyprlang::INT>(0), std::nullopt};
CWindowOverridableVar<Hyprlang::INT> rounding = {std::string("decoration:rounding"), sc<Hyprlang::INT>(0), std::nullopt};
CWindowOverridableVar<Hyprlang::FLOAT> roundingPower = {std::string("decoration:rounding_power")};
CWindowOverridableVar<Hyprlang::FLOAT> scrollMouse = {std::string("input:scroll_factor")};
CWindowOverridableVar<Hyprlang::FLOAT> scrollTouchpad = {std::string("input:touchpad:scroll_factor")};
CWindowOverridableVar<std::string> animationStyle;
CWindowOverridableVar<Vector2D> maxSize;
CWindowOverridableVar<Vector2D> minSize;
CWindowOverridableVar<CGradientValueData> activeBorderColor;
CWindowOverridableVar<CGradientValueData> inactiveBorderColor;
CWindowOverridableVar<bool> persistentSize;
};
struct SInitialWorkspaceToken {
PHLWINDOWREF primaryOwner;
std::string workspace;
};
struct SFullscreenState {
eFullscreenMode internal = FSMODE_NONE;
eFullscreenMode client = FSMODE_NONE;
};
class CWindow {
public:
static PHLWINDOW create(SP<CXDGSurfaceResource>);
static PHLWINDOW create(SP<CXWaylandSurface>);
private:
CWindow(SP<CXDGSurfaceResource> resource);
CWindow(SP<CXWaylandSurface> surface);
public:
~CWindow();
SP<CWLSurface> m_wlSurface;
struct {
CSignalT<> destroy;
} m_events;
WP<CXDGSurfaceResource> m_xdgSurface;
WP<CXWaylandSurface> m_xwaylandSurface;
// this is the position and size of the "bounding box"
Vector2D m_position = Vector2D(0, 0);
Vector2D m_size = Vector2D(0, 0);
// this is the real position and size used to draw the thing
PHLANIMVAR<Vector2D> m_realPosition;
PHLANIMVAR<Vector2D> m_realSize;
// for not spamming the protocols
Vector2D m_reportedPosition;
Vector2D m_reportedSize;
Vector2D m_pendingReportedSize;
std::optional<std::pair<uint32_t, Vector2D>> m_pendingSizeAck;
std::vector<std::pair<uint32_t, Vector2D>> m_pendingSizeAcks;
// for restoring floating statuses
Vector2D m_lastFloatingSize;
Vector2D m_lastFloatingPosition;
// for floating window offset in workspace animations
Vector2D m_floatingOffset = Vector2D(0, 0);
// this is used for pseudotiling
bool m_isPseudotiled = false;
Vector2D m_pseudoSize = Vector2D(1280, 720);
// for recovering relative cursor position
Vector2D m_relativeCursorCoordsOnLastWarp = Vector2D(-1, -1);
bool m_firstMap = false; // for layouts
bool m_isFloating = false;
bool m_draggingTiled = false; // for dragging around tiled windows
SFullscreenState m_fullscreenState = {.internal = FSMODE_NONE, .client = FSMODE_NONE};
std::string m_title = "";
std::string m_class = "";
std::string m_initialTitle = "";
std::string m_initialClass = "";
PHLWORKSPACE m_workspace;
PHLMONITORREF m_monitor;
bool m_isMapped = false;
bool m_requestsFloat = false;
// This is for fullscreen apps
bool m_createdOverFullscreen = false;
// XWayland stuff
bool m_isX11 = false;
bool m_X11DoesntWantBorders = false;
bool m_X11ShouldntFocus = false;
float m_X11SurfaceScaledBy = 1.f;
//
// For nofocus
bool m_noInitialFocus = false;
// Fullscreen and Maximize
bool m_wantsInitialFullscreen = false;
MONITORID m_wantsInitialFullscreenMonitor = MONITOR_INVALID;
// bitfield suppressEvents
uint64_t m_suppressedEvents = SUPPRESS_NONE;
// desktop components
UP<CSubsurface> m_subsurfaceHead;
UP<CPopup> m_popupHead;
// Animated border
CGradientValueData m_realBorderColor = {0};
CGradientValueData m_realBorderColorPrevious = {0};
PHLANIMVAR<float> m_borderFadeAnimationProgress;
PHLANIMVAR<float> m_borderAngleAnimationProgress;
// Fade in-out
PHLANIMVAR<float> m_alpha;
bool m_fadingOut = false;
bool m_readyToDelete = false;
Vector2D m_originalClosedPos; // these will be used for calculations later on in
Vector2D m_originalClosedSize; // drawing the closing animations
SBoxExtents m_originalClosedExtents;
bool m_animatingIn = false;
// For pinned (sticky) windows
bool m_pinned = false;
// For preserving pinned state when fullscreening a pinned window
bool m_pinFullscreened = false;
// urgency hint
bool m_isUrgent = false;
// for proper cycling. While cycling we can't just move the pointers, so we need to keep track of the last cycled window.
PHLWINDOWREF m_lastCycledWindow;
// Window decorations
// TODO: make this a SP.
std::vector<UP<IHyprWindowDecoration>> m_windowDecorations;
std::vector<IHyprWindowDecoration*> m_decosToRemove;
// Special render data, rules, etc
SWindowData m_windowData;
// Transformers
std::vector<UP<IWindowTransformer>> m_transformers;
// for alpha
PHLANIMVAR<float> m_activeInactiveAlpha;
PHLANIMVAR<float> m_movingFromWorkspaceAlpha;
// animated shadow color
PHLANIMVAR<CHyprColor> m_realShadowColor;
// animated tint
PHLANIMVAR<float> m_dimPercent;
// animate moving to an invisible workspace
int m_monitorMovedFrom = -1; // -1 means not moving
PHLANIMVAR<float> m_movingToWorkspaceAlpha;
// swallowing
PHLWINDOWREF m_swallowed;
bool m_currentlySwallowed = false;
bool m_groupSwallowed = false;
// focus stuff
bool m_stayFocused = false;
// for toplevel monitor events
MONITORID m_lastSurfaceMonitorID = -1;
// for idle inhibiting windows
eIdleInhibitMode m_idleInhibitMode = IDLEINHIBIT_NONE;
// initial token. Will be unregistered on workspace change or timeout of 2 minutes
std::string m_initialWorkspaceToken = "";
// for groups
struct SGroupData {
PHLWINDOWREF pNextWindow; // nullptr means no grouping. Self means single group.
bool head = false;
bool locked = false; // per group lock
bool deny = false; // deny window from enter a group or made a group
} m_groupData;
uint16_t m_groupRules = GROUP_NONE;
bool m_tearingHint = false;
// stores the currently matched window rules
std::vector<SP<CWindowRule>> m_matchedRules;
// window tags
CTagKeeper m_tags;
// ANR
PHLANIMVAR<float> m_notRespondingTint;
// For the noclosefor windowrule
Time::steady_tp m_closeableSince = Time::steadyNow();
// For the list lookup
bool operator==(const CWindow& rhs) const {
return m_xdgSurface == rhs.m_xdgSurface && m_xwaylandSurface == rhs.m_xwaylandSurface && m_position == rhs.m_position && m_size == rhs.m_size &&
m_fadingOut == rhs.m_fadingOut;
}
// methods
CBox getFullWindowBoundingBox();
SBoxExtents getFullWindowExtents();
CBox getWindowBoxUnified(uint64_t props);
SBoxExtents getWindowExtentsUnified(uint64_t props);
CBox getWindowIdealBoundingBoxIgnoreReserved();
void addWindowDeco(UP<IHyprWindowDecoration> deco);
void updateWindowDecos();
void removeWindowDeco(IHyprWindowDecoration* deco);
void uncacheWindowDecos();
bool checkInputOnDecos(const eInputType, const Vector2D&, std::any = {});
pid_t getPID();
IHyprWindowDecoration* getDecorationByType(eDecorationType);
void updateToplevel();
void updateSurfaceScaleTransformDetails(bool force = false);
void moveToWorkspace(PHLWORKSPACE);
PHLWINDOW x11TransientFor();
void onUnmap();
void onMap();
void setHidden(bool hidden);
bool isHidden();
void applyDynamicRule(const SP<CWindowRule>& r);
void updateDynamicRules();
SBoxExtents getFullWindowReservedArea();
Vector2D middle();
bool opaque();
float rounding();
float roundingPower();
bool canBeTorn();
void setSuspended(bool suspend);
bool visibleOnMonitor(PHLMONITOR pMonitor);
WORKSPACEID workspaceID();
MONITORID monitorID();
bool onSpecialWorkspace();
void activate(bool force = false);
int surfacesCount();
void clampWindowSize(const std::optional<Vector2D> minSize, const std::optional<Vector2D> maxSize);
bool isFullscreen();
bool isEffectiveInternalFSMode(const eFullscreenMode);
int getRealBorderSize();
float getScrollMouse();
float getScrollTouchpad();
bool isScrollMouseOverridden();
bool isScrollTouchpadOverridden();
void updateWindowData();
void updateWindowData(const struct SWorkspaceRule&);
void onBorderAngleAnimEnd(WP<Hyprutils::Animation::CBaseAnimatedVariable> pav);
bool isInCurvedCorner(double x, double y);
bool hasPopupAt(const Vector2D& pos);
int popupsCount();
void applyGroupRules();
void createGroup();
void destroyGroup();
PHLWINDOW getGroupHead();
PHLWINDOW getGroupTail();
PHLWINDOW getGroupCurrent();
PHLWINDOW getGroupPrevious();
PHLWINDOW getGroupWindowByIndex(int);
int getGroupSize();
bool canBeGroupedInto(PHLWINDOW pWindow);
void setGroupCurrent(PHLWINDOW pWindow);
void insertWindowToGroup(PHLWINDOW pWindow);
void updateGroupOutputs();
void switchWithWindowInGroup(PHLWINDOW pWindow);
void setAnimationsToMove();
void onWorkspaceAnimUpdate();
void onFocusAnimUpdate();
void onUpdateState();
void onUpdateMeta();
void onX11ConfigureRequest(CBox box);
void onResourceChangeX11();
std::string fetchTitle();
std::string fetchClass();
void warpCursor(bool force = false);
PHLWINDOW getSwallower();
void unsetWindowData(eOverridePriority priority);
bool isX11OverrideRedirect();
bool isModal();
Vector2D requestedMinSize();
Vector2D requestedMaxSize();
Vector2D realToReportSize();
Vector2D realToReportPosition();
Vector2D xwaylandSizeToReal(Vector2D size);
Vector2D xwaylandPositionToReal(Vector2D size);
void updateX11SurfaceScale();
void sendWindowSize(bool force = false);
NContentType::eContentType getContentType();
void setContentType(NContentType::eContentType contentType);
void deactivateGroupMembers();
bool isNotResponding();
std::optional<std::string> xdgTag();
std::optional<std::string> xdgDescription();
PHLWINDOW parent();
bool priorityFocus();
SP<CWLSurfaceResource> getSolitaryResource();
CBox getWindowMainSurfaceBox() const {
return {m_realPosition->value().x, m_realPosition->value().y, m_realSize->value().x, m_realSize->value().y};
}
// listeners
void onAck(uint32_t serial);
//
std::unordered_map<std::string, std::string> getEnv();
//
PHLWINDOWREF m_self;
// make private once we move listeners to inside CWindow
struct {
CHyprSignalListener map;
CHyprSignalListener ack;
CHyprSignalListener unmap;
CHyprSignalListener commit;
CHyprSignalListener destroy;
CHyprSignalListener activate;
CHyprSignalListener configureRequest;
CHyprSignalListener setGeometry;
CHyprSignalListener updateState;
CHyprSignalListener updateMetadata;
CHyprSignalListener resourceChange;
} m_listeners;
private:
// For hidden windows and stuff
bool m_hidden = false;
bool m_suspended = false;
WORKSPACEID m_lastWorkspace = WORKSPACE_INVALID;
};
inline bool valid(PHLWINDOW w) {
return w.get();
}
inline bool valid(PHLWINDOWREF w) {
return !w.expired();
}
inline bool validMapped(PHLWINDOW w) {
if (!valid(w))
return false;
return w->m_isMapped;
}
inline bool validMapped(PHLWINDOWREF w) {
if (!valid(w))
return false;
return w->m_isMapped;
}
namespace NWindowProperties {
static const std::unordered_map<std::string, std::function<CWindowOverridableVar<bool>*(const PHLWINDOW&)>> boolWindowProperties = {
{"allowsinput", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.allowsInput; }},
{"dimaround", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.dimAround; }},
{"decorate", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.decorate; }},
{"focusonactivate", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.focusOnActivate; }},
{"keepaspectratio", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.keepAspectRatio; }},
{"nearestneighbor", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.nearestNeighbor; }},
{"noanim", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noAnim; }},
{"noblur", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noBlur; }},
{"noborder", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noBorder; }},
{"nodim", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noDim; }},
{"nofocus", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noFocus; }},
{"nomaxsize", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noMaxSize; }},
{"norounding", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noRounding; }},
{"noshadow", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noShadow; }},
{"noshortcutsinhibit", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noShortcutsInhibit; }},
{"opaque", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.opaque; }},
{"forcergbx", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.RGBX; }},
{"syncfullscreen", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.syncFullscreen; }},
{"novrr", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noVRR; }},
{"immediate", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.tearing; }},
{"xray", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.xray; }},
{"nofollowmouse", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noFollowMouse; }},
{"noscreenshare", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noScreenShare; }},
};
const std::unordered_map<std::string, std::function<CWindowOverridableVar<Hyprlang::INT>*(const PHLWINDOW&)>> intWindowProperties = {
{"rounding", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.rounding; }},
{"bordersize", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.borderSize; }},
};
const std::unordered_map<std::string, std::function<CWindowOverridableVar<Hyprlang::FLOAT>*(PHLWINDOW)>> floatWindowProperties = {
{"roundingpower", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.roundingPower; }},
{"scrollmouse", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.scrollMouse; }},
{"scrolltouchpad", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.scrollTouchpad; }},
};
};
/**
format specification
- 'x', only address, equivalent of (uintpr_t)CWindow*
- 'm', with monitor id
- 'w', with workspace id
- 'c', with application class
*/
template <typename CharT>
struct std::formatter<PHLWINDOW, CharT> : std::formatter<CharT> {
bool formatAddressOnly = false;
bool formatWorkspace = false;
bool formatMonitor = false;
bool formatClass = false;
FORMAT_PARSE( //
FORMAT_FLAG('x', formatAddressOnly) //
FORMAT_FLAG('m', formatMonitor) //
FORMAT_FLAG('w', formatWorkspace) //
FORMAT_FLAG('c', formatClass),
PHLWINDOW)
template <typename FormatContext>
auto format(PHLWINDOW const& w, FormatContext& ctx) const {
auto&& out = ctx.out();
if (formatAddressOnly)
return std::format_to(out, "{:x}", rc<uintptr_t>(w.get()));
if (!w)
return std::format_to(out, "[Window nullptr]");
std::format_to(out, "[");
std::format_to(out, "Window {:x}: title: \"{}\"", rc<uintptr_t>(w.get()), w->m_title);
if (formatWorkspace)
std::format_to(out, ", workspace: {}", w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID);
if (formatMonitor)
std::format_to(out, ", monitor: {}", w->monitorID());
if (formatClass)
std::format_to(out, ", class: {}", w->m_class);
return std::format_to(out, "]");
}
};

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