1
0
mirror of https://github.com/hyprwm/Hyprland.git synced 2025-07-25 01:01:54 -07:00

Compare commits

...

756 Commits

Author SHA1 Message Date
Vaxry
31cc7f3b87 core: move workspace ptrs to weak ()
Fixes some race conditions that come up in tests. We only clean up workspaces when we render a frame. With this, they are always cleared instantly.
2025-07-24 00:36:29 +02:00
mavonarx
ecc04e8ba7 drm: check syncobj timeline support before advertising protocol ()
Prevents crashes on systems where DRM driver lacks syncobj timeline
support (e.g., Apple Silicon with Honeykrisp driver). Applications
like Zed and WezTerm would crash with 'Timeline failed importing'
when trying to use explicit sync.

Fixes  

---------

Co-authored-by: mvonarx <matthias.vonarx@sitrox.com>
2025-07-23 23:11:07 +02:00
Vaxry
c51c6e38ac tests: add a few more workspace tests 2025-07-23 20:41:38 +02:00
Florent Charpentier
55f2daa21e swipe: fix workspace swipe not rendering last frame if target ws is on edge ()
Fix for a weird behaviour that happens when swipe is only valid in 1
direction (i.e. from ws 1)

When you start a swipe from the only direction possible, then swipe back
(without releasing), the last frame where the delta is reset to 0 was
not being rendered
2025-07-23 20:06:28 +02:00
Nikolaos Karaolidis
2d2a5bebff core: fix maxwidth resolution mode ()
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-07-23 12:10:39 +02:00
Tom Englund
6ca7c14b58 CTM: check for finite value aswell ()
checking for < 0.F will not catch NaN or inf values, use std::isfinite
aswell.
2025-07-23 12:09:19 +02:00
Vaxry
fdbbad04bb core: enter unsafe state on boot if there are no mons 2025-07-22 11:14:12 +02:00
Karun Sandhu
873914a2a6 CI/Nix: also check for qt version in update script 2025-07-22 09:56:43 +03:00
00-KAMIDUKI
50758505d5 example: make screen shader example compatible with glsl 300 () () 2025-07-21 21:05:47 +02:00
Vaxry
462729d865 protocols/subcompositor: fix subsurface sorting () 2025-07-20 19:42:40 +02:00
Thomas Müller
bf1602d9f9 renderer: implement wp-color-management-v1 transfer functions () 2025-07-20 18:20:27 +02:00
MightyPlaza
d4de69381e internal: set value and goal for window size and position on setGroupCurrent () 2025-07-20 17:00:17 +02:00
MirzaSamadAhmedBaig
503fc458d8 internal: replace unsafe strcpy with snprintf () 2025-07-20 15:31:53 +02:00
Vaxry
a3d59b525b systeminfo: print more render info 2025-07-20 14:51:17 +02:00
Mozzarella32
b7a91e02e9 renderer: Add cursor:invisible to allow to hide the cursor () 2025-07-20 12:40:21 +02:00
Vaxry
58b6eceb6d sessionlock: fix flipped if condition 2025-07-20 12:33:22 +02:00
Vaxry
91d8a629eb sessionlock: fix timer logic on unsafe state 2025-07-19 16:48:20 +02:00
Vaxry
8b38353012 eventloop: improve timer handling to avoid crashes
ref 
2025-07-19 16:47:14 +02:00
Vaxry
3b04131259 eventloop: avoid duplicate timers 2025-07-19 13:31:37 +02:00
Vaxry
d84699d8e5 opengl: detect android fence support and disable explicit if it's missing ()
Checks for explicit sync support via the android fences, and falls back to implicit sync if it isn't
2025-07-19 12:38:41 +02:00
MrFantOlas
ae3cc48f22 protocols/gamma: support pipes ()
Add support for pipes and potentially other valid file descriptors. Add check for more data on the socket than the required amount as per protocol.

---------

Co-authored-by: Alexandre Teixeira <alexandre.teixeira@etu.emse.fr>
2025-07-18 23:20:17 +02:00
Radovenchyk
4adf658907 README: add link to CI from badge () 2025-07-18 21:13:56 +03:00
Mike Will
260a13a12f snap: use window extents instead of border size ()
* snap: use window extents instead of border size

`border_overlap` no longer does anything for window snapping, only monitor snapping.
2025-07-18 17:35:43 +02:00
vaxerski
088e8af955 [gha] Nix: update inputs 2025-07-18 10:11:00 +00:00
Vaxry
49abc193f7 framescheduler: check monitor validity in doLater 2025-07-18 12:09:43 +02:00
Vaxry
a05c797e4a compositor: properly set infinite region on null input
fixes 
2025-07-17 22:04:20 +02:00
Tom Englund
b46dc9ee0c framescheduler: dont if check deleted weakpointer ()
if m_monitor is destroyed the doOnReadable will eventually hit UB on
destruction if checking a destroyed m_monitor. acctually use the
captured mon weak pointer.
2025-07-17 21:59:20 +02:00
aphelei
75c0675e14 config: add better zoomFactor default () 2025-07-17 18:37:11 +02:00
Vaxry
49d73d1893 config: default drag_lock to 0 2025-07-16 22:39:42 +02:00
boundlessvoid0
409b56f6a3 hyprctl: make animations print details about bezier curves () () 2025-07-16 21:35:15 +02:00
Vaxry
148718b3bc socket2: fixup invalid ws passed to openwindow
fixes 
2025-07-16 18:22:54 +02:00
Vaxry
5349667992 master: add ignoremaster to swapwithmaster
fixes 
2025-07-16 15:51:11 +02:00
Vaxry
c4a4c34156 version: bump to 0.50.0 2025-07-16 11:33:42 +02:00
Vaxry
d4fbedcd35 core: never use hw cursors when tearing 2025-07-16 11:33:40 +02:00
Vaxry
5bfe6dc703 config: disable hw on mgpu nvidia by default () 2025-07-16 11:02:20 +02:00
Mike Will
8453fbf4eb snap: fix border_overlap option for monitor snapping () 2025-07-15 22:24:40 +02:00
UjinT34
e15014e031 protocols/cm: Fix preferred image description () 2025-07-15 19:33:14 +02:00
UjinT34
bc764f7065 protocols: Remove incorrect CM proto debug check and fix preferred image description () 2025-07-14 22:54:43 +02:00
Maximilian Seidler
06fcdbd9c7 renderer: use makeUnique for session-lock render passes ()
Fixup for a merge conflict introduced by .
2025-07-14 15:48:50 +02:00
Maximilian Seidler
01971cb6c7 session-lock: don't render workspaces when locked ()
Avoid rendering the workspace behind if we are locked
2025-07-14 13:13:54 +02:00
Vaxry
d0f58baf29 screencopy: ignore hidden windows in noscreenshare
fixes 
2025-07-12 18:22:47 +02:00
Vaxry
8bfff87833 debugOverlay: fix tick measurement 2025-07-12 18:19:59 +02:00
Vaxry
6821723b44 splashes: add zacoons' splash
winner of the 4th ricing comp
2025-07-11 23:47:48 +02:00
Tom Englund
e589adb00d config: remove render_ahead* config options
remove render_ahead* config options and descriptions. they are unusued.
2025-07-11 17:51:04 +02:00
Tom Englund
523eed048e xwl: dont mark the even source as readable
wl_event_source_check makes the event trigger until onx11event returns non
zero. but we arent removing the event source from the event loop so lets
not mark it at all and recieve spurious constant calls.
2025-07-11 17:51:04 +02:00
Tom Englund
e6bb809663 monintor: remove rathandler
is this even used? lets just remove it.
2025-07-11 17:51:04 +02:00
Tom Englund
b5433bb753 singlepixel: move to unique ptrs
less refcounting, move by rvalue.
2025-07-10 14:09:00 +02:00
Tom Englund
bcb96c5532 presentation: move to unique ptrs
less refcounting, move by rvalue.
2025-07-10 14:09:00 +02:00
Tom Englund
f22b5971d1 dmabuf: move to unique ptrs
less refcounting, move by rvalue.
2025-07-10 14:09:00 +02:00
Tom Englund
87653077f8 cursorshape: use unique ptrs
less refcounting.
2025-07-10 14:09:00 +02:00
Tom Englund
a21882be33 ctmcontrol: move to unique ptrs
less refcounting, move by rvalue.
2025-07-10 14:09:00 +02:00
Tom Englund
37be9a8959 alphamodifier: move to unique ptrs
less refcounting, move by rvalue.
2025-07-10 14:09:00 +02:00
Tom Englund
f5af40afce renderpass: use unique ptr instead of shared ptr
lets use unique ptrs instead of refcounting shared ptr when its not
needed, use rvalue reference to construct in vector directly.
2025-07-10 14:09:00 +02:00
Vaxry
6375e471f3 config: disable new_render_scheduling by default 2025-07-09 16:13:57 +02:00
Kamikadze
c6497a7193 internal: Prevent double-free in attemptDirectScanout () 2025-07-09 14:39:36 +02:00
FrancisTheCat
9517d0eaa4 renderer: Added a pointer position uniform to the screen shader. () 2025-07-08 19:31:15 +02:00
outfoxxed
78e9eddfb6 core: use new typed signals from hu () 2025-07-08 18:56:40 +02:00
Tom Englund
2f34ef141b compositor: fix race to finish on null buffer ()
if a null buffer is commited and a pending state is awaiting, drop the
pending state so we dont end up in race to finish brokeness.
2025-07-08 18:55:46 +02:00
Vaxry
8f948827a6 Renderer: Implement new render scheduling ()
Implements a new render scheduling method, where we triple buffer when necessary.

Enabled by default, improves FPS on underpowered devices.

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2025-07-08 12:41:10 +02:00
Tom Englund
9856563f89 opengl: avoid reallocations in EGLImage ()
use a std::array instead of vector and avoid reallocations.
it should at most be 49 entries, so make the array 50. and RASSERT check
it incase more entries gets added in the future.
2025-07-07 23:44:35 +02:00
Tom Englund
bb958a9e13 pass: overload TexPass constructor
overload it with a rvalue to allow us to move the data directly avoiding
an extra copy. because SRenderData is not trivially copyable.
2025-07-07 18:09:34 +02:00
Tom Englund
c75f85098c renderer: move render calculation behind if case
the durationUs is not used unless debug overlay is enabled, lets not
calculate it each call to rendermonitor if not enabled. same with
renderStart.
2025-07-07 18:09:34 +02:00
Tom Englund
4a30e2acd9 eventloop: RAII the even source on readable fd
RAII remove the event source and honor rule by 5
2025-07-07 18:09:34 +02:00
Tom Englund
ceec1943ff compositor: dont send around int max values
we know the buffersize use it instead of int max values that can
potentially cause math issues and slowdowns. minor improvement to
glmark2 aswell.
2025-07-07 18:09:34 +02:00
jmanc3
83c453cb82 plugins: made currentWindow available in RENDER_PRE_WINDOW () 2025-07-07 18:06:42 +02:00
Tom Englund
54369adffa internal: iso C++ prohibits anonymous structs ()
turn on -Wpedantic and name the anonymous struct.
2025-07-07 16:18:06 +02:00
vaxerski
d23ed852fc [gha] Nix: update inputs 2025-07-07 11:34:38 +00:00
Tom Englund
a16d0c76a6 texture: zero out the cached states in destroy ()
if destroyTexture is called outside of the texture destructor we need to
empty out the cached states.
2025-07-07 13:33:22 +02:00
Mihai Fufezan
6a5f4f5954 Nix: fix overlay application
Should fix 
2025-07-05 14:18:38 +03:00
MightyPlaza
b99c193e46 internal: handle setGroupCurrent properly on fs groups () 2025-07-05 00:16:25 +02:00
bobrat
9b51d73a1e hyprpm: print all dependencies that are missing () 2025-07-04 14:43:46 +02:00
Tiago Dinis
3c9447ca53 nix: update aquamarine 2025-07-04 01:33:31 +03:00
Mihai Fufezan
90c8609cbb CMake: disable tests by default () 2025-07-01 23:18:34 +02:00
Vaxry
b246f33ab1 inputmgr: remove unused var 2025-07-01 23:18:01 +02:00
aphelei
e9c5594186 renderer: add mouse zoom animations ()
Adds animations for the mouse zoom effect.
2025-07-01 11:33:48 +02:00
Tom Englund
e827b75e22 opengl: add missing skipcm if case ()
missing skipcm if case so the CM uniforms where never added on the
gradient2 renderBorder case, until the non gradient2 one had run atleast
once. causing it to not render on first launch/delayed.
2025-07-01 11:32:49 +02:00
Tom Englund
9adacef70b buffer: check if buffer fd already readable ()
check if buffer fd is already readable, to avoid a lot of unnecessery
systemcalls and churn.
2025-07-01 11:32:17 +02:00
Tom Englund
f464dfbefa shader: replace texture2d with texture ()
* shader: replace texture2d with texture

remove unused v_color and replace deprecated texture2d with texture.

* shader: use the more modern essl3 extension

GL_OES_EGL_image_external_essl3 provides support for samplerExternalOES
in texture function, aquamarine already use it. apply it here too.
2025-07-01 11:32:00 +02:00
Tom Englund
8c37d2ce25 sessionlock: restore cursor if hidden on unlock ()
if session locks have hidden the cursor its gonna be missing unless a
new cursor shape is set, hovering windows makes us get one, moving the
wallpaper/desktop does not. set it again to left_ptr as is default on
compositor start.
2025-07-01 11:31:10 +02:00
Karun Sandhu
ee8978b961 flake.lock: update 2025-06-29 19:29:36 +03:00
Vaxry
ab900d8752 screencopy: fix improper box calculations for transforms () 2025-06-28 17:01:14 +02:00
Vaxry
0fea173fc8 unbind: add unbind all 2025-06-28 14:55:13 +02:00
Mihai Fufezan
a01d20cfe8 CI/Nix: fix rebase oopsie 2025-06-27 16:56:52 +03:00
Vaxry
e4b6fedfb9 tester: simplify adding test files 2025-06-27 12:18:45 +02:00
Vaxry
1fc7e80bdb README: update previews 2025-06-27 12:06:21 +02:00
Mike Will
b850b35778 snap: move gapOffset logic outside of for loop () 2025-06-27 12:01:45 +02:00
Mihai Fufezan
2796ec1cf2 CI/Nix: separate xdph from hl 2025-06-26 23:38:23 +03:00
Vaxry
3d6476c902 Core: Add a test suite ()
Adds a test suite for testing hyprland's features with a runtime tester

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2025-06-26 19:43:39 +02:00
rafiq
9a67e0421b renderer: clamp rounding_power () 2025-06-26 19:26:46 +02:00
outfoxxed
3bbdf9dc5a protocols: add ext-workspace implementation () 2025-06-26 18:32:44 +02:00
sam
1f337a7a5e hyprctl: replace read-only strings with std::string_view () 2025-06-26 12:28:35 +02:00
UjinT34
452a158107 config: use parseScale for monitorv2 () 2025-06-26 12:28:21 +02:00
Tom Englund
f4f090e4b2 renderer: reduce a lot of glcalls and cache various states ()
* opengl: cache viewport state

according to nvidia docs calling glViewPort unnecessarily on the same
already set viewport is wasteful and can cause state changes when not
needed. cache it in a struct and only call it when the viewport is
actually changing.

* opengl: cache glenable/gldisable state

avoid making multiple glenable/gldisable calls on already set caps, can
cause state changes and incur driver overhead.

* opengl: cache glscissor box

only call glscissor if the box actually has changed, try to avoid state
changes.

* opengl: cache gluniform calls

cache the gluniform calls, the uniform values are cached in driver per
program only the drawcalls setting the uniform yet again with the same
value on same location is causing more overhead then caching it ourself
and just no oping on it if no changes.

* shader: rewrite handling of uniforms and state

this is way faster as we don't need to mess with maps (hashing, etc) and instead can just use an array

* opengl: stuff and 300 shaders

* opengl: typo

* opengl: get the uniform locations properly

now that the legacy shaders are gone get the uniformlocations for
SKIP_CM etc, so they can be properly set and used depending on if
cm_enabled is set to false or true, before it was falling back to a
legacy shader that didnt even have those uniforms.

* opengl: check epsilon on float and remove extra glcall

seems an extra unset glcall was added, remove it. and check the float
epsilon on the glfloat.

* opengl: remove instanced shader draw

remove the instanced boolean from the vertex shader, might be neglible
differences, needs more benchmark/work to see if its even worth it.

* texture: cache texture paramaters

parameters where occasionally set twice or more on same texture, short
version wrap it and cache it. and move gpu churn to cpu churn.

add a bind/unbind to texture aswell.

* texture: use fast std::array caching

cache the texparameter values in fast array lookups
and incase we dont want it cached, apply it anyways.

* shader: fix typo and hdr typo

actually use Matrix4x2fv in the 4x2fv cache function, and send the
proper float array for hdr.

* texture: make caching not linear lookup

make caching of texture params not linear.

* minor style changes

* opengl: revert drawarrays

revert the mostly code style reduce loc change of drawarrays, and focus
on the caching. its a if else case going wrong here breaking
blur/contrast amongst others drawing.

---------

Co-authored-by: Vaxry <vaxry@vaxry.net>
2025-06-25 12:42:32 +02:00
Mihai Fufezan
5a348fb7df Nix: filter src using fileset
Allows reusing already-built derivation when changing files outside the
ones defined in the fileset.
2025-06-24 21:39:42 +03:00
Vaxry
aea8132001 buffer: don't use crazy listener::emit() 2025-06-24 15:23:53 +02:00
UjinT34
cf7e3aa448 renderer/cm: Add automatic hdr () 2025-06-23 14:33:09 +02:00
Vaxry
c7c8ca475b config: add missing description for enforce_permissions 2025-06-23 13:56:02 +02:00
Vaxry
24e5f9974d hyprctl: print no open windows instead of invalid request on empty clients 2025-06-23 13:49:30 +02:00
Vaxry
dd33128c2f input: fix mouseDown triggering hl ops on locked () 2025-06-22 12:49:13 +02:00
zacoons
8b1d5560cf renderer: add wrapping options to renderTextureWithBlur method () 2025-06-21 19:03:28 +02:00
vaxerski
2388874738 [gha] Nix: update inputs 2025-06-21 14:22:28 +00:00
Vladimir-csp
4be32dbff4 xwayland: Don't leave shell process () 2025-06-21 16:21:08 +02:00
fufexan
8ebff1948f [gha] build man pages 2025-06-19 22:49:42 +00:00
Mihai Fufezan
a301d54df8 treewide: hyprland.org -> hypr.land 2025-06-20 01:49:20 +03:00
Mihai Fufezan
ff2f85641a CI/Nix: add cache-nix-action
Use nixbuild/nix-quick-install-action which pairs well with
nix-community/cache-nix-action.

Should help with build times by reducing the number of packages needing
to be re-downloaded on each run.

Parameters are taken from https://github.com/nix-community/cache-nix-action
and may be tweaked later.
2025-06-20 01:37:59 +03:00
Jasson
b49d0ca20e xwayland: Fix crash when copying from wayland to xwayland () 2025-06-19 19:44:38 +02:00
Vaxry
86b5e3bfbc config: nuke explicit_sync settings
were not used anymore, explicit is on by default
2025-06-19 14:58:03 +02:00
Vaxry
54ccf9c6b3 renderer: make lock fail textures dynamically loaded
this should reduce idle vram usage by a whopping 16MB, but also might fix the tty unknown issue.
2025-06-19 13:46:42 +02:00
Vaxry
e999ad664d hookSystem: avoid using manual mem management, fix leak
fixes 
2025-06-19 11:58:12 +02:00
7mile
9fb6b5d96b input: Fix incorrect localcoords with a surface above an XWayland window () 2025-06-18 22:48:51 +02:00
Vaxry
0fb63c68e9 permissions: properly print config requests for plugins 2025-06-18 22:43:04 +02:00
Vaxry
83a4c61048 plugins: don't update config plugins on state unchanged
fixes 
2025-06-18 22:42:57 +02:00
Jasson
bef1321f00 xwayland: fix minor errors in previous refactor () 2025-06-18 10:16:22 +02:00
Jacob Ilias Komissar
0ece4af36a grpupbar: Add config options to color inactive and locked groupbar titles () 2025-06-16 22:40:38 +02:00
Vaxry
aba2cfe7a8 asyncDialogBox: lock box in fdWrite to prevent a uaf () 2025-06-16 17:02:08 +02:00
Vaxry
d4e8a44087 windowrules/move: clamp max pos in onscreen to avoid assert crash
fixes 
2025-06-16 13:43:06 +02:00
Jasson
1905c41c65 xwayland: Use RAII instead or freeing memory manually ()
As suggested by clang-tidy
2025-06-16 13:31:46 +02:00
UjinT34
bd5703d5c6 protocols/cm: fix wp invalid luminance check () 2025-06-15 23:13:57 +02:00
Vaxry
d037c54260 protocols: support xdg-shell v7
there's nothing special we need to add for this rev
2025-06-15 12:21:16 +02:00
UjinT34
c3894d9288 config/monitor: Add monitor v2 HDR rules () 2025-06-15 12:15:18 +02:00
Vaxry
3db3baa19e opengl: use a stack for storing monitor transform enabled
fixes 
2025-06-15 12:11:28 +02:00
Vaxry
57d20a1bf6 internal: clean up dead snapshot code 2025-06-15 11:51:27 +02:00
Vaxry
472b52bc06 cursor: reset hc data after theme change
theme change invalidates the cairo surfaces there

fixes 
2025-06-15 11:47:10 +02:00
vaxerski
79b9edb85b [gha] Nix: update inputs 2025-06-15 09:46:31 +00:00
may
f08167c877 input: add sticky option for drag_lock ()
* allow configuring the sticky option for `drag_lock`

* enable sticky drag_lock by default as recommended by libinput

recommended here:
https://lists.freedesktop.org/archives/wayland-devel/2024-November/043860.html
2025-06-15 11:45:06 +02:00
Joel-Valenciano
ad85406220 drm-lease: Add Multi-GPU Support () 2025-06-13 15:17:32 +02:00
Otto Modinos
d14f81e6ac protocols: whitelist wp_color_manager_v1 for security_context ()
Now that `wine` (and `proton`) supports Wayland it makes sense to allow the `wp_color_manager_v1` in Flatpak for native HDR without the need for `gamescope`!
2025-06-13 00:27:30 +02:00
linfindel
826061d166 README: Update image cdn () 2025-06-12 23:22:52 +02:00
Vaxry
748419faa5 hyprpm: check version and update automatically on add ()
ideally should let the user know, but jic also checks for header updates
2025-06-12 13:40:55 +02:00
Vaxry
412c7dc7f7 renderer: fixup some missing fadeout cases with special
fixes some fadeout missing cases:

- closing last window
- closing above fs
- closing in general

fixes 
2025-06-11 17:52:23 +02:00
Vaxry
8329de1ab5 input: grab the correct active workspace on mouseMove
fixes 
2025-06-11 17:09:39 +02:00
Vaxry
144885d89f anr: make dialog disappear if the app dies
fixes 
2025-06-11 17:00:16 +02:00
Vaxry
f7526d6be0 renderer: refuse rendering invalid resolutions
sometimes a driver fails to assign any reasonable mode in which case we might render 0x0 which will make us crash. Don't do that. Part 1 of 
2025-06-11 16:57:07 +02:00
Vaxry
6910ca76bd protocols/subcompositor: fixup place_above and _below
fixes 
2025-06-11 16:54:47 +02:00
Viktor
6bdb1f413e dwindle: add the ability to specify an aspect ratio for a singular window () 2025-06-10 08:20:31 +01:00
ilusha420
81468253ea hyprpm: fix typo in help message () 2025-06-09 14:31:04 +03:00
Luuk Blankenstijn
231e01e39b hyprctl: don't detect a negative value as a parameter () 2025-06-08 20:17:38 +01:00
Kamikadze
c6f713fefe screencopy: fix incorrect noscreenshare positions with monitor scaling () 2025-06-08 08:19:23 +01:00
Kamikadze
91967f8ec0 renderer: fix incorrect cursor position when screencopy region with monitor scaling () 2025-06-08 08:18:42 +01:00
Kamikadze
0a47575c7f internal: Use using instead of #define to alias smart pointers () 2025-06-08 08:13:56 +01:00
vaxerski
8801770981 [gha] Nix: update inputs 2025-06-07 20:14:08 +00:00
Kamikadze
66b99bd277 monitor: ensure autoDir is applied when changed () 2025-06-07 21:12:43 +01:00
sam
2794f485cb hyprctl: Remove exceptions, use modern error handling () 2025-06-06 20:23:33 +01:00
Ufuk Ustali
0ac3bef724 input: support configuring drag_3fg from libinput () 2025-06-06 15:47:15 +01:00
vaxerski
456c820d52 assets: update header 2025-06-06 15:14:17 +02:00
UjinT34
c35c2fea40 config: Restore auto-center-* for monitors () 2025-06-06 08:01:19 +01:00
Friday
d6fbd89336 nix: use gcc15-built dependencies 2025-06-06 08:07:34 +03:00
XPhyro
fb7548cb41 screencopy: fix applying noscreenshare to invisible special workspaces () 2025-06-05 21:29:01 +01:00
Eric Li
423b69f5d3 config: add group: selector () 2025-06-05 21:17:04 +01:00
UjinT34
abdfc5ea40 config: add a new monitor v2 config syntax () 2025-06-05 15:56:46 +01:00
sam
59c886d855 internal: Catch filesystem exceptions while iterating RunTimeDir () 2025-06-05 15:19:54 +01:00
Jasson
d7a87ce6e2 xwayland: fix xwayland -> wayland clipboard () 2025-06-04 16:00:55 +01:00
vaxerski
d9f7448d82 xwayland: pad pid with leading zeroes in lockfile
fixes 
2025-06-04 16:54:12 +02:00
littleblack111
b5c0d0b8aa keybinds: add an option to respect gaps out for floating to movewindow () 2025-06-03 19:48:56 +01:00
sam
b1d0a727cc internal: Center window on parent if available ()
Fixes 
2025-06-02 19:22:51 +01:00
Kamikadze
ef2c73af80 internal: embed example config () 2025-06-02 18:36:44 +01:00
Kamikadze
16c62a6dbb internal: Fix HyprError not displaying at startup () 2025-06-01 21:03:53 +01:00
Jasson
2d1c6f88d2 xwm: Refactored functions in XWM.cpp ()
* Refactored SXSelection::onSelection in XWM.cpp

- Made the function more readable and less redundant
- Extracted repeated conditions into booleans.
- Reduced nested conditionals
- Reused (conn) pointer

* Refectd readProp

* Refactor initSelection
2025-06-01 21:02:17 +01:00
vaxerski
82b8549542 hyprpm: refuse adding a new repo without update 2025-06-01 21:53:30 +02:00
Kamikadze
69c2b2926e internal: refactor to use empty() () 2025-05-31 19:49:50 +01:00
Kamikadze
4078e1d17c refactor: replace all typedef with using () 2025-05-31 14:02:02 +01:00
mitsuru
af2fdb5d58 nix: use gcc15
resolves Nix build/CI failures introduced in 9190443
bumps flake.lock as gcc15Stdenv wasn't available at the pinned version
of nixpkgs
2025-05-31 01:45:34 +03:00
Kamikadze
9190443d95 refactor: use std::ranges whenever possible () 2025-05-30 14:25:59 +01:00
littleblack111
9bf1b49144 snap: add option to respect gaps () 2025-05-28 14:20:03 +01:00
vaxerski
5cc6cb4945 groupbar: force recalc on visibility changes
fixes 
2025-05-28 15:18:30 +02:00
vaxerski
9b327ddfd1 monitor: mark 0, 0 presentation timestamps as invalid
fixes 
2025-05-27 21:26:47 +02:00
Kamikadze
24915a3a9b windowrules: Add noscreenshare () 2025-05-27 16:10:22 +01:00
Nikolaos Karaolidis
90d0b8ecae core: add auto-center arrangements ()
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-05-27 15:51:59 +01:00
littleblack111
ddb9f8394d config: fix inconsistant hint of default value ()
similar to https://github.com/hyprwm/hyprland-wiki/pull/1093
2025-05-27 15:50:00 +01:00
littleblack111
a62ccb169a config: fix crash on misnamed variable () 2025-05-27 08:33:17 +01:00
Vaxry
be6ee6e55f cmake: disable gprof by default 2025-05-26 23:33:44 +02:00
Nikolaos Karaolidis
c2805aad92 config: add maxwidth monitor resolution mode ()
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-05-26 19:25:58 +02:00
littleblack111
4c4c9bb324 dwindle: add better automatic window drag and drop direction detection () 2025-05-26 19:15:11 +02:00
Vaxry
292a7456af eventLoop: fixup headers 2025-05-26 16:53:35 +02:00
Vaxry
2347050285 pass/surface: make sure popup blurs are marked for require live blur
fixes 
2025-05-25 18:48:32 +02:00
Vaxry
a58ab20e8b debug/pass: show live/precompile blur in debug 2025-05-25 18:45:28 +02:00
Vladimir-csp
cc0792c1dc hyprland-uwsm.desktop: Add TryExec
This should hide this entry when uwsm is missing (at least in DMs that handle TryExec)
2025-05-25 14:55:33 +03:00
vaxerski
28c9122adb [gha] Nix: update inputs 2025-05-24 18:41:03 +00:00
Vaxry
55076edaac versionkeeper: don't pop up on initial launch 2025-05-24 20:39:36 +02:00
Virt
81cd526f92 cursor: fix screencopy cursor pos and duplicate shape with sw cursors ()
* cursor: account for hotspot with overridePos

* cursor: don't draw cursor on screencopy if using sw anyways
2025-05-23 23:41:35 +02:00
Mihai Fufezan
bd4733a0ff flake.lock: update
nix/overlays: remove wayland-protocols overlay. PR landed in master a while ago
2025-05-22 18:02:20 +03:00
nezu
4f161da3d6 hyprpm: ignore pins when adding a package with a git rev ()
ref 
2025-05-22 13:54:02 +02:00
darkwater
185c96849e input: unhide cursor on tablet events after touch events () 2025-05-21 23:44:21 +02:00
zacoons
b90910c0dc renderer: add wrapping options to renderTexture method () 2025-05-21 16:41:40 +01:00
Vaxry
eb3b38d40b eventLoop: fixup event source callbacks 2025-05-19 01:27:30 +02:00
Vaxry
d9c8a37811 input: always allow focus to permission popups 2025-05-18 19:34:20 +02:00
Vaxry
158c0f2911 permissions: add permission management for keyboards () 2025-05-18 19:13:20 +02:00
zacoons
44cb8f769e internal: added error log when getEdgeDefinedPoint is impossible () 2025-05-18 19:10:06 +02:00
Vaxry
705b97c4ac input: revert and
fixes 
2025-05-17 19:43:12 +02:00
Vaxry
c19f383685 hyprpm: fix crash with enable without an arg 2025-05-17 19:07:18 +02:00
Vaxry
bb5cd5b2dd screencopy: store a fb before permission popup if the permission is pending ()
stops rendering the permission popup on stuff like grim when it asks
2025-05-17 19:03:35 +02:00
Vaxry
bb9aa79b21 hyprpm: reject remove without a param
ref 
2025-05-17 18:10:35 +02:00
Vaxry
dfa4836216 hyprpm: fix execute permission bit on installed dirs () 2025-05-17 18:08:42 +02:00
vaxerski
18377d221d [gha] Nix: update inputs 2025-05-17 11:08:13 +00:00
outfoxxed
2aa21625bd input: ensure seat grabs from exclusive layers can be dismissed () 2025-05-17 13:06:48 +02:00
outfoxxed
2946009006 input: do not send mouse events when outside of a surface () 2025-05-16 23:39:28 +02:00
outfoxxed
b0cc49218d protocols: simulate mouse movement after activating a toplevel () 2025-05-16 23:38:45 +02:00
Zach DeCook
a5c9b3e490 core: Include cstring whenever strncpy is used ()
Fixes ppc64le build in alpine
2025-05-15 10:31:44 +01:00
outfoxxed
dfb841c303 desktop: prevent layers from dismissing their own seat grabs on map () 2025-05-15 10:16:03 +01:00
Tom Englund
5ceb0ec15d core: drop the legacy renderer ()
* core: drop the legacy renderer

the legacy renderer is broken and barely used, drop it.

* Nix: drop support for legacyRenderer

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2025-05-15 10:13:24 +01:00
outfoxxed
f707d86912 protocols/hyprland-surface: account for scaled monitor positions () 2025-05-15 10:12:55 +01:00
Yukari Chiba
75f2cb5f65 xwayland: do not include xcb.h when xwayland is disabled ()
xcb.h should not be included when xwayland is disabled. 
This allows hyprland to not use X11 libraries at all when xwayland is disabled.
2025-05-14 19:31:19 +01:00
Vaxry
a51e639d81 input: disallow virtual keyboards from changing LED state () 2025-05-14 17:48:17 +01:00
Tom Englund
59b2340680 opengl: add missing vao for screenshader ()
missed creating vertex array objects in 04124988e8
add it.
2025-05-13 23:46:29 +01:00
outfoxxed
da3583fd5e opengl: publicize shader creation/usage functions ()
Allows plugins to create and use shaders again
2025-05-12 14:15:47 +02:00
Tom Englund
04124988e8 opengl: optimize shaders and reduce unneeded drawcalls ()
* opengl: remove unnecessery glflush calls

glflushing forces the driver to break batching and issue commands
prematurely and prevents optimisations like command reordering and
merging.

many glFunctions already internally glflushes and eglsync creation still
has a glflush at end render. so lets reduce the overhead of these calls.

* opengl: reduce glUseProgram calls

apitrace shows cases where the same program gets called multiple times,
add a helper function that keeps track of current program and only call
it once on same program. reduces slight overhead.

* opengl: use more efficient vertex array object

use a more modern vertex array object approach with the shaders, makes
it a onetime setup on shader creation instead of once per drawcall, also
should make the driver not have to revalidate the vertex format on each
call.
2025-05-11 18:36:20 +02:00
Vaxry
390a357859 renderer: use alpha for the lockttytext texture
ref 
2025-05-11 13:15:03 +01:00
Vaxry
9a87498bb1 renderer: minor damage fixes 2025-05-10 23:53:05 +01:00
Vaxry
f58bb72d3a renderer: render blur on fade out () 2025-05-10 19:31:26 +02:00
vaxerski
60cd5b7a48 renderer: always render snapshots as 8bit
fixes issues with transparent windows on 10b
2025-05-09 22:16:21 +01:00
Florian "sp1rit
25cf06f6cf build: require hyprgraphics>=0.1.3 ()
49974d5 introduced use of types, which were only added in 0.1.3
2025-05-09 14:47:28 +02:00
Jan Beich
e44aae0c20 hyprpm: switch to numeric owner/group after f8bbe5124c ()
On BSDs "root" is in "wheel" group. Instead of enumerating platforms
or probing "wheel" explicitly use numeric value for the superuser.

$ truss hyprpm add <url>
[...]
read(5,"install: unknown group root\n",1023)     = 28 (0x1c)
[...]
[ERR] ✖ Failed to write plugin state
2025-05-09 14:18:15 +02:00
Jan Beich
fcb6f936ea hyprpm: add missing include for libc++ after 1c530cbc66 ()
hyprpm/src/helpers/Sys.cpp:24:24: error: implicit instantiation of undefined temp
late 'std::basic_ostringstream<char>'
   24 |     std::ostringstream oss;
      |                        ^
/usr/include/c++/v1/__fwd/sstream.h:27:28: note: template is declared here
   27 | class _LIBCPP_TEMPLATE_VIS basic_ostringstream;
      |                            ^
2025-05-09 03:42:19 +02:00
Vaxry
9958d29764 version: bump to 0.49.0 2025-05-08 21:15:18 +01:00
Vaxry
239cdd67fd socket2: fix order of window events on map () 2025-05-08 22:10:31 +02:00
s1dd
f01e3043b8 desktop: cleanup code and use std::ranges () 2025-05-08 21:01:49 +02:00
Vaxry
04c98abd1f layout: properly assign workspace and monitor when moving a child to the parent () 2025-05-08 21:00:28 +02:00
nyx
53bfb92d65 layout: allow interacting with pinned windows when fullscreened ()
* layout: allow interacting with pinned windows when fullscreened

* IHyprLayout: format code

* e
2025-05-08 20:59:51 +02:00
Mihai Fufezan
2b3cac018e flake.lock: update 2025-05-08 20:37:36 +03:00
Vaxry
f909b0f114 opengl: fix legacyrenderer 2025-05-08 18:22:44 +01:00
MightyPlaza
fa1e343b04 compositor: set fullscreenstate on movetoworkspace ()
modified:   src/managers/KeybindManager.cpp
2025-05-08 18:29:47 +02:00
davc0n
22b12e3013 refactor: cshader class to sshader struct () 2025-05-08 00:07:35 +02:00
Mihai Fufezan
7a971735af flake.lock: update 2025-05-07 21:51:59 +03:00
Tom Englund
6abb5b0c7e renderer: precompute fullalpha ()
precompute it once instead of calculating it every single call, was showing
up as a lot of time wasted in profiling.
2025-05-07 19:02:07 +02:00
Tom Englund
0dfcba9825 DMABuffer: reserve vector and avoid UB ()
actually reserve the vector instead of initializing it with the
m_attrs.fd.size() adding 4 invalid fd entries, and later emplace_back
the valid ones.

sync_merge_data name is defined as char name[32] a fixed size array, and
c++ technically doesnt allow assigning string literals directly to array
fields in aggregate initializers, it may compile but is technically
undefined behaviour or ill formed. zero initalise it and use
std::ranges::copy_n instead.
2025-05-07 18:15:27 +02:00
davc0n
e5df8cdc62 xwayland: refactor class member vars ()
* xwayland: refactor class member vars

* xwayland: fix pure wayland build
2025-05-07 15:21:44 +02:00
Vaxry
f8bbe5124c hyprpm: clean up root access and properly check input ()
* manifest: reject bad names from parsing

* sys: restructure root functions
2025-05-06 22:43:08 +02:00
Vaxry
948277895e popup: damage old size on unmap as well as new ()
fixes 

chromium for some reason sets the popup size to like 8x8 at the same time as unmapping
2025-05-06 21:49:20 +02:00
Zebra2711
708a7c24ef hyprpm: add missing return ()
Add a missing return statement after handling the first superuser binary in the `dropSudo` function

Fixes: 1c530cb

Co-authored-by: Zebra2711 <zebra2711@users.noreply.github.com>
2025-05-06 14:53:28 +02:00
CyrenArkade
1ce614dfc0 animations: Add option for animating workspaces as if the first and last were adjacent ()
* add option for animating workspaces as if the first and last were adjacent

* change wraparound detection to use IDs instead of dispatcher

* move shouldWraparound from MiscFunctions to Monitor
2025-05-06 03:54:27 +02:00
Jack Barnes
930eeac900 window: use stored size for new floating window when persistentsize is set ()
* fix(window): use stored size for new floating window when persistentsize is set. fix hyprwm#9422.

* fix: replace `std::any_of` with `std::ranges:any_of`

* fix: use initialClass and initialTitle when storing sizes on close

* fix: add `xdgTag` as a new indicator

* fix: no {}

* fix: format with clang-format
2025-05-06 03:53:43 +02:00
shane blackthorne
ec93f8a1cd socket2: add monitorremovedv2 event () 2025-05-06 03:52:36 +02:00
davc0n
997fefbc11 render: refactor class member vars ()
* render: refactor class member vars

* render: fix clang format
2025-05-05 23:44:49 +02:00
Vaxry
c7eb141098 renderer: always try to apply a mode if pixel size is invalid () 2025-05-05 23:40:37 +02:00
Vaxry
1f50cdfa8b hyprpm: wrap sudo cmd in quotes
ref 
2025-05-05 13:09:00 +01:00
s1dd
1c530cbc66 hyprpm: Minor optimizations and refactor of helpers and progress bar ()
* hyprpm: optimize sys.cpp

* hyprpm: refine progress bar logic

* chore: fix fetchSuperuserBins

* chore: modify one line if/else statements

* chore: fix if/else statements

* chore: follow naming convention for const vars

* chore: revert shell escape logic
2025-05-05 04:22:22 +02:00
davc0n
9cd5b25745 protocols: refactor class member vars (u-z) ()
* protocols: refactor class member vars (u-z)

* protocols: fix clang format
2025-05-04 23:39:00 +02:00
phonetic112
78ff20ddf0 workspaces: Fix empty flag not selecting active workspace () 2025-05-04 19:22:41 +02:00
davc0n
2626f89ea6 protocols: refactor class member vars (n-t) () 2025-05-04 19:21:36 +02:00
davc0n
adbae0f74d protocols: refactor class member vars (a-m) () 2025-05-04 00:13:29 +02:00
davc0n
46ac115bd1 protocols: refactor class member vars (types) () 2025-05-03 18:54:50 +02:00
Honkazel
3465efcdc1 internal: Use vecnotinrect instead of !vecinrect ()
* monitor: use vecnotinrect

* inputmanager: vecnotinrect

* clang format
2025-05-03 18:54:15 +02:00
Virt
cdf5736f1a layerrules: fix abovelock interactivity for touch input () 2025-05-03 16:07:02 +02:00
davc0n
2d6ca96e07 plugins: refactor class member vars () 2025-05-03 16:06:24 +02:00
davc0n
d9cad5e1b6 protocols: refactor class member vars (core) () 2025-05-03 16:02:49 +02:00
Vaxry
0c736217a7 configmgr: fix CConfigValue<> from plugins 2025-05-02 17:53:55 +01:00
Vaxry
77ecf09506 internal: fix name confusion in SAlphaValue
fixes 
2025-05-02 16:29:15 +01:00
vaxerski
f1ac1847ff [gha] Nix: update inputs 2025-05-02 15:08:46 +00:00
davc0n
ce821294e2 managers: refactor class member vars () 2025-05-02 17:07:20 +02:00
Vaxry
6f174a9e08 renderer: render fading out floating windows over fs 2025-05-02 00:16:17 +01:00
davc0n
5b3e489108 inputs: refactor class member vars () 2025-05-01 23:57:11 +02:00
Jan Beich
2670b8f772 hyprpm: add missing include for libc++ after 858c0e26d1 ()
hyprpm/src/core/DataState.cpp:80:23: error: implicit instantiation of undefined template 'std::basic_stringstream<char>'
   80 |     std::stringstream ss;
      |                       ^
hyprpm/src/core/DataState.cpp:149:23: error: implicit instantiation of undefined template 'std::basic_stringstream<char>'
  149 |     std::stringstream ss;
      |                       ^
hyprpm/src/core/DataState.cpp:229:31: error: implicit instantiation of undefined template 'std::basic_stringstream<char>'
  229 |             std::stringstream ss;
      |                               ^
/usr/include/c++/v1/__fwd/sstream.h:30:28: note: template is declared here
   30 | class _LIBCPP_TEMPLATE_VIS basic_stringstream;
      |                            ^
2025-05-01 20:48:13 +02:00
Vaxry
858c0e26d1 hyprpm: move to system directories for storing plugins () 2025-05-01 18:00:26 +02:00
Zetta1 Reid0
b5ef049ea1 master: Change center_master_slaves_on_right to center_master_fallback ()
* Change center_master_slaves_on_right  to center_master_fallback
2025-05-01 13:29:06 +02:00
davc0n
615e0dae46 layouts: refactor class member vars () 2025-05-01 13:27:07 +02:00
davc0n
ce4766772d hyprerror: refactor class member vars () 2025-05-01 13:26:43 +02:00
Vaxry
8d6618104e cmake: ignore Wclobbered
fails build otherwise
2025-04-30 23:06:40 +01:00
davc0n
50e1bec85f helpers: refactor class member vars () 2025-04-30 23:45:20 +02:00
Vaxry
b8a204c21d ci: minor fixes to glaze 2025-04-30 22:43:37 +01:00
Ikalco
2ee5118d7a render: properly release rendered buffers ()
* cleanup eglSync

* properly release buffers in renderer

* add renderingDoneCallback and use it in screencopy

* use static constructor for CEGLSync
2025-04-30 18:35:25 +02:00
Vaxry
5d005f11fa xdg-bell: fix wrong resource cast 2025-04-30 14:07:23 +01:00
Vaxry
54c89104de DonationNag: ask after each major update ()
This changes how the donation nag timing works.

The donation nag will now appear:

- after a major update (e.g. 48 -> 49)*
- once in late july
- once in december

however, a donation nag will never pop up more than once a month. So, if there is an update on the 26th of November, and you get a popup on the 28th, you will not get one in december.

This is of course still disableable in your config.
2025-04-30 14:47:35 +02:00
Mihai Fufezan
b2ad21a65c flake.lock: update 2025-04-30 09:26:58 +03:00
Vaxry
72cb5d24b6 permissions: disable automatic reloading of permissions from cfg
security reasons, avoid reading them live. Ideally we'd lock them behind sudo, but we can't do that.
2025-04-30 00:35:32 +01:00
Vaxry
9868b18378 input: don't use pointer hold logic for unmapped surfs
fixes 
2025-04-30 00:31:21 +01:00
Sander
208f4c48db config: use natural increase and decrease of brightness for default cfg ()
* feat(config): hyprland natural brightness

* feat(config): natural increase and decrease of brightness
2025-04-30 01:10:47 +02:00
Vaxry
4506871310 xdg-bell: avoid crashes on null toplevel 2025-04-30 00:10:07 +01:00
Vaxry
6483f4ec22 screencopy: don't render cursor when frame doesn't want it 2025-04-29 22:02:55 +01:00
UjinT34
ae1fe860ff renderer: add render:send_content_type setting () 2025-04-29 22:09:14 +02:00
UjinT34
49974d5e34 cm: Use precomputed primaries conversion () 2025-04-29 21:29:40 +02:00
Vaxry
94bc132084 xdg-bell/xdg-tag: fix moved resource usage 2025-04-29 19:49:13 +01:00
davc0n
e9c3fcbb64 devices: refactor class member vars () 2025-04-29 19:51:07 +02:00
Vaxry
40147d3a3f asyncdialogbox: fix missing header 2025-04-29 18:37:21 +01:00
Vaxry
23ecce0e7a protocols: add support for xdg-system-bell-v1 2025-04-29 18:32:21 +01:00
Vaxry
465e3d979d window: make AsyncDialogBoxes not closeable
we don't want the user to accidentally close a popup for permissions or ANR. They can dismiss them by clicking an appropriate option.
2025-04-29 18:20:06 +01:00
Vaxry
b10a43dabc windowrules: add noclosefor
fixes 
2025-04-29 18:14:02 +01:00
Vaxry
5bd7ff884d permissions: add perms for plugin loading ()
Adds permission management for loading plugins

---------

Co-authored-by: Jan Beich <jbeich@FreeBSD.org>
2025-04-29 18:59:43 +02:00
davc0n
2118440488 windows: refactor class member vars () 2025-04-28 22:25:22 +02:00
Vaxry
c505eb55ff screencopy: support hw cursors while sharing with cursor 2025-04-28 20:18:02 +01:00
Vaxry
f5c5cfa960 keybindmgr: fixup bindn regression
fixes 
2025-04-28 00:18:51 +01:00
Vaxry
0302bfdc22 async: add Promise and use it for AsyncDialogBox 2025-04-27 13:28:06 +01:00
Vaxry
4f868a1f3c SECURITY: init security policy
fixes 
2025-04-27 00:07:00 +02:00
nyx
94c55fe909 helpers: properly support next/prev for workspace switching () 2025-04-26 23:52:07 +02:00
Vaxry
742bce016c decorationPositioner: update posinfo on window update
fixes 
2025-04-25 16:09:11 +01:00
Virt
4cf62c114e layerrules: add abovelock to render above lockscreen () 2025-04-25 16:38:31 +02:00
Jason
41f5f67f6c window: Fix order of urgency flag and event emission () 2025-04-25 02:37:49 +02:00
davc0n
02d7badd15 workspaces: refactor class member vars () 2025-04-25 02:37:12 +02:00
davc0n
0e80ecc534 layers: refactor class member vars ()
* layers: refactor class member vars

* popups: rename m_WLSurface to m_wlSurface
2025-04-24 20:49:49 +02:00
M Matthew Hydock
be6268a7ec groupbar: Add options for setting group bar title font weight (and indicator gap) () 2025-04-24 20:48:08 +02:00
WhySoBad
a9549dbca0 protocols: add Hyprland toplevel mapping implementation () 2025-04-24 18:10:57 +02:00
Vaxry
b06fbdb743 dwindle: use idealIgnoreReserved for moveWindowTo bbs
fixes 
2025-04-23 22:31:14 +01:00
davc0n
241a4935a2 compositor: refactor class member vars () 2025-04-22 15:23:29 +02:00
Mihai Fufezan
3577a6be31 nix/overlays: add w-p 1.43 2025-04-22 11:03:27 +03:00
Mihai Fufezan
2e540e4ec4 flake.lock: update 2025-04-22 11:00:49 +03:00
Vaxry
a4f7d7c594 protocols: add xdg_toplevel_tag_v1 support
Adds a new windowrule to target windows by xdgTag, xdgtag:
2025-04-21 22:30:27 +01:00
nyx
55e953b383 InputManager: add nofollowmouse ()
* InputManager: add nofollowmouse

with this, focus_follows_mouse=1 acts like focus_follows_mouse=2 on the specific windows defined by the user

* e

* e

biggest e of all time
2025-04-21 20:48:27 +02:00
nyx
d29723cb76 keybinds: allow executing binds not bound to a key () 2025-04-21 20:47:14 +02:00
davc0n
400dd16072 debug: refactor class member vars () 2025-04-21 20:42:02 +02:00
Andrei V
a3b96961a2 tablet: naive window refocusing ()
Signed-off-by: Andrei V <andrei@ptaxa.net>
2025-04-21 00:32:31 +02:00
Vaxry
a3d32f3b70 hyprpm: fix format 2025-04-20 23:23:17 +01:00
davc0n
4d14bcb02f config: Refactor class member vars ()
* Refactor config classes vars

* Fix clang format errors
2025-04-20 20:39:33 +02:00
Vaxry
9b4060f09b hyprpm: extend dep list 2025-04-20 16:54:28 +01:00
syuzuki
867bc86089 compositor: fix getMonitorInDirection skipping active monitor () 2025-04-20 04:22:21 +02:00
Bruno Krügel
f48ee7a3d1 protocols: ensure PointerConstraints activation occurs only after attched to InputManager () 2025-04-19 01:16:30 +02:00
fazzi
51afc2c291 ctm: enable fade animation on nvidia driver versions 575 and above ()
* ctm: enable fade animation on nvidia driver versions 575 and above

* format if statement without braces; handle potential throw when checking for nvidia version file
2025-04-18 21:44:54 +02:00
vaxerski
02f7da2bf2 [gha] Nix: update inputs 2025-04-18 15:39:22 +00:00
Lee Bousfield
7631d4c73f render, helpers: Call OpenGL destroyMonitorResources on disconnect ()
* render, helpers: Call OpenGL destroyMonitorResources on disconnect

* helpers: Add opengl null check
2025-04-18 17:37:51 +02:00
raf
ddae3036ca ci: close unwanted issues automatically ()
* ci: close unwanted issues automatically

You want it, it's yours my friend.

* ci/close-issues: simplify

* ci/close-issues: try to handle large number of issues

* ci/close-issues: fix 'vaxerski'
2025-04-17 22:33:05 +02:00
nyx
225e13c3cc InputManager: add config option to disable keybinds per device () 2025-04-17 02:19:10 +02:00
Lee Bousfield
3fa6320a39 desktop: Damage subsurface when position changes () 2025-04-16 17:49:01 +02:00
Vaxry
1ae7e2164c xcursormgr: include <variant>
ref 
2025-04-16 13:49:41 +01:00
Vaxry
877fb5b93a time: move to stl's clocks and move timer 2025-04-16 01:37:48 +01:00
Ikalco
0e521788bc core: wait for dmabuf readiness ()
* add doOnReadable to event loop manager

* move syncTimeline addWaiter to doOnReadable

* wait on dmabuf buffers to be readable

* don't over synchronize in scanout, also give present feedback on same buffer commit
2025-04-16 01:02:31 +02:00
MightyPlaza
ffd6cf65e4 windowrules: allow incrementing window props () 2025-04-16 01:00:40 +02:00
Mihai Fufezan
8b7b169043 flake.lock: update 2025-04-14 18:58:37 +03:00
Nathan Ollerenshaw
533bc5115e monitors: fix disconnected monitors are reconnected to an empty workspace ()
---------

Co-authored-by: nyx <nnyyxxxx@protonmail.com>
2025-04-14 11:07:53 +02:00
psyvern
99ab3e19d9 framebuffer: Fix framebuffer size check () 2025-04-13 22:40:15 +02:00
Vaxry
f4e19d3f1e layerSurface: warp position and size before taking a snapshot () 2025-04-13 17:56:20 +02:00
nyx
06469b3391 IHyprLayout: center floating window at cursor when picked up from fullscreen () 2025-04-13 17:32:53 +02:00
nyx
303a10d27c IHyprLayout: respect minimum window size ()
* IHyprLayout: respect minimum window size

this prevents window warping, before we were not respecting the minimum size which caused the window to move suddenly, even though it would be sized to its minimum size.
2025-04-13 01:03:03 +02:00
rszyma
4d85e7996d dwindle: ignore fullscreen window for positioning when use_active_for_splits=false ()
* fix(dwindle): ignore fullscreen window for positioning when use_active_for_splits=false

* rename NON_FULLSCREEN -> SKIP_FULLSCREEN_PRIORITY
2025-04-13 00:58:38 +02:00
Ikalco
2da4f427ea compositor/surface: fix surface opaque and input regions not being updated () 2025-04-12 18:58:29 +02:00
Vaxry
a17cea8b8c asyncDialogBox: fix missing pid
fixes 
2025-04-12 17:47:22 +01:00
alaricljs
6538970087 binds: add drag_threshold for click/drag isolation ()
---------

Co-authored-by: Leeman <lstrout@enlj.com>
2025-04-12 16:43:13 +02:00
Lee Bousfield
0399e64274 screencopy: Handle explicit sync failure () 2025-04-12 16:38:46 +02:00
Vaxry
382f0f23f1 pass: revert "remove renderer finalDamage since it's unused ()" ()
This reverts commit 0a7e2cb152.
2025-04-11 00:34:50 +02:00
Aaron Blasko
b83c9f5c6f cmake: do not install version.h.in () 2025-04-11 00:31:07 +02:00
Virt
d775686380 input: add warp_on_toggle_special () 2025-04-10 14:54:24 +02:00
Ikalco
0dc531c4a7 core: fix crash in data device on shutdown () 2025-04-09 18:08:42 +02:00
Ikalco
0a7e2cb152 pass: remove renderer finalDamage since it's unused () 2025-04-09 17:50:06 +02:00
Vaxry
4f991610d0 watchdog: remove watchdog
it has been unused for a while now
2025-04-09 01:48:21 +01:00
nyx
ea852965ff xdg-shell: fix some null refs () 2025-04-08 19:43:15 +02:00
Vaxry
260d8e1f71 Permission Manager: add permission management for screencopy () 2025-04-08 19:39:53 +02:00
nyx
642f394eb3 xwayland: sync primary selection with wayland () 2025-04-08 17:36:29 +02:00
Vaxry
b15c2bfff6 CursorManager: Store cursor pixel data retrieved from X/HC as a copy ()
Instead of storing pointers as refs (which could randomly get invalid very easily) copy the data.
2025-04-07 21:08:16 +02:00
Ikalco
da86db43d4 core: refactor and improve surface commit ()
* make CHLBufferReference not a SP anymore

* copy over release and acquire points in CHLBufferReference

* use CHLBufferReference in screencopy and toplevel export

TODO: use CHLBufferReference in direct scanout properly
      the only problem is the scanout buffer release timing,
      specifically the onBackendRelease mechanism

* cleanup SSurfaceState and surface pending commit tracking

* move surface code from DRMSyncobj, and move acquire to SSurfaceState

* use queue for comitted pending surface states like proto says

"The content update is placed in a queue until it becomes active." - wl_surface::commit

* drop, not release, prev buffer if 2nd buffer wl_surface.attach is sent

"A wl_buffer that has been attached and then replaced by another attach instead of committed will not receive a release event, and is not used by the compositor." - wl_surface::attach
2025-04-07 21:03:27 +02:00
Vaxry
70ae99f521 input/layers: Fix exclusive LS focus / refocus after unmap () 2025-04-07 20:52:11 +02:00
Jan Beich
a8eda7f978 helpers: add missing include for BSDs after 3c128679ee ()
src/helpers/AsyncDialogBox.cpp:47:23: error: use of undeclared identifier 'read'
   47 |         while ((ret = read(m_pipeReadFd.get(), buf.data(), 1023)) > 0) {
      |                       ^
src/helpers/AsyncDialogBox.cpp:83:9: error: use of undeclared identifier 'pipe'
   83 |     if (pipe(outPipe)) {
      |         ^
src/helpers/AsyncDialogBox.cpp:110:5: error: use of undeclared identifier 'close'
  110 |     close(outPipe[1]);
      |     ^
2025-04-07 20:36:01 +02:00
kerty0
8a8f394da7 swipe: fix swiping onto a new workspace bound to another monitor () ()
The previous code didn't check if the chosen new workspace was bound to another monitor, causing buggy behavior where workspace was simultaneously open and not.

The fix simply uses `r+1` for new workspace selection.

Also, the previous code would select rightmost workspace + 1, creating large gaps in workspace IDs in some scenarios. Example (`()` and `[]` indicate workspaces on different monitors):

`(1), 2, 3, 4, 5, 6, 7, 8, 9, [10]`

Swipe right on `()` monitor would create:

`(1), 2, 3, 4, 5, 6, 7, 8, 9, [10], (11)`

But with this commit:

`(1), (2), 3, 4, 5, 6, 7, 8, 9, [10]`
2025-04-07 14:46:31 +02:00
Virt
51838fb5f5 layout: properly track floating window position () 2025-04-06 23:41:27 +02:00
Vaxry
85f874d10f swipe: fix prev workspace remembering
fixes 
2025-04-06 22:35:26 +01:00
nyx
9b3925009a DataDevice: position icon at cursor hotspot ()
* DataDevice: position icon at cursor hotspot

ref: https://wayland.app/protocols/wayland#wl_data_device:request:start_drag
2025-04-06 23:24:14 +02:00
Vaxry
3c128679ee helpers: Add an async dialog box impl ()
Adds an async dialog box, way safer than our previous local solution for ANR
2025-04-06 17:31:58 +02:00
Vaxry
e96b8ce4cc window: send fractional scale on updateScaleTransform
fixes 
2025-04-06 00:30:13 +01:00
Maximilian Seidler
433b7881a3 compositor: fix crash when moving a workspace to a monitor with size 0x0 () 2025-04-06 00:54:29 +02:00
Vaxry
ed05f14300 ci: nuke stalebot 2025-04-05 19:28:42 +01:00
Vaxry
c62fb08da6 github: remove issue templates 2025-04-05 18:45:52 +01:00
Vaxry
8ba20fcae1 compositor: avoid crash on null window monitor move
ref 
2025-04-05 00:30:33 +01:00
Mihai Fufezan
ff97d18c4c flake.lock: update 2025-04-03 18:08:55 +00:00
Maximilian Seidler
5e8bb71785 ctm: fix crash when finishing ctm progress with a destroyed monitor () 2025-04-03 16:40:59 +02:00
Amadej Kastelic
b496e2c718 nix/module: load plugins using exec-once () 2025-04-03 10:43:06 +03:00
Arkady Buryakov
a41b8d5e97 groupbar: add text offset and upper gap settings ()
* Groupbar: add keep_upper_gap setting to apply/remove outer gap offset to the upper side of groupbar

* Groupbar: add text_offset setting to adjust text vertical position in a group header
2025-04-02 22:26:46 +02:00
Armin
8654029f86 versionkeeper: create version file if not present () 2025-04-02 22:21:05 +02:00
nyx
a4e6c5d678 window: don't deactivate unfocused xwayland windows in groups ()
* window: don't deactivate unfocused xwayland windows in groups

we dont want to deactivate unfocused xwayland windows because X is weird, keep the behavior for wayland windows
2025-04-02 00:51:37 +02:00
nyx
3a47c73f34 layout: center floating window at cursor when picked up from fullscreen ()
* layout: center floating window at cursor when picked up from fullscreen

when picking up a floating window after it had been fullscreened before it would return to its previous position which looked ugly because the cursor could be no where near the windows original position, this patch makes it so that the window is returned to the users current cursor position

* E
2025-04-02 00:45:51 +02:00
X2E4VXpZKv
1f0fd79b91 internal: Don't force default cursor on config reload/monitor reconfigure () 2025-04-01 16:20:38 +02:00
Vaxry
d1a59ec39e renderer: render tiled fading out above other tiled windows
fixes 

closes 
2025-04-01 00:25:09 +01:00
Vaxry
4c987b20e2 makefile: fix find command in installheaders
fixes 
2025-03-31 17:13:27 +01:00
nyx
2309270752 anr: add config for ping number before popup shows up ()
* anr: make pings configurable

makes the pings of the dialog popup configurable
2025-03-31 18:06:17 +02:00
Vaxry
79b526a041 socket2: add minimized event for foreign-wlr
ref 
2025-03-30 22:38:30 +01:00
nyx
075bbecabd core: fix artifacts when fullscreening ()
* core: fix artifacts when fullscreening

fixes an issue where fullscreening a floating window that is between two monitors causes artifacts to appear on the monitor where it did not become fullscreened on

* e
2025-03-30 23:28:12 +02:00
nyx
8aaffda969 core: fix null ref when resuming system ()
* core: fix null ref when resuming system

* e
2025-03-30 23:18:04 +02:00
Shockingly Good
10a335631e solitary: Fix the non-working tearing ()
Fixes the non-working tearing by removing the incorrect
opaqueness check for the windows.

Fixes 
2025-03-30 20:29:39 +02:00
Emad Elsaid
da2d7c3971 config: Fix matching monitor by description to allow space prefix () 2025-03-30 03:12:15 +02:00
LeviVanDerMaas
05eb0aa43d workspaces: Add binds:hide_special_on_workspace_change () 2025-03-30 03:11:39 +02:00
Tom Englund
fc7223edc0 synctimeline: check if fd is readable before wait ()
a lot of the time the fd is already readable, and done. so just call the
waiter directly instead of making a waiter and adding it to the
eventloop.
2025-03-30 01:53:23 +01:00
Lee Bousfield
86c279d7d0 protocols: Don't update hdr metadata if image description is unchanged () 2025-03-30 01:25:27 +01:00
micha4w
46b00a4a86 makefile: add new shaders to make installheaders () 2025-03-30 01:25:02 +01:00
Tom Englund
4a79eea6dc opengl: check for g_pHyprOpengl pointer ()
restore the pointer check to avoid null ptr dereference on compositor
destruction.
2025-03-29 21:52:27 +01:00
UjinT34
7374a023ef renderer/opengl: Extract shaders from source ()
---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2025-03-29 01:19:35 +01:00
Lee Bousfield
a46576afc3 xwayland: Cleanup server startup and FDs () 2025-03-28 17:12:25 +01:00
Lee Bousfield
10035a85cc core: Don't damage the entire surface every frame ()
* core: Don't damage the entire surface every frame

* core: Damage buffer on dims or transform change

* core: Use guards for scale and tr equality checks
2025-03-28 17:00:39 +01:00
Vaxry
c93140a5f1 surfacestate: reset buffer bit before applying to current
fixes 
2025-03-28 12:32:07 +00:00
Vaxry
5380cbcdda workspaces: minor fixes to persistence
fixes 
2025-03-27 14:00:29 +00:00
Tom Englund
9ea76428b6 internal: fix minor ubsan errors ()
* opengl: check if g_pHyprOpengl exist

on compositor destruction we can hit a race where a CEGLSync destructs
and tries to call eglDestroySyncKHR on a null g_pHyprOpengl.

/src/render/OpenGL.cpp:3019:32: runtime error: member access within null pointer of type 'struct CHyprOpenGLImpl'
      0x555565eed979 in CEGLSync::~CEGLSync() /src/render/OpenGL.cpp:3019
      0x555565f6271e in std::default_delete<CEGLSync>::operator()(CEGLSync*)
     const /usr/lib/gcc/x86_64-pc-linux-gnu/14/include/g++-v14/bits/unique_ptr.h:93

* xdgshell: dont apply state on empty states

setsize can be called before a state has been added to pending,
resulting in calling ApplyState with a empty state.

/src/protocols/XDGShell.cpp:323:11: runtime error: null pointer passed as argument 2, which is declared to never be null
      0x5555659bf67e in CXDGToplevelResource::applyState() /src/protocols/XDGShell.cpp:323
      0x5555659bcedc in CXDGToplevelResource::setSize(Hyprutils::Math::Vector2D const&) /src/protocols/XDGShell.cpp:  256
      0x555563eed0ef in Events::listener_commitWindow(void*, void*) /src/events/Windows.cpp:841
2025-03-26 18:22:44 +01:00
Vaxry
0cd04bd666 surfacestate: track and apply updated state
fixes 
2025-03-26 17:22:21 +00:00
Vaxry
1c2b9a9ce3 opengl: don't attempt to compile cm on gles3.0
also disable the error for the cm shader

fixes 
2025-03-26 14:28:19 +00:00
vaxerski
cec084c178 pass/rect: include clipBox in opaque calculations
ref  ref 
2025-03-26 11:47:04 +00:00
vaxerski
c2ef8fcc00 groupbar: round boxes 2025-03-26 11:44:38 +00:00
Tom Englund
3fc3521a97 pass: remove unusued timeline in texpass ()
remove unused timeline and waitpoint in texpass and especially remove
the passing it to renderTextureInternalWithDamage that implicitly
converted it to bool. setting discardActive and allowCustomUV
2025-03-26 02:22:09 +01:00
Arkady Buryakov
9a67354fa2 Groupbar: apply scaling factor to text () 2025-03-26 02:07:56 +01:00
nyx
f7ba86d1f3 keybinds: add sendkeystate dispatcher () 2025-03-25 00:59:13 +01:00
Vaxry
f3db1b172c decoration: bring back border_part_of_window
fixes , now under decoration: though
2025-03-24 23:57:50 +00:00
Tom Englund
2a6d070774 xwl: dont close the fd to early ()
dont close the fd until the wl_event_source is removed, so we dont get
another event triggered with an already closed fd.
2025-03-24 19:33:07 +01:00
Vaxry
aec69131cd seat: avoid sending null surfaces in leave/enter events
ref 
2025-03-24 14:10:47 +00:00
vaxerski
4b968e5bc1 [gha] Nix: update inputs 2025-03-24 12:57:34 +00:00
UjinT34
a852461c7d renderer: Simplify and fix hdr metadata setting ()
* simplify and fix hdr metadata setting

* keep incorrect(?) cm skip till 
2025-03-24 13:56:07 +01:00
Mihai Fufezan
e4abf26069 Nix: add changes from Nixpkgs derivation 2025-03-23 18:19:34 +02:00
Mihai Fufezan
006bd9eef5 protocols/meson.build: use native wayland-scanner 2025-03-23 18:15:17 +02:00
Vaxry
5ee35f914f version: bump to 0.48.0 2025-03-23 14:56:13 +00:00
entailz
aa1bd647b1 core/Compositor.hpp: fix non-relative Texture import () 2025-03-23 14:49:10 +02:00
Lee Bousfield
fdb7ca6c8f core/compositor: Fix dropping cursor buffer data early () 2025-03-22 23:06:02 +01:00
UjinT34
6ab5a0befb renderer: fix cm_fs_passthrough () 2025-03-22 18:34:01 +01:00
Lee Bousfield
6384f4acf4 core/compositor: Correctly track SHM buffer damage () 2025-03-22 17:13:44 +01:00
Aaron Blasko
4600043a49 hyprpm: return 1 when plugins are outdated ()
* hyprpm: return 1 when plugins are outdated

* clang-formatted
2025-03-22 17:01:35 +01:00
Lee Bousfield
279b06044c screencopy, render: Use explicit sync for screencopy ()
* screencopy, render: Use explicit sync for screencopy

* screencopy: Check if explicit sync is enabled

* screencopy: Don't require explicit KMS enabled
2025-03-22 17:01:14 +01:00
Tom Englund
ccbdba7ee2 syncobj: refactor point timelines ()
no need to store the resource, just store the csynctimeline as a shared
pointer and make the timeline own the syncobj fd.
2025-03-21 20:19:53 +01:00
UjinT34
c7f0519faf core: fix DS and VRR automation () 2025-03-21 14:33:07 +01:00
Lee Bousfield
7ea4fbf0ba types: Upgrade buffer ref from WP to SP () 2025-03-20 11:08:47 +00:00
Tom Englund
f6ca4bac51 syncobj: restore SHM buffer reset ()
reset shm buffers early to mitigate stuttering animations, also reuse
the monitors eglSync and store the eglsync per monitor. this however
reintroduces flickering in dbeaver nonsyncobj application.
2025-03-20 11:39:55 +01:00
phonetic112
155eba57d8 groupbar: remove 2 pixel gap above groupbar () 2025-03-19 23:09:36 +01:00
Andrei V
7b10530a0d XWayland: restore the abstract socket, and make it optional ()
* Revert "xwayland: don't create an abstract unix domain socket on linux ()" ()

This reverts commit 2b01a5bcf6.

* xwayland: make the abstract Unix domain socket optional ()

* xwayland: extend the default permissions for a regular Unix domain socket ()

* xwayland: a little refactoring for `createSocket`
2025-03-19 23:06:30 +01:00
Tom Englund
a25a214523 dmabuf: pop buffer on failure ()
ensure it doesnt permanently gets stuck in the container on failure, pop
it from the container.
2025-03-19 17:49:20 +01:00
Lee Bousfield
c8d80a2920 ci: Fail on warnings ()
* ci: Fail on warnings

* misc: Fix compiler warnings
2025-03-19 00:46:28 +01:00
Vaxry
03385fc07f seatmgr: avoid crash on null surfs 2025-03-18 19:43:05 +00:00
vaxerski
cca0f48b74 renderer: add an option to disable cm and auto-skip cm if not necessary
fixes 

adds render:cm_enabled default true
2025-03-18 11:30:08 +00:00
Kamikadze
60edb376f2 config/defaultConfig.hpp: windowrulev2 -> windowrule () 2025-03-18 13:13:03 +02:00
tachyglossues
6f74d8d7e9 example/hyprland.conf: windowrulev2 -> windowrule () 2025-03-18 12:24:43 +02:00
Vaxry
ec4bea7901 config: nuke windowrule v1 syntax 2025-03-18 01:37:00 +00:00
Honkazel
9171db1984 renderer: delete now redundant ifdefs ()
Hyprland for now requires aquamarine =>0.8.0 anyway
2025-03-18 02:29:08 +01:00
Ikalco
5f60fc7d00 renderer: only commit hw cursor stuff if needed () 2025-03-17 22:06:41 +01:00
Lee Bousfield
c4f46473df monitor: Optimize direct scanout damage () 2025-03-17 22:05:44 +01:00
Lee Bousfield
011d7ccb91 internal: Fix compiler warnings () 2025-03-17 15:52:40 +01:00
Lee Bousfield
efc51eb7d1 managers: Use primary backend for cursor swapchain () 2025-03-17 15:51:18 +01:00
nyx
c2835b6b0f groupbar: remove recursive window recalc () 2025-03-17 15:50:49 +01:00
Mihai Fufezan
d5d7f69d1e flake.lock: update
aquamarine: bump to 0.8.0
hyprcursor: bump to 0.1.12
2025-03-17 14:50:35 +02:00
Ikalco
5cef2f44fe renderer: allow commits when buffer is unchanged but cursor changed () 2025-03-17 13:06:58 +01:00
Vaxry
22154fa272 opengl: simplify cm pipeline
fixes a few mistakes, and skips the CM shader in cpu instead of adding a costly branch

ref 
2025-03-16 21:50:20 +00:00
Mihai Fufezan
2ddd16ef28 CMake: install frag files (for real this time) 2025-03-16 19:35:37 +02:00
Mihai Fufezan
d7382aa8a1 CMake: install frag files 2025-03-16 18:27:27 +02:00
Mihai Fufezan
90306bdae6 Meson: include frags in globber 2025-03-16 16:30:50 +02:00
Vaxry
b1ab0f7539 splashes: update for 3ya 2025-03-16 03:03:37 +00:00
Tom Englund
bf5e4bf116 syncobj: dont crash compositor on protocol errors ()
dont call a member on null pointer if client misbehaves.
as in the weak pointer being expired.
2025-03-15 19:57:52 +01:00
Blackilykat
4c471218c9 renderer: fix window offset for dragged windows () 2025-03-15 19:15:09 +01:00
phonetic112
e59680481d input: Fix clicking through groupbar tabs () 2025-03-15 01:22:39 +01:00
André Silva
f4315db50f nix: mesa -> libgbm ()
d209d800b7
2025-03-14 20:24:55 +02:00
Vaxry
3cc8e8c6be renderer: don't crash if cm fails to compile 2025-03-14 15:20:08 +00:00
Vaxry
b37944605f protocols: fix include 2025-03-14 14:35:01 +00:00
Vaxry
f4995c1837 descriptions: remove allow_early_buffer_release 2025-03-14 14:14:10 +00:00
Tom Englund
6ffde36466 syncobj: use eventfd instead of stalling fd checks ()
* syncobj: cleanup and use uniqueptrs

cleanup a bit missing removals if resource not good, erasing from
containers etc. make use of unique ptrs instead. and add default
destructors.

* syncobj: rework syncobj entirerly

remove early buffer release that was breaking explicit sync, the buffer
needs to exist until the surface commit event has been emitted and draw
calls added egl sync points, move to eventfd signaling instead of
stalling sync point checks, and recommit pending commits if waiting on a
signal. add a CDRMSyncPointState helper class. move a few weak pointers
to shared pointers so they dont destruct before we need to use them.

* syncobj: queue pending states for eventfd

eventfd requires us to queue pending stats until ready and then apply to
current, and also when no ready state exist commit the client commit on
the current existing buffer, if there is one.

* syncobj: clear current buffer damage

clear current buffer damage on current buffer commits.

* syncobj: cleanup code and fix hyprlock

remove unused code, and ensure we dont commit a empty texture causing
locksession protocol and gtk4-layer-shell misbehaving.

* syncobj: ensure buffers are cleaned up

ensure the containers having the various buffers actually gets cleaned
up from their containers, incase the CSignal isnt signaled because of
expired smart pointers or just wrong order destruction because mishaps.
also move the acquire/point setting to buffer attaching. instead of on
precommit.

* syncobj: remove unused code, optimize

remove unused code and merge sync fds if fence is valid, remove manual
directscanout buffer dropping that signals release point on pageflip, it
can cause us to signal the release point while still keeping the current
buffer and rendering it yet again causing wrong things.

* syncobj: delay buffer release on non syncobj

delay buffer releases on non syncobj surfaces until next commit, and
check on async buffers if syncobj and drop and signal the release point
on backend buffer release.

* syncobj: ensure we follow protocol

ensure we follow protocol by replacing acquire/release points if they
arrive late and replace already existing ones. also remove unneded
brackets, and dont try to manual lock/release buffers when it comes to
explicit protocol. it doesnt care about buffer releases only about
acquire and release points and signaling them.

* syncobj: lets not complicate things

set points in precommit, before checking protocol errors and we catch
any pending acquire/release points arriving late.

* syncobj: move SSurfaceState to types

remove destructor resource destroying, let resources destroys them on
their events, and move SSurfaceStates to types/SurfaceState.hpp

* syncobj: actually store the merged fd

have to actually store the mergedfd to use it.

* syncobj: cleanup a bit around fences

ensure the current asynchronous buffer is actually released on pageflip
not the previous. cleanup a bit FD handling in
commitPendingAndDoExplicitSync, and reuse the in fence when syncing
surfaces.

* syncobjs: ensure fence FD doesnt leak

calling resetexplicitfence without properly ensuring the FD is closed
before will leak it, store it per monitor and let it close itself with
the CFileDescriptor class.

* syncobj: ensure buffers are actually released

buffers were never being sent released properly.

* types: Defer buffer sync releaser until unlock

* syncobj: store directscanout fence in monitor

ensure the infence fd survives the scope of attemptdirectscanout so it
doesnt close before it should have.

* syncobj: check if if acquire is expired

we might hit a race to finish on exit where the timeline just has
destructed but the buffer waiter is still pending. and such we
removeAllWaiters null dereferences.

* syncobj: code style changes

remove quack comment, change to m_foo and use a std::vector and
weakpointer in the waiter for removal instead of a std::list.

* syncobj: remove unused async buffer drop

remove unused async buffer drop, only related to directscanout and is
handled elsewhere.

---------

Co-authored-by: Lee Bousfield <ljbousfield@gmail.com>
2025-03-14 15:08:20 +01:00
André Silva
c754d7963f nix: remove wayland-protocols overlay and bump flake ()
* flake.lock: update nixpkgs

* nix: remove wayland-protocols bump

* flake.lock: update

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2025-03-14 09:14:59 +02:00
UjinT34
8c97cb7858 renderer: add simple color management ()
Adds proper color management and transformations for CM surfaces.
2025-03-14 00:15:18 +01:00
phonetic112
e86d3a14e4 groupbar: add an option to adjust gap sizes () 2025-03-12 15:09:09 +01:00
nyx
4b25fbe5fd windows: respect noinitialfocus with workspace changes ()
If a window is specifically placed on another workspace then we should
not "focus" that workspace even if the window itself never gets focused
when noinitialfocus is enabled for the window.
2025-03-11 18:32:01 +01:00
Vaxry
81e93acba4 groupbar: pass alpha to title tex render pass
ref 
2025-03-11 17:30:12 +00:00
Khalid J.
b21edb1a97 input: fix touch calibration matrix overriding
make the default behavior non-overriding to avoid overriding user-set
values elswehere.
2025-03-11 00:51:42 +00:00
MightyPlaza
e4af4b5e2e core: update decorations on lockgroups ()
modified:   src/managers/KeybindManager.cpp
2025-03-10 18:40:46 +01:00
Ikalco
d30cc19d25 renderer: skip ds commits if buffer didn't change ()
this fixes direct scanout glitches by ensuring that attemptDirectScanout doesn't try to recommit the same buffer to AQ
which would cause a pageflip event and the backendRelease to release the same buffer too early
2025-03-08 20:24:22 +01:00
nyx
f15b49e0fd core: prevent crash when monitor list is empty () 2025-03-08 20:14:03 +01:00
nyx
c544c5115c windowrules: store floating size on close instead of resize () 2025-03-08 14:20:27 +01:00
nyx
b80b64cd6c windowrules: add option to allow size persistence between app launches () 2025-03-08 02:12:02 +01:00
vaxerski
4082e876d5 [gha] Nix: update inputs 2025-03-07 22:56:23 +00:00
Lee Bousfield
8ce1665fdb protocols: Fix blocked color management get_information () 2025-03-07 23:54:56 +01:00
nyx
7753e8ea68 keybinds): prevent crash when executing movewindoworgroup () 2025-03-06 23:08:57 +01:00
Vaxry
cb4230e1c2 touch: reject swipe on locked sessions
fixes 
2025-03-06 22:08:23 +00:00
nyx
7055d0c138 master: add option to keep master window position () 2025-03-06 23:06:05 +01:00
nyx
4435f5c546 input: fixup mouse check for flipping x / y () 2025-03-06 17:33:01 +01:00
Lee Bousfield
7a84317f33 protocols: Fix cursor shape protocol valid shape check () 2025-03-06 16:54:47 +01:00
Abdalrahman Shaban
2433760786 hyprctl: Error handling improvements, minor cleanups () 2025-03-06 00:50:28 +01:00
Matt White
b51ab182ae socket2: add activespecialv2 () 2025-03-05 20:33:33 +01:00
nyx
d7e7a29261 input: add flip_x and flip_y for touchpad () 2025-03-03 21:56:01 +01:00
Ikalco
f1ef724a87 protocols: Ensure protocol dtors are called in ProtocolManager dtor () 2025-03-03 01:57:49 +01:00
Vaxry
474bfcbccd core: fix clang-format 2025-03-02 16:24:37 +00:00
Mihai Fufezan
905ca39bc9 nix/module: toHyprconf -> toHyprlang ()
* nix/module: toHyprconf -> toHyprlang

Updated generator that will end up living in Nixpkgs' `lib/generators`.

* nix/module: use xdph package directly

The downstream module already applies hyprland's
finalPackage to the portalPackage.

* new lib

* lib: add flattenAttrs, remove category processing

Flattening attributes means we no longer need to process categories
separately. For all intents and purposes, they do not exist.

Simplify the codebase once again, while introducing an easy to grasp
recursive function.

Add a bit of documentation for toHyprlang, though I doubt it's clear
enough even now. Still needs proper NixDoc.

* lib: add proper NixDoc

* nix/lib: inherit from lib
2025-03-02 15:41:49 +02:00
Vaxry
3a21dd84b3 core: cleanup some smart pointer usage 2025-03-02 02:19:35 +00:00
Vaxry
e6be4af21f core: add default initializers for ccompositor ptrs 2025-03-02 02:16:24 +00:00
Vaxry
2c78867a98 core: cleanup window properties
they don't need to be in CConfigManager, and belong to window instead
2025-03-02 02:13:37 +00:00
phonetic112
2acbb59bf2 core: ignore internal move workspace calls if the workspace doesn't change () 2025-03-01 19:34:38 +01:00
Vaxry
11943f761e monitor: avoid crash on expired special workspace monitor owner
fixes 
2025-03-01 15:56:44 +00:00
Indy Ray
f148b96bea input: Provide a non-zero time for button release triggered during window unmap. ()
It doesn't seem that this is actually required, but it seems more consistant with other areas, such as in CVirtualKeyboardV1Resource::releasePressed.
2025-03-01 16:53:30 +01:00
Lee Bousfield
445337d03d protocols: Fix IWaylandProtocol onDisplayDestroy m_pGlobal double-free () 2025-02-28 02:52:31 +01:00
Lee Bousfield
34f2a4713e core: Add null activeWorkspace check in moveWorkspaceToMonitor () 2025-02-28 02:52:23 +01:00
Tuur Vanhoutte
d5920bc5da internal: fix include paths ()
* Fix include path in Monitor.hpp

* fix more includes
2025-02-27 00:56:11 +01:00
Vaxry
aee9b8ac19 protocols: fix relative include 2025-02-26 22:13:06 +00:00
Vaxry
32a3d84d74 protocols: fix wrong iface for xdg_dialog
fixes 
2025-02-26 20:49:31 +00:00
Soliprem
002cd91fbf nix: wp-protocol version bump () 2025-02-26 19:36:24 +02:00
littleblack111
01f4074421 hyprctl/layers: print pid for layershell clients ()
Adds a `getPID()` fn to layershell to print it in `hyprctl layers`
2025-02-26 16:03:06 +01:00
UjinT34
6787fe8933 protocols: Support wp color management proto ()
Adds support for the recently merged w-p CM protocol alongside the (now deprecated) old CM WIP protocols
2025-02-26 15:56:37 +01:00
牧羊犬真Q
f0850905f0 cmake: add CXX_STANDARD_REQUIRED flag to ensure c++ version must be c++26 ()
As the [cmake documentation](https://cmake.org/cmake/help/latest/prop_tgt/CXX_STANDARD_REQUIRED.html) said, when we use the CMAKE_CXX_STANDARD without CXX_STANDARD_REQUIRED, then the standard version we set will be an optional value, and may decay back to older c++ version. So I add this configuration into cmakelist to ensure c++ version is c++26.
2025-02-25 14:03:09 +00:00
Vaxry
d1ea18b492 hyprctl/config: fixup config descriptions, fill out missing ones, provide more data
adds current and explicit, better parsing, and fixes missing / invalid ones
2025-02-25 14:03:04 +00:00
Vaxry
73ae9790f9 hyprpm: add --hl-url for custom forks 2025-02-24 13:11:30 +00:00
nyx
3458d7ac93 xwayland: improve dnd and cleanup ()
Minor changes to xwayland dnd / regular dnd to fix various issues

---------

Co-authored-by: Vaxry <vaxry@vaxry.net>
2025-02-24 03:52:10 +01:00
cyanbun96
e59464629f config: fix a possible crash in the monitor config parser ()
* Less crash-prone monitor config parser

* clang-format
2025-02-23 23:43:24 +01:00
littleblack111
2e81648980 internal: use isNumber from hyprutils () 2025-02-23 18:35:29 +01:00
Tom Englund
8f50460bfe xcursor: move config check to sync gsettings ()
move the check down into the function itself, 39df1f4 began syncing on
session change, but isnt checking for the setting itself, so instead of
duplicating checks all over. do it here.
2025-02-23 18:32:38 +01:00
Vaxry
f4b148df1e anr: add xwayland support ()
Adds XWayland support to ANR dialogs
2025-02-21 21:26:53 +01:00
Vaxry
0e24f9c0d5 anr: add a note for no pid dialogs 2025-02-21 16:58:22 +00:00
Aaron Tulino
2cfa5d2408 hyprctl: Add IPC support for Hyprsunset ()
* Add IPC support for Hyprsunset

* clang-format

* Add documentation
2025-02-21 15:20:11 +01:00
Mihai Fufezan
fa246cb6ed flake.lock: update hyprutils and aquamarine 2025-02-20 14:56:17 +02:00
nyx
6d25ef09cd xwayland: add INCR support for clipboard transfers ()
add INCR protocol support for large transfers
fix write handling for partial transfers
fix an issue where wayland windows could die from a paste from an
xwayland window
2025-02-19 16:29:39 +01:00
Vaxry
0137a5f6cd anr: capitalize options 2025-02-18 15:20:00 +00:00
Vaxry
3c1a2e9fca config/descriptions: add enable_anr_dialog 2025-02-18 15:18:34 +00:00
Vaxry
fb8eaba83f core: add an ANR dialog
for xdg-shell, we can ping the wm_base, and thus render an ANR dialog if an app dies

for XWayland, there probably is a similar method, but I don't know about it and don't care.
2025-02-18 15:13:56 +00:00
nyx
3352317ca8 scripts/generateVersion.sh: convert to posix () 2025-02-18 16:18:22 +02:00
Vaxry
e59623d1d5 hyprctl: don't return empty str if there are no global shortcuts
fixes 
2025-02-18 00:33:36 +00:00
nyx
d01f9943e1 subsurfaces: dont try to access popup surfaces when handling subsurface updates () 2025-02-17 19:02:32 +01:00
andrewandreii
59c615c321 input: add follow_mouse_threshold () 2025-02-17 03:03:27 +01:00
Vaxry
e2a9271150 animations: add the gnomed animation style for windows 2025-02-16 20:53:49 +00:00
Maximilian Seidler
897ee276dc xwayland: configure on a configure request and cleanup geometry conversion ()
* xwayland: configure the window on a configure request

* xwayland: move coordinate conversion handling to their own functions

* xwayland: rename configure to configureRequest
2025-02-16 01:20:42 +01:00
andrewandreii
94a30889a7 keybinds: fix some errors not returning a failure () 2025-02-15 20:04:02 +01:00
nyx
9228116c9a xwayland: fix a possible clipboard race condition () 2025-02-15 20:03:37 +01:00
Roberto Previdi
410da2e46f workspaces: update persistence on workspace rename () 2025-02-15 15:01:52 +01:00
nyx
7a6fde8414 internal: redirect exec'd app output to /dev/null () 2025-02-15 14:51:17 +01:00
Tom Englund
3eb6cb1875 syncobj: ensure we only add waiters on succesful checks ()
timeline check only returns nullopt on ETIME_ERR , meaning the if check
later on returns true if drmSyncobjTimelineWait returns anything else
like EINVAL/EPERM/EACCESS etc, so actually check the returned .value()
of the std::optional. also move the fd to rvalue references.
2025-02-15 14:48:52 +01:00
Vaxry
2f967037aa config: set no_hw_cursors to auto by default and disable on tearing
when tearing, updates to the overlay plane may be ignored by the kernel. To avoid the cursor being a slideshow, disable hw cursors
2025-02-15 00:29:27 +00:00
Vaxry
1309b59f2c monitor: report a scheduled frame when tearing on cursor move 2025-02-15 00:18:43 +00:00
Vaxry
fb36815b01 renderer: remove spammy log 2025-02-14 22:51:41 +00:00
Honkazel
df3fba1572 internal: remove unused variable () 2025-02-14 16:31:03 +01:00
Maximilian Seidler
40adb3dfb4 config: actually set initial beziers () 2025-02-13 11:37:59 +00:00
Anthony Ruhier
5d2b008294 renderer: disable explicit if aquamarine output doesn't support it () ()
The explicit settings ignore the aquamarine output.supportsExplicit
attribute, which creates glitches on drivers not supporting explicit
sync (example: freedreno).

If the output has been set as not supporting explicit, disable the
explicit settings.
2025-02-13 11:09:25 +00:00
Maximilian Seidler
208f94fe12 animations: sync inactive/active border angles when using borderangle animations () 2025-02-13 11:08:03 +00:00
Maximilian Seidler
1789405163 session-lock: send locked when in unsafe state () 2025-02-13 11:06:36 +00:00
nyx
68bb3e7f0a env: move XDG_SESSION_TYPE to before backend init () 2025-02-12 13:54:42 +00:00
J. J. Ramsey
f83fe9986b protocols: add version 2 of ext-idle-notify-v1 protocol ()
Signed-off-by: James Ramsey <james.jehiel.ramsey@gmail.com>
Co-authored-by: James Ramsey <james.jehiel.ramsey@gmail.com>
2025-02-11 15:58:43 +01:00
Mihai Fufezan
f2d43e5f21 nix/overlays: add wayland-protocols overlay 2025-02-10 20:17:29 +02:00
Mihai Fufezan
e1179b665b flake.lock: update 2025-02-10 20:11:35 +02:00
Vaxry
f261fb6fe0 groupbar: fix groupbar missing when indicator_height is <= 0
ref  
2025-02-09 17:58:09 +00:00
Vaxry
3a43e7bb9a config: default movefocus_cycles_fullscreen to false
less confusing
2025-02-09 17:50:54 +00:00
Vaxry
1f97643e83 core: add mallopt to modify trim threshold 2025-02-09 17:38:20 +00:00
clamydo
56f6f61596 tablet: take active_area_size into account when sending tip event ()
* fixes , take `active_area_size` into account when sending tip event

* check if `relative_input` is set

As suggested by @y47s5s68tq870r7tc1xpp755pabopg

* refactoring active area in own function to keep it DRY

* coding style

* making transformation static

---------

Co-authored-by: clamydo <clamydo@users.noreply.github.com>
2025-02-09 15:30:30 +01:00
Honkazel
8e10ddb592 datadevice: fix wrong param ()
Reason: look at the setReceive parameters and also at the next line(tries to narrow)
2025-02-08 22:00:55 +01:00
nyx
feb8ad48f0 groups: deactivate unfocused windows in groups () 2025-02-08 15:05:44 +01:00
Tom Englund
f7fcbe32c9 renderer: various fixes towards improving gpu calls robustness ()
ensure framebuffer textures are detached and deleted, avoid leaving framebuffers bound when not needed

* render: avoid calling glDeleteProgram on no program

its safe to do so but it adds a bunch of unnecessery lines in apitrace
when tracing. if guard it and return early.

* opengl: ensure texture and buffers are unbound

ensure bound buffers are unbound after use, also detach textures from
framebuffer before deleting it otherwise it will become dangling and
essentially leak.
2025-02-08 01:46:26 +01:00
Paul Cross
a724332eb8 desktop: add ability to target pinned windows in workspace rules ()
* desktop: add ability to target pinned windows in workspace rules

* desktop: add ability to target pinned windows in workspace rules

* fix formating
2025-02-08 01:45:13 +01:00
Vaxry
54441e0c4e renderer: fix fade out on silent moves to special 2025-02-06 17:45:28 +00:00
WhySoBad
acbcf0cf11 toplevelexport: fix transformed origin for shm buffers ()
* toplevelexport: fix transformed origin for shm buffers

* toplevelexport: fix style nits
2025-02-06 12:42:20 +00:00
vaxerski
30b49c75bf popup: improve ::at() behavior 2025-02-06 12:15:41 +00:00
Maximilian Seidler
ff9e059de6 window: move size reporting to animation begin callback ()
* window: fix resizes with an update callback

* window: fixup sendWindowSize

Remove the size argument from sendWindowSize, since it is now a member of the Window class
and we don't want any mismatches between m_vRealSize and what we report.

Remove sendWindowSize from mapWindow, since we shouldn't need it.

* window: sendWindowSize on animation begin

* window: move most calls to sendWindowSize to the animation begin
callback

* window: remove sendWindowSize in unmanaged if not fullscreen
2025-02-06 11:21:04 +00:00
Tom Englund
f1e32cd122 core: avoid .at() and use [] operator ()
avoid .at() where it makes sense and use [] operator in loops.
2025-02-06 11:18:04 +00:00
nyx
868b2b544a window: fix missing surface null checks to prevent crashes () 2025-02-06 11:17:12 +00:00
Vaxry
8a6778f0a0 scripts: don't overwrite generated version if we're not in a git repo 2025-02-05 15:42:01 +00:00
raf
64591c85aa nix: add hydraJobs output for aggregating Hyprland build jobs 2025-02-05 16:55:33 +02:00
nyx
873bff390e renderer: fix missing null checks to prevent crashes () 2025-02-05 15:53:09 +01:00
Tom Benham
84c9baecc6 keybinds: Added toggleswallow dispatcher ()
* Added `toggleswallow` dispatcher

* clang-format

* Removed brackets for 1-line if
2025-02-05 09:56:41 +00:00
vaxerski
3b99e906df compositor: don't iterate over unmapped ls-es in vectorToLS
fixes 
2025-02-04 10:18:08 +00:00
Vaxry
5e7292434a compositor: guard null ws in updating fade 2025-02-03 22:36:10 +00:00
Vaxry
70cfc7cc9c cmonitor: guard old workspace 2025-02-03 19:53:14 +00:00
Vaxry
1da0b2c02e subprojects: update h-p
fixes 
2025-02-03 19:45:26 +00:00
Alexander
708d166360 dispatchers: add cyclenext hist option () 2025-02-03 02:34:30 +01:00
Vaxry
44004abc01 config: fix includes 2025-02-02 22:20:28 +00:00
UjinT34
31431a9271 protocols: Support content-type-v1 proto () 2025-02-02 20:25:29 +01:00
Alexander
70d94fec13 refactor: clang-tidy in compositor ()
Co-authored-by: Alexandr Krylov <t@ruenya.nix>
2025-02-02 18:34:26 +01:00
outfoxxed
373108102c protocols: implement hyprland-ctm-control rev 2 ()
* protocols: implement hyprland-ctm-control v2

* bump h-p and nix
2025-02-02 18:31:04 +01:00
micha4w
97a24ec6f3 Nix: change meson buildtype from debugoptimized to debug 2025-02-02 14:32:03 +02:00
Vaxry
64fefa3749 desktop: move popups to UPs and fix missing subsurface resource
fixes 
2025-02-01 19:10:19 +00:00
Vaxry
e380b6ed66 popup: take xdg geometry into account in input calcs
fixes 
2025-02-01 15:49:10 +00:00
Vaxry
88adae73ba pass: add input region debug 2025-02-01 15:31:31 +00:00
Vaxry
c6f672257b desktop: move popup and subsurface ctors to factories
makes sure m_pSelf is set before we do anything like possibly adding children

fixes 

supersedes 
2025-02-01 15:08:30 +00:00
Maximilian Seidler
5b43c106bd animation: don't immediately disconnect active vars during tick () 2025-02-01 15:44:20 +01:00
Mihai Fufezan
d11d069715 CI/Nix: remove deprecated magic-nix-cache-action 2025-02-01 09:29:06 +02:00
nyx
ddf180fa30 render: enforce framebuffer offloading and remove introspection toggle () 2025-01-31 17:08:43 +01:00
nyx
a4b7d1c2d7 xwayland: correct pointer coordinate mismatch in X11 windows ()
refactor(xwayland): add back comments
2025-01-31 14:36:56 +01:00
Mihai Fufezan
12b5034c99 configWatcher: watch both symlinks and canonical paths () 2025-01-31 14:36:22 +01:00
Vaxry
9c38287410 groupbar: various visual improvements
added rounding, round at edges, and indicator height
2025-01-31 13:32:36 +00:00
Brayden Zee
ac5668192e desktop: fix segfault when destroying a partially create layer surface () 2025-01-31 13:33:51 +01:00
Aaron Tulino
7d51dee103 hyprctl: Extract IPC code to separate method ()
This makes it possible to use the same IPC code for more projects in the future
2025-01-31 13:33:36 +01:00
nyx
ea16b64ec1 xwayland: prevent invalid window configurations for X11 apps ()
* fix(xwayland): prevent invalid window configurations for X11 apps

* refact(xwayland): remove unneeded line
2025-01-31 13:26:08 +01:00
Ikalco
e6a9cfab91 monitor: preferred mode now tries first 3 modes if preferred fails before erroring () 2025-01-31 13:23:32 +01:00
nyx
ef03f69116 xwayland: handle window coords correctly () 2025-01-30 22:36:42 +01:00
Tom Englund
32c0fa2f2f core: begin using CFileDescriptor from hyprutils ()
* config: make fd use CFileDescriptor

make use of the new hyprutils CFileDescriptor instead of manual FD
handling.

* hyprctl: make fd use CFileDescriptor

make use of the new hyprutils CFileDescriptor instead of manual FD
handling.

* ikeyboard: make fd use CFileDescriptor

make use of the new CFileDescriptor instead of manual FD handling, also
in sendKeymap remove dead code, it already early returns if keyboard
isnt valid, and dont try to close the FD that ikeyboard owns.

* core: make SHMFile functions use CFileDescriptor

make SHMFile misc functions use CFileDescriptor and its associated usage
in dmabuf and keyboard.

* core: make explicit sync use CFileDescriptor

begin using CFileDescriptor in explicit sync and its timelines and
eglsync usage in opengl, there is still a bit left with manual handling
that requires future aquamarine change aswell.

* eventmgr: make fd and sockets use CFileDescriptor

make use of the hyprutils CFileDescriptor instead of manual FD and
socket handling and closing.

* eventloopmgr: make timerfd use CFileDescriptor

make the timerfd use CFileDescriptor instead of manual fd handling

* opengl: make gbm fd use CFileDescriptor

make the gbm rendernode fd use CFileDescriptor instead of manual fd
handling

* core: make selection source/offer use CFileDescriptor

make data selection source and offers use CFileDescriptor and its
associated use in xwm and protocols

* protocols: convert protocols fd to CFileDescriptor

make most fd handling use CFileDescriptor in protocols

* shm: make SHMPool use CfileDescriptor

make SHMPool use CFileDescriptor instead of manual fd handling.

* opengl: duplicate fd with CFileDescriptor

duplicate fenceFD with CFileDescriptor duplicate instead.

* xwayland: make sockets and fds use CFileDescriptor

instead of manual opening/closing make sockets and fds use
CFileDescriptor

* keybindmgr: make sockets and fds use CFileDescriptor

make sockets and fds use CFileDescriptor instead of manual handling.
2025-01-30 11:30:12 +00:00
Honkazel
7d1c78f4a3 core,hyprctl: clang, clang-tidy, typo fixes and dtors changes ()
* declare dtor once + DMABBUF typo fix

* dup include + clang moment

* linux-dmabuf: last minute nit change
2025-01-30 11:07:06 +00:00
Vaxry
d462cc7fa1 subsurface: fix invalid parent typo
fixes 
2025-01-29 23:16:25 +00:00
Vaxry
09ec1cca51 popup: stop refocusing at unmap
fixes 
2025-01-29 23:05:59 +00:00
Mihai Fufezan
6fc9c8e479 flake.lock: update 2025-01-29 22:45:38 +02:00
Vaxry
6131919715 monitor: round refresh rates in sorting modes
fixes 
2025-01-29 13:34:11 +00:00
vaxerski
aaa5573c73 config/hyprctl: fix keyword not updating autoreload
ref 
2025-01-29 10:50:39 +00:00
vaxerski
344e32d71b pass/rect: fix bounding / opaque regions
fixes 
2025-01-29 10:42:46 +00:00
Owen L.
d41135d07c input: change window grab cursor to closed hand () 2025-01-29 10:27:34 +01:00
nyx
b884f1f7c8 renderer: calculate UV using both pixel and monitor dimensions () 2025-01-29 09:41:56 +01:00
Vaxry
3d1dd6b5c7 presentation: log a fixme when there is a feedback leak
ref 
2025-01-28 23:43:31 +00:00
Tom Englund
1d3904c3e7 configmgr: properly free glob memory
globfree is only freeing internally allocated resources, so also call
free the on glob_t memory we allocated.
2025-01-28 11:19:52 +01:00
Tom Englund
529ad4eaf4 ikeyboard: free xkbSymState in clearManuallyAllocd
asan reported a leak on xkbSymState on destruction, because it wasnt
beeing unrefed, was only being unrefed on calls to updateXKBTranslationState.
2025-01-28 11:19:52 +01:00
DDoSolitary
d3042e5358 xwayland: respect window size set by configure requests () 2025-01-28 10:04:57 +00:00
Jan Beich
d2773d7a4e deps: add libinotify-kqueue on BSDs after 8dd2cd41fb ()
src/config/ConfigWatcher.cpp:2:10: fatal error: 'sys/inotify.h' file not found
    2 | #include <sys/inotify.h>
      |          ^~~~~~~~~~~~~~~
2025-01-27 23:06:48 +01:00
matt1432
5fd90548dc nix: fix duplicate inputs and update flake.lock 2025-01-27 22:45:18 +02:00
Mihai Fufezan
25d5ce4833 CI/setup_base: add libspng 2025-01-27 22:25:27 +02:00
DDoSolitary
e7a72de9b5 xwayland: send synthetic configure events () 2025-01-27 17:45:15 +01:00
littleblack111
cb7ed4f62b ci: clang-format fix ()
* move

* revert, comment via peter-evans/commit-comment@v3

* remove
2025-01-27 17:41:26 +01:00
Vaxry
2a478c30ca core: fix clang-format 2025-01-27 13:41:38 +00:00
Vaxry
04ac46c543 version: bump to 0.47.0 2025-01-27 11:43:43 +00:00
Vaxry
2f55806d6f renderer: fix rare case when a tiled window would be rendered over fs 2025-01-26 21:06:25 +00:00
user111111111111111111111111111111111
3b207d29bd core: update groups on movewindow () 2025-01-26 20:06:50 +01:00
Vaxry
6bd6c5512e hooksystem: avoid huge include for HANDLE 2025-01-26 18:38:35 +00:00
Vaxry
bb5b09def0 renderer: fix funky corners
oopsie
2025-01-26 16:00:29 +00:00
Vaxry
efe29a2461 shadow: avoid drawing empty shadows 2025-01-26 16:00:28 +00:00
Vaxry
e951011503 renderer/internal: stop using box pointers
in favor of const refs
2025-01-26 15:05:34 +00:00
Vaxry
16aeb24bc1 core: make persistent workspaces always follow the config
instead of just staying after open, they will now be enforced on their respective monitors

fixes 
2025-01-26 14:40:42 +00:00
Vaxry
74d0f34cf3 pointer: always scale the cpu cursor to the right size
fixes 
2025-01-26 14:25:54 +00:00
Maximilian Seidler
4abf9155ee window: only set m_iMonitorMovedFrom when the workspace is visible () 2025-01-26 14:00:52 +01:00
Vaxry
0a28e13787 desktop: move desktop types to memory-safe pointers 2025-01-26 12:54:38 +00:00
Maximilian Seidler
3cd6e3960f xwayland: fix pointer mismatches with multiple monitors () 2025-01-26 13:23:39 +01:00
Vaxry
8b1d6e3009 subsurface: damage the entire parent on size change
the previous method doesn't exactly work, idk todo fix

fixes 
2025-01-25 21:57:58 +00:00
Vaxry
445acec2a2 core: move sendWindowSize off of xwaylandmgr
additionally fixes that one weird x11 issue with floating windows being mis-sized on open
2025-01-25 20:36:44 +00:00
Vaxry
45c3787e75 window: revert only set m_iMonitorMovedFrom, when moving to a different monitor
This reverts commit 089fdd1ea0.

Great commit.
2025-01-25 19:37:52 +00:00
Junxuan Liao
9199a9746d input: pass touch events to lock screens ()
* refactor: use weak pointers for session lock surfaces

* input: pass touch events to lock screens
2025-01-25 19:44:13 +01:00
Vaxry
bce58d9d65 dwindle: fix possible crash on null ws 2025-01-25 18:34:15 +00:00
heather7283
f3fc8d599a config/ConfigWatcher.cpp: add missing include needed for clang () 2025-01-25 19:23:53 +01:00
Maximilian Seidler
107723bdf4 config: disable borderangle by default () 2025-01-25 19:23:36 +01:00
Mihai Fufezan
065e89648b flake.lock: update 2025-01-25 15:33:27 +02:00
Vaxry
354d4594de xwayland: various window handling fixes
I hate this fucking garbage
2025-01-25 02:35:41 +00:00
Maximilian Seidler
089fdd1ea0 window: only set m_iMonitorMovedFrom, when moving to a different monitor () 2025-01-25 00:27:24 +01:00
heather7283
1815f9a2e5 compositor: fix incorrect cast, use lambda capture instead () 2025-01-25 00:26:46 +01:00
Vaxry
d8f79d7678 core: add --verify-config to verify the config with Hyprland
fixes 
2025-01-24 20:30:21 +00:00
Maximilian Seidler
80b2fd135b animations: fix borderangle once () 2025-01-24 19:22:05 +01:00
Vaxry
fda9790cde layout: force full damage on toggling floating mode
fixes 
2025-01-24 17:51:16 +00:00
heather7283
9b3f71390c layershell: check if layer is valid ()
fixes compositor crash when client tried to create a layer surface with
invalid layer argument
2025-01-24 16:51:31 +01:00
Maximilian Seidler
0e5d03a557 datadevice: do the unfocus surface stuff before dndActive is true () 2025-01-24 16:50:15 +01:00
Vaxry
57a39984dd input: abord dnd op on escape pressed
ref 
2025-01-24 13:37:30 +00:00
Vaxry
944e36ea2e config: fix misc:disable_autoreload
fixes 
2025-01-24 13:23:23 +00:00
Vaxry
4a1b960cbe layout: set window size after toggling floating 2025-01-24 00:49:47 +00:00
Vaxry
5d8261aee2 xwayland: avoid sending value of real size to xwayland 2025-01-24 00:25:14 +00:00
Vaxry
d075d1cab9 pass/rect: fix bounding box 2025-01-23 22:59:42 +00:00
Vaxry
bb099e5733 pass/rect: fix box shrinking with bb
fixes 
2025-01-23 22:57:14 +00:00
Vaxry
dadb2e0949 opengl: use uv to avoid rendering the entire blurbox
additionally can use smoothing for rounding

fixes 
2025-01-23 22:41:46 +00:00
Vaxry
a8c2d5a616 layout: damage window properly on float mode changes
fixes 
2025-01-23 22:15:14 +00:00
outfoxxed
465cf66df1 protocols: add hyprland_surface_v1.set_visible_region implementation () 2025-01-23 22:57:33 +01:00
Vaxry
1a0a22ad03 configmgr: fix crash on very early plugin loads 2025-01-23 21:27:11 +00:00
Vaxry
0a1ae48a9f core: move all shared_ptrs from the STL to hyprutils () 2025-01-23 21:55:41 +01:00
Mihai Fufezan
ae403e6a05 flake.lock: update 2025-01-23 14:48:37 +02:00
Junxuan Liao
ae4e38d9d5 tablet: send motions on tip events ()
Typically, the position of the tool tip also changes on tool tip events, so we
should forward this update to the clients.
2025-01-23 12:35:13 +00:00
littleblack111
ecae3c5e4b ci: fix "Resource not accessible by integration" for cf workflow () 2025-01-23 12:32:44 +00:00
maround95
f1bd62806e core/renderer: Add GPU hotplug support () 2025-01-23 11:08:19 +00:00
DDoSolitary
fda5626594 xwayland: fix sending large clipboard data () 2025-01-22 14:27:46 +00:00
DDoSolitary
c90dbfab6f xwayland: fix clipboard mime name and atom mismatch () 2025-01-22 14:26:53 +00:00
Vaxry
d335c8f101 fractional-scale: avoid redundant and duplicate scale events
fixes 
2025-01-22 10:41:04 +00:00
amnesiacsardine
c8a0443adc config/ConfigManager.cpp: add instruction to edit config () 2025-01-22 12:16:46 +02:00
Christoph Hrdinka
ce48bc5408 flake.lock: update () 2025-01-21 21:21:00 +02:00
Alexander
da6e966313 keybinds: add visible arg for cyclenext ()
Co-authored-by: Крылов Александр <aleksandr.krylov@hyperus.team>
2025-01-21 14:17:07 +00:00
Zach DeCook
a661203bb6 xwayland: fix crash when trying to initialize without Xwayland installed () 2025-01-20 18:40:51 +00:00
DDoSolitary
9e8d9791c7 xwayland: support sending clipboard change notification on focus () 2025-01-20 17:53:29 +00:00
Maximilian Seidler
2d82a92324 config: fix float animation speeds < 0 () 2025-01-20 16:48:04 +00:00
Maximilian Seidler
407453166c protocols: add hyprland_lock_notify_v1 implementation () 2025-01-19 18:21:36 +00:00
vaxerski
8dd2cd41fb core: move to inotify for monitoring the config files
instead of manually polling every second which is not efficient, use inotify.

an added bonus is that inotify is much much faster
2025-01-19 16:11:12 +01:00
vaxerski
0a0e56d99c core: use readFileAsString instead of cat for os-release 2025-01-19 14:04:59 +01:00
outfoxxed
086fd7ece8 protocols: do not destroy screencopy resources before client request () 2025-01-19 12:51:42 +00:00
Tom Englund
4da9b7cc5b core: reserve vector sizes as much as we can ()
avoid reallocations as much as possible with a few edge cases where the
reservation overshoots a tiny bit. but a few bytes of memory short term
is better used then the overhead of potential reallocation.
2025-01-19 10:38:42 +00:00
Charlie Root
f56153a9c1 nix/module.nix: expand nixos module for configuring hyprland
Expand the nixos module to be able to configure hyprland, just like the
current home-manager module does.
2025-01-18 12:48:38 +02:00
Mihai Fufezan
a36fa5c229 Nix: disable hyprpm
It does not work properly on Nix anyway. If you were using hyprpm in
some way before, please switch to using the `plugins` option in the
HM module (and the upcoming option in the NixOS module, soon™).
2025-01-18 11:39:44 +02:00
Mihai Fufezan
fdfcfc824e CMake, Meson: add option controlling hyprpm building 2025-01-18 11:39:44 +02:00
Mihai Fufezan
d01756c1f4 Meson: properly install 'hyprland' symlink () 2025-01-18 10:59:25 +02:00
UjinT34
078e13f463 renderer: Auto enable wide color gamut in HDR mode () 2025-01-17 20:23:57 +00:00
vaxerski
47d645d84a core: fixup includes 2025-01-17 19:14:55 +01:00
Beau
b65f8a8723 desktop/DesktopTypes.hpp: fix include () 2025-01-17 18:56:43 +01:00
vaxerski
401a3bae61 core: fix warning in shadow 2025-01-17 18:56:43 +01:00
vaxerski
76a899627e regex: log an error if regex parsing fails 2025-01-17 18:56:43 +01:00
vaxerski
b7a3c45269 core: add LIKELY and UNLIKELY macros
helps the compiler optimize
2025-01-17 18:56:39 +01:00
user111111111111111111111111111111111
2bad73354a core: cleanup header includes ()
Cleanup some unneeded includes to speed up compilation
2025-01-17 15:21:35 +00:00
Vaxry
0dc7367a70 renderer: use cairo for cpu buffer rendering ()
Instead of a wonky memcpy which doesn't work when anything non-standard is used (size, pos, transform), just use cairo
2025-01-16 14:42:39 +00:00
UjinT34
52b9ae592b renderer: fix fullscreen hdr check () 2025-01-16 13:13:47 +01:00
vaxerski
25add26881 renderer: unload background texture if it's disabled
ref 
2025-01-14 17:52:32 +01:00
outfoxxed
f16f170433 protocols: immediately copy toplevel content when ignoreDamage set ()
Also sets m_ignoreDamage, as it wasn't set before.
2025-01-14 16:44:09 +00:00
outfoxxed
a6b263713a protocols: allow hyprland-toplevel-export to capture hidden windows () 2025-01-12 17:10:36 +00:00
outfoxxed
4f0f512cab protocols: do not capture cursor in toplevel without pointer focus () 2025-01-12 17:09:02 +00:00
UjinT34
a3a7499317 renderer: Do not set hdr metadata unless needed () 2025-01-12 17:02:41 +00:00
staz
b117fae3b4 keybinds: fix movefocus fallback for special workspaces () 2025-01-12 17:00:56 +00:00
Toria
2671656a75 helpers/Monitor.cpp: fix include path () 2025-01-12 18:35:10 +02:00
Maximilian Seidler
2778aff08f animations: fix XWayland cursor glitch and refactor skill issues () 2025-01-11 16:38:04 +00:00
Maximilian Seidler
9e4f90aedf animation: fixup adding animvars during ::tick () 2025-01-11 16:35:57 +00:00
Alexander
15dc024a39 keybinds: fix previous_per_monitor logic ()
Co-authored-by: Крылов Александр <aleksandr.krylov@hyperus.team>
2025-01-11 16:05:53 +00:00
littleblack111
3b85690aa6 config: add exec(-onec) with rules and execr(-once) () 2025-01-11 15:58:05 +00:00
vaxerski
cef09fbfe6 animation: avoid crashes in ::tick() on mutations
mutating the active variables is valid during a tick, we can't let it crash
2025-01-10 21:55:19 +01:00
UjinT34
a8b568c6c4 core: Add render:allow_early_buffer_release to make buffer release configurable () 2025-01-10 18:42:26 +00:00
Vaxry
b5fb6110ab core: Add a periodic donation request ()
Will fire once in december and july. Disableable with `ecosystem:no_donation:nag`
2025-01-10 18:09:40 +00:00
littleblack111
da9252a23e keybinds: fix nullptr deref in forcekillactive () 2025-01-10 15:16:52 +00:00
vaxerski
8475a8ef99 core: always use goal size to send to clients 2025-01-10 14:09:16 +01:00
Dardo D Kleiner
f9c37ca43b windows: honor xdg_toplevel_set_fullscreen output hint ()
Co-authored-by: Dardo D Kleiner <dardo.kleiner@nrl.navy.mil>
2025-01-09 22:38:38 +00:00
vaxerski
9dc9366fc6 config: fix animations requiring all args
fixes 
2025-01-09 22:08:16 +01:00
littleblack111
85aba23cbe ci(clang-format): directly do the clang-format instead of error ()
Will suggest a clang-format fix on every MR that violates it.
2025-01-09 20:54:25 +00:00
zakk4223
2d1ebadb9b selectors: add a tag: to for matching window tag(s) by regex () 2025-01-09 20:52:26 +00:00
Maximilian Seidler
e66eab7b6a animationmgr: don't warp based on POINTY value () 2025-01-09 11:13:47 +00:00
littleblack111
c9822b08f9 keybinds: add new window destruction dispatchers () 2025-01-08 21:16:34 +00:00
vaxerski
983bc067da opengl: fix crash on null fb stencil op 2025-01-08 12:34:43 +01:00
vaxerski
b320bc2dc6 core: use cpu-buffer hw cursors on nvidia by default 2025-01-08 12:29:24 +01:00
vaxerski
ad64726f5d opengl: only allocate offMainFB on demand
These are not used by hyprland, only potentially plugins. No need to keep the VRAM allocated all the time
2025-01-08 11:33:45 +01:00
Maximilian Seidler
5fa2594659 renderer: don't access hdrMetadata optional if it has no value () 2025-01-08 10:28:55 +00:00
staz
75727e7c17 protocols: fix compilation error () 2025-01-08 10:28:47 +00:00
vaxerski
67e1e46f9b window: fixup fade out animation on silent moves 2025-01-07 20:58:49 +01:00
vaxerski
308b1f3afb core: minor fixes for animation rewrite 2025-01-07 20:30:44 +01:00
vaxerski
c143907857 config: update animation even if disabled 2025-01-07 20:27:07 +01:00
UjinT34
830350a1f7 core: Add support for HDR and color management protocols () 2025-01-07 18:32:50 +00:00
vaxerski
95542e4488 animationmgr: fix invalid include 2025-01-07 19:13:35 +01:00
Maximilian Seidler
5642ed331d core: move parts of the animation system to hyprutils ()
* core: change animation manager to use Hyprutils::Animation

* config: move animation config to hyprutils animation tree

* use g_pAnimationManager->createAnimation and the new PHLANIMVAR template

* core: use CGenericAnimatedVariabled::{enabled,setConfig,getStyle} and adapt callbacks

* core: adapt animated variable usage (dereference the shared pointer)

* misc: bump CMakeLists to hyprutils 0.3.3
2025-01-07 17:55:14 +00:00
Zetta1 Reid0
c7086f936a master: add option to show slaves on left in center orientation ()
Co-authored-by: Zetta1_Reid0 <11255-Zetta1_Reid0@users.noreply.gitlab.xfce.org>
2025-01-07 17:50:34 +00:00
Pollux
b9f110ef87 shaders: fix blank windows when using corner rounding () () 2025-01-06 19:30:57 +00:00
Byso
1bf4937b02 hyprctl: fix hyprctl --batch not working with exec rules () 2025-01-06 16:52:59 +00:00
UjinT34
6a90b50545 core/compositor: fix too early buffer release () 2025-01-06 16:52:35 +00:00
Mike Will
602d6b7356 snap: don't snap to any windows if workspace has a fullscreen window () 2025-01-06 16:37:13 +00:00
vaxerski
780e3dd542 [gha] Nix: update inputs 2025-01-06 14:29:29 +00:00
vaxerski
f1a7a7497e datadevice: guard XWayland server against crashes
The server might be dead or restarting, and we'd deref null

ref 
2025-01-06 15:27:57 +01:00
vaxerski
f390f48a07 pass: fixup debug mode rendering of input boxes 2025-01-05 23:19:13 +01:00
vaxerski
1b06d222cf pass/surface: fixup invalid expansion by old -1 rounding param
fixes 
2025-01-05 20:35:24 +01:00
vaxerski
391ff29110 pass: improve pass debug mode 2025-01-05 19:49:31 +01:00
Pollux
a5c14370c1 renderer: Add supercircular window corners ()
renderer: Add supercircular shadows and borders

config: Add rounding_power to default and example configs

rule: add `roundingpower` window rule
2025-01-05 18:38:49 +00:00
Sergio Miguéns
b0bae15499 master: make loop around optional when cycling () 2025-01-04 16:40:33 +00:00
vaxerski
e8317ae34d xwayland: don't define atoms on no_xwayland builds
fixes 
2025-01-04 17:35:11 +01:00
vaxerski
a25d228840 windows: minor initial workspace improvements
ref 
2025-01-04 17:21:02 +01:00
vaxerski
60f069d540 groupbar: fix missing ellipsize for text
fixes 
2025-01-04 00:25:01 +01:00
vaxerski
2e6e0e9278 core: guard workspace and monitor in moveWorkspaceToMonitor
ref 
2025-01-04 00:10:21 +01:00
davc0n
f69e72eca1 socket2: add focusedmonv2 event ()
* socket2: add focusedmonv2 event

* socket2: remove workspace name from focusedmonv2
2025-01-03 22:43:48 +00:00
rszyma
b0cd9972e8 github: bug issue template improvements () 2025-01-03 12:47:34 +00:00
vaxerski
f3c49c1295 pass: ignore empty rectangles queued
ref 
2025-01-02 21:58:23 +01:00
vaxerski
4971725b69 config: avoid querying the fs every tick 2025-01-02 18:29:10 +01:00
vaxerski
1697171fc0 pass: allow removing all pass elements of a given type 2025-01-02 18:14:45 +01:00
vaxerski
81721b8aa8 groupbar: unify title rendering
moves the text renderer to the unified opengl impl
2025-01-02 17:50:55 +01:00
vaxerski
365caa49ff keybinds: attempt to wrap around if fallback is allowed in movefocus
fixes 
2025-01-02 17:36:02 +01:00
vaxerski
94381e5999 dnd/seat: avoid sending button events during a dnd op
fixes 
2025-01-02 15:58:28 +01:00
outfoxxed
42fd366046 protocols: fix alpha-modifier noncompliance ()
Also fixes small issues with hyprland-surface opacity < 1.0 while
surface alpha = 1.0.
2025-01-02 11:53:57 +00:00
Raymond Bian
cbd2451570 renderer: fix CRendererHintsPassElement reset duplication issue () 2025-01-02 11:51:59 +00:00
outfoxxed
dde3e082c9 protocols: add hyprland_surface_v1 implementation () 2025-01-01 23:34:02 +00:00
vaxerski
9f3c9ac01a pass: mark crucial elements as undiscardable 2025-01-01 22:55:21 +01:00
vaxerski
7f177fa1cf layershell: return focus to a valid ls on close if possible 2025-01-01 17:28:04 +01:00
vaxerski
d64ac47202 pass: improve pass debugging 2025-01-01 17:26:05 +01:00
vaxerski
4e93b2def5 renderer: use a render pass for render modif in client render
fixes 
2025-01-01 14:11:21 +01:00
vaxerski
aff34089c4 monitor: avoid crashes on no good modes
fixes 
2025-01-01 13:47:16 +01:00
davc0n
1989b0049f hyprpm: add glaze dependency FetchContent fallback ()
Use FetchContent to retrieve glaze dependency if not available with
find_package.
Allows to build hyprpm w/o installing glaze at system level (on some distros is
not available in official repositories).
2024-12-30 19:12:29 +00:00
vaxerski
9f933da1c5 renderer: fix oversized blur precalcs not blurring at all
fixes 
2024-12-30 20:00:45 +01:00
Ikalco
af301312d5 core: fix custom resolutions () 2024-12-30 18:45:42 +00:00
vaxerski
8c14c2a5f4 ctm: disable fade animation by default on nvidia
Fixes , the nvidia driver dies when we spam CTM too much.
2024-12-30 11:58:35 +01:00
Ikalco
cb211d83f6 internal: few small monitor improvements ()
* remove framesToSkip, its not used anymore

* only set drm format when needed after leaving direct scanout

* always set drm damage
2024-12-29 23:19:12 +00:00
khushal-solves
fde569db65 master: replace always_center_master with slave_count_for_center_master ()
- Replaced boolean config parameter `always_center_master` to an integer value `slave_count_for_center_master` for more control on when to center master window.
- Also updated configuration parameter, type, description and related internal variable.

New 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)**

This effects center orientation of master layout.
Now, user can control when the master window would be centered
2024-12-29 23:11:16 +00:00
Tuur Vanhoutte
5b37d53992 hyprpm: add an option to force reload all plugins () 2024-12-29 23:09:17 +00:00
vaxerski
deb077c346 ctm: add an internal fade animation to ctm transitions 2024-12-29 19:21:20 +01:00
vaxerski
3f40d6d936 pass: scale blur region in ::render 2024-12-29 12:56:58 +01:00
Mike Will
a364e80425 snap: give edge snapping precedence over corner snapping () 2024-12-29 11:18:14 +00:00
Clayton
2b01a5bcf6 xwayland: don't create an abstract unix domain socket on linux () 2024-12-28 14:07:42 +00:00
Ikalco
8d4c18d723 core: refactor/improve monitor mode selection () 2024-12-28 13:32:04 +00:00
vaxerski
c600e1aaff [gha] Nix: update inputs 2024-12-27 21:51:12 +00:00
Mike Will
cca0adf6a3 snap: revert , use bounds checking instead of bit mask ()
They seem like the same check, but a snap bit might not be
turned on as expected if we grab a different corner after
an edge has already been snapped.
2024-12-27 21:49:45 +00:00
vaxerski
534adad6b1 pass: scale blur regions properly 2024-12-27 17:49:05 +01:00
user111111111111111111111111111111111
775111b603 foreign-toplevel: update active on null window focus ()
* update ForeignTopLevel on null window focus

* fix

* clang
2024-12-27 15:20:55 +00:00
khushal-solves
85632e7c33 internal: update window position/size after changing fullscreenstate ()
This effects fullscreen group of floating windows.
2024-12-27 15:20:38 +00:00
Tuur Vanhoutte
43ca66779b hyprpm: use glaze to parse hyprctl plugin list ()
* Use std::filesystem::path in hyprpm DataState to avoid concatenating strings with (folder + "/" + file)
* Added getPluginStates helper method in DataState
* Small clang-tidy improvements
2024-12-27 14:40:46 +00:00
vaxerski
e75e2cdac7 functionHooks: wait for hyprland pages before returning addr for trampo
ref 
2024-12-26 11:31:59 +01:00
vaxerski
2eaa4d80a0 debug: fix overlay not rendering 2024-12-26 00:16:42 +01:00
Avi Herman
dddb64dc35 internal: added reference to CTimer class in KeybindManager () 2024-12-25 19:09:45 +00:00
staz
1a3d17da91 debug: fix ISDEBUG checking () 2024-12-25 16:33:10 +01:00
vaxerski
2a24a61126 pass: improve blur region detection 2024-12-25 00:57:55 +01:00
vaxerski
2e2e2e2cad monitor: bring back old description behavior
this makes description match the beginning again. Fixes 
2024-12-23 21:17:38 +00:00
Mihai Fufezan
68a5842f06 Nix: fix TAG substitution 2024-12-23 20:07:12 +02:00
Mihai Fufezan
5f7ad767db flake.lock: update 2024-12-23 00:39:17 +02:00
Vaxry
a4a4fffffb renderer: allow plugins to know what window was rendered in post 2024-12-22 22:07:08 +00:00
Vaxry
1830296df3 debug: add debug:pass for debugging the render pass 2024-12-22 17:20:33 +00:00
Vaxry
e536b02248 Renderer: rewrite render scheduling ()
This rewrites renderer scheduling. Occlusion is now unified in a new Pass type.
2024-12-22 17:12:09 +01:00
Vaxry
1cc1a46c2e core: fade in windows when they are brought from invisible workspaces 2024-12-22 16:04:10 +00:00
Vaxry
31422ae25d windowrules: add negative: prefix for negating a regex
fixes 
2024-12-21 23:07:34 +00:00
Vaxry
57921d7dbd windowrules: precompute regexes for window/layer rules 2024-12-21 19:21:50 +00:00
Aqa-Ib
8e8073c421 groups: honor group lock window rule () 2024-12-21 17:35:47 +01:00
Vaxry
52ee7a8748 data-device: fix edge case crash on null xwm
fixes 
2024-12-21 15:08:00 +00:00
Vaxry
71dc9f6128 pluginAPI: add register dispatcher v2 2024-12-19 20:23:32 +00:00
Aqa-Ib
fd67ee9ecd layout: apply group rules after window creation ()
* apply group rules after window creation

* clang-format
2024-12-19 20:22:40 +01:00
Vaxry
bec18dc6f9 core: fix possible crash on null active workspace
fixes 
2024-12-19 19:11:15 +00:00
Ikalco
5b714f05f8 internal: Make static analysis more happy ()
* make functions used in 1 file static

* fix invalid substr param -1

* give default initializer to borderGradier

* move RASSERT from printf to std::print
2024-12-19 02:56:01 +01:00
Aaron Blasko
7c43eed2c1 hyprpm: fix hyrpm sometimes returning 0 despite errors occuring ()
* hyprpm: fix hyrpm sometimes returning 0 despite errors occuring

* there u go
2024-12-19 01:33:57 +01:00
Kamikadze
23e7d8f6a7 logging: fix Gpu info () 2024-12-19 00:08:57 +01:00
Jan Beich
df06cb4d71 core: Unbreak build on FreeBSD ()
* CrashReporter: restore explicit environ(7) after 8bbeee1173

BSD systems don't declare environ(7) in any header like POSIX suggests.

src/signal-safe.cpp:12:23: error: use of undeclared identifier 'environ'
   12 |     for (char** var = environ; *var != nullptr; var++) {
      |                       ^

* fix

---------

Co-authored-by: Vaxry <vaxry@vaxry.net>
2024-12-18 20:15:44 +01:00
Vaxry
0fec38fe72 xwayland: fix compile with no xwayland 2024-12-18 19:09:02 +00:00
Vaxry
80a4852266 xwm: fixup targets in selection requests
fixes 
2024-12-18 15:24:37 +00:00
Vaxry
4c4471c66d xwayland: fix dnd including xwayland
ref 
2024-12-18 15:14:37 +00:00
Vaxry
6378c8ed65 core: avoid activating toplevel-less surfaces
ref 
2024-12-18 15:01:12 +00:00
Ikalco
49e5f9c428 logging: get broader GPUINFO () 2024-12-18 15:56:53 +01:00
Khalid
5f1df55fcb hyprctl: Fix hyprctl batch JSON command () 2024-12-17 18:26:02 +01:00
Ikalco
e2c78c00e5 internal: remove dead code () 2024-12-17 17:31:27 +01:00
normaltaro
bd7092a9fe binds: cycle within group on single monitor if no window found in the argument direction. () 2024-12-17 17:12:38 +01:00
Mihai Fufezan
c7d9719910 flake.lock: update 2024-12-17 10:20:33 +02:00
Austin Horstman
7ae7920572 flake.lock: update () 2024-12-17 03:15:39 +01:00
Vaxry
4b6163aef3 windowrules: fixup duplicate rule enum tags
fixes 
2024-12-17 01:57:35 +00:00
476 changed files with 39110 additions and 23265 deletions
.github
.gitignoreCMakeLists.txtMakefileREADME.mdSECURITY.mdVERSION
assets
docs
example
flake.lockflake.nix
hyprctl
hyprpm
hyprtester
meson.buildmeson_options.txt
nix
protocols
scripts
src
Compositor.cppCompositor.hppSharedDefs.hpp
config
debug
defines.hpp
desktop
devices
events
helpers
hyprerror
includes.hpp
layout
macros.hppmain.cpp
managers
meson.build
pch
plugins
protocols
AlphaModifier.cppAlphaModifier.hppCTMControl.cppCTMControl.hppColorManagement.cppColorManagement.hppContentType.cppContentType.hppCursorShape.cppCursorShape.hppDRMLease.cppDRMLease.hppDRMSyncobj.cppDRMSyncobj.hppDataDeviceWlr.cppDataDeviceWlr.hppExtWorkspace.cppExtWorkspace.hppFocusGrab.cppFocusGrab.hppForeignToplevel.cppForeignToplevel.hppForeignToplevelWlr.cppForeignToplevelWlr.hppFractionalScale.cppFractionalScale.hppFrogColorManagement.cppFrogColorManagement.hppGammaControl.cppGammaControl.hppGlobalShortcuts.cppGlobalShortcuts.hppHyprlandSurface.cppHyprlandSurface.hppIdleInhibit.cppIdleInhibit.hppIdleNotify.cppIdleNotify.hppInputMethodV2.cppInputMethodV2.hppLayerShell.cppLayerShell.hppLinuxDMABUF.cppLinuxDMABUF.hppLockNotify.cppLockNotify.hppMesaDRM.cppMesaDRM.hppOutputManagement.cppOutputManagement.hppOutputPower.cppOutputPower.hppPointerConstraints.cppPointerConstraints.hppPointerGestures.cppPointerGestures.hppPresentationTime.cppPresentationTime.hppPrimarySelection.cppPrimarySelection.hppRelativePointer.cppRelativePointer.hppScreencopy.cppScreencopy.hppSecurityContext.cppSecurityContext.hppServerDecorationKDE.cppServerDecorationKDE.hppSessionLock.cppSessionLock.hppShortcutsInhibit.cppShortcutsInhibit.hppSinglePixel.cppSinglePixel.hppTablet.cppTablet.hppTearingControl.cppTearingControl.hppTextInputV1.cppTextInputV1.hppTextInputV3.cppTextInputV3.hppToplevelExport.cppToplevelExport.hppToplevelMapping.cppToplevelMapping.hppViewporter.cppViewporter.hppVirtualKeyboard.cppVirtualKeyboard.hppVirtualPointer.cppVirtualPointer.hppWaylandProtocol.cppWaylandProtocol.hppXDGActivation.cppXDGActivation.hppXDGBell.cppXDGBell.hppXDGDecoration.cppXDGDecoration.hppXDGDialog.cppXDGDialog.hppXDGOutput.cppXDGOutput.hppXDGShell.cppXDGShell.hppXDGTag.cppXDGTag.hppXWaylandShell.cppXWaylandShell.hppXXColorManagement.cppXXColorManagement.hpp
core
types
render
signal-safe.cpp
xwayland
subprojects
systemd

@@ -1,75 +1,15 @@
name: Bug Report
description: Something is not working right
labels: ["bug"]
name: Do not open issues, go to discussions please!
description: Do not open an issue
body:
- type: checkboxes
attributes:
label: Already reported ? *
description: Before opening a new bug report, please take a moment to search through the current open and closed issues to check if it already exists.
label: Please close this issue.
description: Users cannot open issues. I want my issue to be closed.
options:
- label: I have searched the existing open and closed issues.
required: true
- type: dropdown
id: type
attributes:
label: Regression?
description: |
Regression means that something used to work but no longer does.
**BEFORE CONTINUING**, please check if this bug is a regression or not, and if it is, we need you to bisect with the help of the wiki: https://wiki.hyprland.org/Crashes-and-Bugs/#bisecting-an-issue
multiple: true
options:
- "Yes"
- "No"
validations:
- label: Yes, I want this issue to be closed.
required: true
- type: textarea
id: ver
id: body
attributes:
label: System Info and Version
description: |
Paste the output of `hyprctl systeminfo -c` here. If you can't
launch Hyprland, paste the output of `Hyprland --systeminfo`.
If `Hyprland --systeminfo` errors out (added in 0.44.0), find
and paste the Hyprland version manually.
value: "<details>
<summary>System/Version info</summary>
```sh
<Paste the output of the command here>
```
</details>"
validations:
required: true
- type: textarea
id: desc
attributes:
label: Description
description: "What went wrong?"
validations:
required: true
- type: textarea
id: repro
attributes:
label: How to reproduce
description: "How can someone else reproduce the issue?"
validations:
required: true
- type: textarea
id: logs
attributes:
label: Crash reports, logs, images, videos
description: |
Anything that can help. Please always ATTACH and not paste them.
Logs can be found in $XDG_RUNTIME_DIR/hypr
Crash reports are stored in ~/.cache/hyprland or $XDG_CACHE_HOME/hyprland
label: Issue body

@@ -1,19 +0,0 @@
name: Feature Request
description: I'd like to request additional functionality
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Before opening a new issue, take a moment to search through the current open ones.
---
- type: textarea
id: desc
attributes:
label: Description
description: "Describe your idea"
validations:
required: true

@@ -20,6 +20,7 @@ runs:
clang \
cmake \
git \
glaze \
glm \
glslang \
go \
@@ -35,6 +36,7 @@ runs:
libinput \
libjxl \
libliftoff \
libspng \
libwebp \
libxcursor \
libxcvt \

@@ -21,7 +21,7 @@ jobs:
- name: Build Hyprland
run: |
make all
CFLAGS=-Werror CXXFLAGS=-Werror make all
- name: Compress and package artifacts
run: |
@@ -107,6 +107,7 @@ jobs:
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)"
runs-on: ubuntu-latest

48
.github/workflows/clang-format.yml vendored Normal file

@@ -0,0 +1,48 @@
name: clang-format
on: pull_request_target
jobs:
clang-format:
permissions: write-all
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
name: "Code Style (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: clang-format check
run: ninja -C build clang-format-check
- name: clang-format apply
if: ${{ failure() && github.event_name == 'pull_request' }}
run: ninja -C build clang-format
- name: Create patch
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
- name: Comment patch
if: ${{ failure() && github.event_name == 'pull_request' }}
uses: mshick/add-pr-comment@v2
with:
message-path: |
clang-format.patch

101
.github/workflows/close-issues.yml vendored Normal file

@@ -0,0 +1,101 @@
name: Close Unauthorized Issues
on:
workflow_dispatch:
issues:
types: [opened]
jobs:
close-unauthorized-issues:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
# XXX: This *could* be done in Bash by abusing GitHub's own tool to interact with its API
# but that's too much of a hack, and we'll be adding a layer of abstraction. github-script
# is a workflow that eases interaction with GitHub API in the workflow run context.
- name: "Close 'unauthorized' issues"
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const ALLOWED_USERS = ['vaxerski', 'fufexan', 'NotAShelf'];
const CLOSING_COMMENT = 'Users are no longer allowed to open issues themselves, please open a discussion instead.\n\nPlease see the [wiki](https://wiki.hyprland.org/Contributing-and-Debugging/Issue-Guidelines/) on why this is the case.\n\nWe are volunteers, and we need your cooperation to make the best software we can. Thank you for understanding! ❤️\n\n[Open a discussion here](https://github.com/hyprwm/Hyprland/discussions)';
async function closeUnauthorizedIssue(issueNumber, userName) {
if (ALLOWED_USERS.includes(userName)) {
console.log(`Issue #${issueNumber} - Created by authorized user ${userName}`);
return;
}
console.log(`Issue #${issueNumber} - Unauthorized, closing`);
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
state: 'closed',
state_reason: 'not_planned'
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: CLOSING_COMMENT
});
}
if (context.eventName === 'issues' && context.payload.action === 'opened') {
// Direct access to the issue that triggered the workflow
const issue = context.payload.issue;
// Skip if this is a PR
if (issue.pull_request) {
console.log(`Issue #${issue.number} - Skipping, this is a pull request`);
return;
}
// Process the single issue that triggered the workflow
await closeUnauthorizedIssue(issue.number, issue.user.login);
} else {
// For manual runs, we need to handle pagination
async function* fetchAllOpenIssues() {
let page = 1;
let hasNextPage = true;
while (hasNextPage) {
const response = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100,
page: page
});
if (response.data.length === 0) {
hasNextPage = false;
} else {
for (const issue of response.data) {
yield issue;
}
page++;
}
}
}
// Process issues one by one
for await (const issue of fetchAllOpenIssues()) {
try {
// Skip pull requests
if (issue.pull_request) {
console.log(`Issue #${issue.number} - Skipping, this is a pull request`);
continue;
}
await closeUnauthorizedIssue(issue.number, issue.user.login);
} catch (error) {
console.error(`Error processing issue #${issue.number}: ${error.message}`);
}
}
}

@@ -1,29 +0,0 @@
name: Nix (Build)
on:
workflow_call:
secrets:
CACHIX_AUTH_TOKEN:
required: false
jobs:
build:
strategy:
matrix:
package:
- hyprland
# - hyprland-cross # cross compiling fails due to qt
# failure chain: hyprland-qtutils -> qt6.qtsvg -> qt6.qtbase -> psqlodbc & qt6.qttranslations
- xdg-desktop-portal-hyprland
runs-on: ubuntu-latest
steps:
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- uses: cachix/cachix-action@v15
with:
name: hyprland
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- run: nix build 'github:hyprwm/Hyprland?ref=${{ github.ref }}#${{ matrix.package }}' -L --extra-substituters "https://hyprland.cachix.org"

@@ -1,4 +1,4 @@
name: Nix (CI)
name: Nix
on: [push, pull_request, workflow_dispatch]
@@ -8,7 +8,23 @@ jobs:
uses: ./.github/workflows/nix-update-inputs.yml
secrets: inherit
build:
hyprland:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)
uses: ./.github/workflows/nix-build.yml
uses: ./.github/workflows/nix.yml
secrets: inherit
with:
command: nix build 'github:hyprwm/Hyprland?ref=${{ github.ref }}' -L --extra-substituters "https://hyprland.cachix.org"
xdph:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)
needs: hyprland
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"
test:
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)
needs: hyprland
uses: ./.github/workflows/nix-test.yml
secrets: inherit

59
.github/workflows/nix-test.yml vendored Normal file

@@ -0,0 +1,59 @@
name: Nix (Test)
on:
workflow_call:
secrets:
CACHIX_AUTH_TOKEN:
required: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Install Nix
uses: nixbuild/nix-quick-install-action@v31
with:
nix_conf: |
keep-env-derivations = true
keep-outputs = true
- 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 }}
# 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:
name: hyprland
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"
- name: Check exit status
run: grep 0 result/exit_status
- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: logs
path: result

@@ -17,7 +17,36 @@ jobs:
with:
token: ${{ secrets.PAT }}
- uses: DeterminateSystems/nix-installer-action@main
- name: Install Nix
uses: nixbuild/nix-quick-install-action@v31
with:
nix_conf: |
keep-env-derivations = true
keep-outputs = true
- 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') }}
# 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: 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
- name: Update inputs
run: nix/update-inputs.sh

53
.github/workflows/nix.yml vendored Normal file

@@ -0,0 +1,53 @@
name: Build
on:
workflow_call:
inputs:
command:
required: true
type: string
description: Command to run
secrets:
CACHIX_AUTH_TOKEN:
required: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Install Nix
uses: nixbuild/nix-quick-install-action@v31
with:
nix_conf: |
keep-env-derivations = true
keep-outputs = true
- 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 }}
# 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:
name: hyprland
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- run: ${{ inputs.command }}

@@ -1,28 +0,0 @@
# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
#
# You can adjust the behavior by modifying this file.
# For more information, see:
# https://github.com/actions/stale
name: Mark stale issues and pull requests
on:
schedule:
- cron: "7 */4 * * *"
workflow_dispatch:
jobs:
stale:
if: github.repository == 'hyprwm/Hyprland'
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.STALEBOT_PAT }}
stale-issue-label: "stale"
stale-pr-label: "stale"
operations-per-run: 40
days-before-close: -1

2
.gitignore vendored

@@ -28,6 +28,8 @@ protocols/*.c*
protocols/*.h*
.ccls-cache
*.so
src/render/shaders/*.inc
src/render/shaders/Shaders.hpp
hyprctl/hyprctl

@@ -9,6 +9,7 @@ project(
DESCRIPTION "A Modern C++ Wayland Compositor"
VERSION ${VER})
include(CTest)
include(CheckIncludeFile)
include(GNUInstallDirs)
@@ -25,6 +26,9 @@ 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})
find_package(PkgConfig REQUIRED)
@@ -77,14 +81,18 @@ add_compile_definitions(HYPRLAND_VERSION="${HYPRLAND_VERSION}")
include_directories(. "src/" "protocols/")
set(CMAKE_CXX_STANDARD 26)
set(CXX_STANDARD_REQUIRED ON)
add_compile_options(
-Wall
-Wextra
-Wpedantic
-Wno-unused-parameter
-Wno-unused-value
-Wno-missing-field-initializers
-Wno-gnu-zero-variadic-macro-arguments
-Wno-narrowing
-Wno-pointer-arith
-Wno-clobbered
-fmacro-prefix-map=${CMAKE_SOURCE_DIR}/=)
set(CMAKE_EXECUTABLE_ENABLE_EXPORTS TRUE)
@@ -94,20 +102,24 @@ message(STATUS "Checking deps...")
find_package(Threads REQUIRED)
if(LEGACY_RENDERER)
set(GLES_VERSION "GLES2")
else()
set(GLES_VERSION "GLES3")
endif()
set(GLES_VERSION "GLES3")
find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION})
pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=0.4.5)
pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=0.9.0)
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.2.3)
pkg_check_modules(hyprgraphics_dep REQUIRED IMPORTED_TARGET hyprgraphics>=0.1.1)
pkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=0.8.1)
pkg_check_modules(hyprgraphics_dep REQUIRED IMPORTED_TARGET hyprgraphics>=0.1.3)
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}")
@@ -120,14 +132,14 @@ pkg_check_modules(
xkbcommon
uuid
wayland-server>=1.22.90
wayland-protocols
wayland-protocols>=1.43
cairo
pango
pangocairo
pixman-1
xcursor
libdrm
libinput
libinput>=1.28
gbm
gio-2.0
re2)
@@ -144,7 +156,7 @@ endif()
add_executable(Hyprland ${SRCFILES} ${TRACY_CPP_FILES})
set(USE_GPROF ON)
set(USE_GPROF OFF)
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
message(STATUS "Setting debug flags")
@@ -197,9 +209,10 @@ if(NOT HAS_TIMERFD AND epoll_FOUND)
target_link_libraries(Hyprland PkgConfig::epoll)
endif()
if(LEGACY_RENDERER)
message(STATUS "Using the legacy GLES2 renderer!")
add_compile_definitions(LEGACY_RENDERER)
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)
endif()
if(NO_XWAYLAND)
@@ -248,7 +261,15 @@ target_precompile_headers(Hyprland PRIVATE
message(STATUS "Setting link libraries")
target_link_libraries(Hyprland rt PkgConfig::aquamarine_dep PkgConfig::hyprlang_dep PkgConfig::hyprutils_dep PkgConfig::hyprcursor_dep PkgConfig::hyprgraphics_dep PkgConfig::deps)
target_link_libraries(
Hyprland
rt
PkgConfig::aquamarine_dep
PkgConfig::hyprlang_dep
PkgConfig::hyprutils_dep
PkgConfig::hyprcursor_dep
PkgConfig::hyprgraphics_dep
PkgConfig::deps)
if(udis_dep_FOUND)
target_link_libraries(Hyprland PkgConfig::udis_dep)
else()
@@ -290,7 +311,7 @@ endfunction()
target_link_libraries(Hyprland OpenGL::EGL OpenGL::GL Threads::Threads)
pkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.4.0)
pkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.6.4)
if(hyprland_protocols_dep_FOUND)
pkg_get_variable(HYPRLAND_PROTOCOLS hyprland-protocols pkgdatadir)
message(STATUS "hyprland-protocols dependency set to ${HYPRLAND_PROTOCOLS}")
@@ -316,8 +337,14 @@ protocolnew("protocols" "kde-server-decoration" true)
protocolnew("protocols" "wlr-data-control-unstable-v1" true)
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-focus-grab-v1" true)
protocolnew("protocols" "wlr-layer-shell-unstable-v1" true)
protocolnew("protocols" "xx-color-management-v4" true)
protocolnew("protocols" "frog-color-management-v1" true)
protocolnew("protocols" "wayland-drm" true)
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-ctm-control-v1" true)
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-surface-v1" true)
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-lock-notify-v1" true)
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-toplevel-mapping-v1"
true)
protocolnew("staging/tearing-control" "tearing-control-v1" false)
protocolnew("staging/fractional-scale" "fractional-scale-v1" false)
@@ -350,12 +377,29 @@ protocolnew("staging/linux-drm-syncobj" "linux-drm-syncobj-v1" false)
protocolnew("staging/xdg-dialog" "xdg-dialog-v1" false)
protocolnew("staging/single-pixel-buffer" "single-pixel-buffer-v1" false)
protocolnew("staging/security-context" "security-context-v1" false)
protocolnew("staging/content-type" "content-type-v1" false)
protocolnew("staging/color-management" "color-management-v1" false)
protocolnew("staging/xdg-toplevel-tag" "xdg-toplevel-tag-v1" false)
protocolnew("staging/xdg-system-bell" "xdg-system-bell-v1" false)
protocolnew("staging/ext-workspace" "ext-workspace-v1" false)
protocolwayland()
# tools
add_subdirectory(hyprctl)
add_subdirectory(hyprpm)
if(NO_HYPRPM)
message(STATUS "hyprpm is disabled")
else()
add_subdirectory(hyprpm)
message(STATUS "hyprpm is enabled (NO_HYPRPM not defined)")
endif()
if(NO_TESTS)
message(STATUS "building tests is disabled")
else()
message(STATUS "building tests is enabled (NO_TESTS not defined)")
endif()
# binary and symlink
install(TARGETS Hyprland)
@@ -366,7 +410,6 @@ install(
${CMAKE_INSTALL_FULL_BINDIR}/Hyprland \
\"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/hyprland\" \
)")
# session file
install(FILES ${CMAKE_SOURCE_DIR}/example/hyprland.desktop
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/wayland-sessions)
@@ -410,4 +453,19 @@ install(
DIRECTORY ${HEADERS_SRC}
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hyprland
FILES_MATCHING
PATTERN "*.h*")
PATTERN "*.h"
PATTERN "*.hpp"
PATTERN "*.inc")
if(TESTS)
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 hyprtester)
endif()

@@ -3,14 +3,6 @@ PREFIX = /usr/local
stub:
@echo "Do not run $(MAKE) directly without any arguments. Please refer to the wiki on how to compile Hyprland."
legacyrenderer:
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -DLEGACY_RENDERER:BOOL=true -S . -B ./build
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
legacyrendererdebug:
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -DLEGACY_RENDERER:BOOL=true -S . -B ./build
cmake --build ./build --config Debug --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
release:
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -S . -B ./build
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
@@ -52,7 +44,7 @@ installheaders:
cmake --build ./build --config Release --target generate-protocol-headers
find src -name '*.h*' -print0 | cpio --quiet -0dump ${PREFIX}/include/hyprland
find src -type f \( -name '*.hpp' -o -name '*.h' -o -name '*.inc' \) -print0 | cpio --quiet -0dump ${PREFIX}/include/hyprland
cp ./protocols/*.h* ${PREFIX}/include/hyprland/protocols
cp ./build/hyprland.pc ${PREFIX}/share/pkgconfig
if [ -d /usr/share/pkgconfig ]; then cp ./build/hyprland.pc /usr/share/pkgconfig 2>/dev/null || true; fi

@@ -4,7 +4,7 @@
<br>
![Badge Workflow]
[![Badge Workflow]][Workflow]
[![Badge License]][License]
![Badge Language]
[![Badge Pull Requests]][Pull Requests]
@@ -100,7 +100,7 @@ easy IPC, much more QoL stuff than other compositors and more...
<!----------------------------------------------------------------------------->
[Configure]: https://wiki.hyprland.org/Configuring/
[Configure]: https://wiki.hypr.land/Configuring/
[Stars]: https://starchart.cc/hyprwm/Hyprland
[Hypr]: https://github.com/hyprwm/Hypr
@@ -108,9 +108,10 @@ easy IPC, much more QoL stuff than other compositors and more...
[Issues]: https://github.com/hyprwm/Hyprland/issues
[Todo]: https://github.com/hyprwm/Hyprland/projects?type=beta
[Contribute]: https://wiki.hyprland.org/Contributing-and-Debugging/
[Install]: https://wiki.hyprland.org/Getting-Started/Installation/
[Quick Start]: https://wiki.hyprland.org/Getting-Started/Master-Tutorial/
[Contribute]: https://wiki.hypr.land/Contributing-and-Debugging/
[Install]: https://wiki.hypr.land/Getting-Started/Installation/
[Quick Start]: https://wiki.hypr.land/Getting-Started/Master-Tutorial/
[Workflow]: https://github.com/hyprwm/Hyprland/actions/workflows/ci.yaml
[License]: LICENSE
@@ -125,9 +126,9 @@ easy IPC, much more QoL stuff than other compositors and more...
<!----------------------------------{ Images }--------------------------------->
[Preview A]: https://i.ibb.co/C1yTb0r/falf.png
[Preview B]: https://linfindel.github.io/cdn/hyprland-preview-b.png
[Preview C]: https://i.ibb.co/B3GJg28/20221126-20h53m26s-grim.png
[Preview A]: https://i.ibb.co/XxFY75Mk/greerggergerhtrytghjnyhjn.png
[Preview B]: https://i.ibb.co/C1yTb0r/falf.png
[Preview C]: https://i.ibb.co/2Yc4q835/hyprland-preview-b.png
<!----------------------------------{ Badges }--------------------------------->

32
SECURITY.md Normal file

@@ -0,0 +1,32 @@
# Hyprland Development Security Policy
If you have a bug that affects the security of your system, you may
want to privately disclose it instead of making it immediately public.
## Supported versions
_Only_ the most recent release on Github is supported. There are no LTS releases.
## What is not a security issue
Some examples of issues that should not be reported as security issues:
- An app can execute a command when ran outside of a sandbox
- An app can write / read hyprland sockets when ran outside of a sandbox
- Crashes
- Things that are protected via permissions when the permission system is disabled
## What is a security issue
Some examples of issues that should be reported as security issues:
- Sandboxed application executing arbitrary code via Hyprland
- Application being able to modify Hyprland's code on the fly
- Application being able to keylog / track user's activity beyond what the wayland protocols allow
## How to report security issues
Please report your security issues via either of these channels:
- Mail: `vaxry [at] vaxry [dot] net`
- Matrix: `@vaxry:matrix.vaxry.net`
- Discord: `@vaxry`

@@ -1 +1 @@
0.46.0
0.50.0

File diff suppressed because one or more lines are too long

Before

(image error) Size: 506 KiB

After

(image error) Size: 6.6 KiB

@@ -1,5 +1,19 @@
.\" Automatically generated by Pandoc 2.9.2.1
.\" Automatically generated by Pandoc 3.1.3
.\"
.\" Define V font for inline verbatim, using C font in formats
.\" that render this, and otherwise B font.
.ie "\f[CB]x\f[]"x" \{\
. ftr V B
. ftr VI BI
. ftr VB B
. ftr VBI BI
.\}
.el \{\
. ftr V CR
. ftr VI CI
. ftr VB CB
. ftr VBI CBI
.\}
.TH "Hyprland" "1" "" "" "Hyprland User Manual"
.hy
.SH NAME

@@ -2,15 +2,15 @@
First of all, please remember to:
- Check that your issue is not a duplicate
- Read the [FAQ](https://wiki.hyprland.org/FAQ/)
- Read the [Configuring Page](https://wiki.hyprland.org/Configuring/)
- Read the [FAQ](https://wiki.hypr.land/FAQ/)
- Read the [Configuring Page](https://wiki.hypr.land/Configuring/)
<br/>
# Reporting suggestions
Suggestions are welcome.
Many features can be implemented using bash scripts and Hyprland sockets, read up on those [Here](https://wiki.hyprland.org/IPC). Please do not suggest features that can be implemented as such.
Many features can be implemented using bash scripts and Hyprland sockets, read up on those [Here](https://wiki.hypr.land/IPC). Please do not suggest features that can be implemented as such.
<br/>
@@ -70,7 +70,7 @@ A debug coredump provides more information for debugging and may speed up the pr
Make sure you're on latest git. Run `git pull --recurse-submodules` to sync everything.
1. [Compile Hyprland with debug mode](http://wiki.hyprland.org/Contributing-and-Debugging/#build-in-debug-mode)
1. [Compile Hyprland with debug mode](http://wiki.hypr.land/Contributing-and-Debugging/#build-in-debug-mode)
> Note: The config file used will be `hyprlandd.conf` instead of `hyprland.conf`
2. `cd ~`

@@ -1,5 +1,19 @@
.\" Automatically generated by Pandoc 2.9.2.1
.\" Automatically generated by Pandoc 3.1.3
.\"
.\" Define V font for inline verbatim, using C font in formats
.\" that render this, and otherwise B font.
.ie "\f[CB]x\f[]"x" \{\
. ftr V B
. ftr VI BI
. ftr VB B
. ftr VBI BI
.\}
.el \{\
. ftr V CR
. ftr VI CI
. ftr VB CB
. ftr VBI CBI
.\}
.TH "hyprctl" "1" "" "" "hyprctl User Manual"
.hy
.SH NAME

@@ -1,6 +1,6 @@
# This is an example Hyprland config file.
# Refer to the wiki for more information.
# https://wiki.hyprland.org/Configuring/
# https://wiki.hypr.land/Configuring/
# Please note not all available settings / options are set here.
# For a full list, see the wiki
@@ -14,7 +14,7 @@
### MONITORS ###
################
# See https://wiki.hyprland.org/Configuring/Monitors/
# See https://wiki.hypr.land/Configuring/Monitors/
monitor=,preferred,auto,auto
@@ -22,7 +22,7 @@ monitor=,preferred,auto,auto
### MY PROGRAMS ###
###################
# See https://wiki.hyprland.org/Configuring/Keywords/
# See https://wiki.hypr.land/Configuring/Keywords/
# Set programs that you use
$terminal = kitty
@@ -46,41 +46,59 @@ $menu = wofi --show drun
### ENVIRONMENT VARIABLES ###
#############################
# See https://wiki.hyprland.org/Configuring/Environment-variables/
# See https://wiki.hypr.land/Configuring/Environment-variables/
env = XCURSOR_SIZE,24
env = HYPRCURSOR_SIZE,24
###################
### PERMISSIONS ###
###################
# See https://wiki.hypr.land/Configuring/Permissions/
# Please note permission changes here require a Hyprland restart and are not applied on-the-fly
# for security reasons
# ecosystem {
# enforce_permissions = 1
# }
# permission = /usr/(bin|local/bin)/grim, screencopy, allow
# permission = /usr/(lib|libexec|lib64)/xdg-desktop-portal-hyprland, screencopy, allow
# permission = /usr/(bin|local/bin)/hyprpm, plugin, allow
#####################
### LOOK AND FEEL ###
#####################
# Refer to https://wiki.hyprland.org/Configuring/Variables/
# Refer to https://wiki.hypr.land/Configuring/Variables/
# https://wiki.hyprland.org/Configuring/Variables/#general
# https://wiki.hypr.land/Configuring/Variables/#general
general {
gaps_in = 5
gaps_out = 20
border_size = 2
# https://wiki.hyprland.org/Configuring/Variables/#variable-types for info about colors
# https://wiki.hypr.land/Configuring/Variables/#variable-types for info about colors
col.active_border = rgba(33ccffee) rgba(00ff99ee) 45deg
col.inactive_border = rgba(595959aa)
# Set to true enable resizing windows by clicking and dragging on borders and gaps
resize_on_border = false
# Please see https://wiki.hyprland.org/Configuring/Tearing/ before you turn this on
# Please see https://wiki.hypr.land/Configuring/Tearing/ before you turn this on
allow_tearing = false
layout = dwindle
}
# https://wiki.hyprland.org/Configuring/Variables/#decoration
# https://wiki.hypr.land/Configuring/Variables/#decoration
decoration {
rounding = 10
rounding_power = 2
# Change transparency of focused and unfocused windows
active_opacity = 1.0
@@ -93,7 +111,7 @@ decoration {
color = rgba(1a1a1aee)
}
# https://wiki.hyprland.org/Configuring/Variables/#blur
# https://wiki.hypr.land/Configuring/Variables/#blur
blur {
enabled = true
size = 3
@@ -103,11 +121,11 @@ decoration {
}
}
# https://wiki.hyprland.org/Configuring/Variables/#animations
# https://wiki.hypr.land/Configuring/Variables/#animations
animations {
enabled = yes, please :)
# Default animations, see https://wiki.hyprland.org/Configuring/Animations/ for more
# Default animations, see https://wiki.hypr.land/Configuring/Animations/ for more
bezier = easeOutQuint,0.23,1,0.32,1
bezier = easeInOutCubic,0.65,0.05,0.36,1
@@ -131,30 +149,31 @@ animations {
animation = workspaces, 1, 1.94, almostLinear, fade
animation = workspacesIn, 1, 1.21, almostLinear, fade
animation = workspacesOut, 1, 1.94, almostLinear, fade
animation = zoomFactor, 1, 7, quick
}
# Ref https://wiki.hyprland.org/Configuring/Workspace-Rules/
# Ref https://wiki.hypr.land/Configuring/Workspace-Rules/
# "Smart gaps" / "No gaps when only"
# uncomment all if you wish to use that.
# workspace = w[tv1], gapsout:0, gapsin:0
# workspace = f[1], gapsout:0, gapsin:0
# windowrulev2 = bordersize 0, floating:0, onworkspace:w[tv1]
# windowrulev2 = rounding 0, floating:0, onworkspace:w[tv1]
# windowrulev2 = bordersize 0, floating:0, onworkspace:f[1]
# windowrulev2 = rounding 0, floating:0, onworkspace:f[1]
# 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]
# See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more
# See https://wiki.hypr.land/Configuring/Dwindle-Layout/ for more
dwindle {
pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
preserve_split = true # You probably want this
}
# See https://wiki.hyprland.org/Configuring/Master-Layout/ for more
# See https://wiki.hypr.land/Configuring/Master-Layout/ for more
master {
new_status = master
}
# https://wiki.hyprland.org/Configuring/Variables/#misc
# https://wiki.hypr.land/Configuring/Variables/#misc
misc {
force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers
disable_hyprland_logo = false # If true disables the random hyprland logo / anime girl background. :(
@@ -165,7 +184,7 @@ misc {
### INPUT ###
#############
# https://wiki.hyprland.org/Configuring/Variables/#input
# https://wiki.hypr.land/Configuring/Variables/#input
input {
kb_layout = us
kb_variant =
@@ -182,13 +201,13 @@ input {
}
}
# https://wiki.hyprland.org/Configuring/Variables/#gestures
# https://wiki.hypr.land/Configuring/Variables/#gestures
gestures {
workspace_swipe = false
}
# Example per-device config
# See https://wiki.hyprland.org/Configuring/Keywords/#per-device-input-configs for more
# See https://wiki.hypr.land/Configuring/Keywords/#per-device-input-configs for more
device {
name = epic-mouse-v1
sensitivity = -0.5
@@ -199,10 +218,10 @@ device {
### KEYBINDINGS ###
###################
# See https://wiki.hyprland.org/Configuring/Keywords/
# See https://wiki.hypr.land/Configuring/Keywords/
$mainMod = SUPER # Sets "Windows" key as main modifier
# Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more
# Example binds, see https://wiki.hypr.land/Configuring/Binds/ for more
bind = $mainMod, Q, exec, $terminal
bind = $mainMod, C, killactive,
bind = $mainMod, M, exit,
@@ -259,8 +278,8 @@ bindel = ,XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@
bindel = ,XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
bindel = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
bindel = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
bindel = ,XF86MonBrightnessUp, exec, brightnessctl s 10%+
bindel = ,XF86MonBrightnessDown, exec, brightnessctl s 10%-
bindel = ,XF86MonBrightnessUp, exec, brightnessctl -e4 -n2 set 5%+
bindel = ,XF86MonBrightnessDown, exec, brightnessctl -e4 -n2 set 5%-
# Requires playerctl
bindl = , XF86AudioNext, exec, playerctl next
@@ -272,17 +291,14 @@ bindl = , XF86AudioPrev, exec, playerctl previous
### WINDOWS AND WORKSPACES ###
##############################
# See https://wiki.hyprland.org/Configuring/Window-Rules/ for more
# See https://wiki.hyprland.org/Configuring/Workspace-Rules/ for workspace rules
# See https://wiki.hypr.land/Configuring/Window-Rules/ for more
# See https://wiki.hypr.land/Configuring/Workspace-Rules/ for workspace rules
# Example windowrule v1
# windowrule = float, ^(kitty)$
# Example windowrule v2
# windowrulev2 = float,class:^(kitty)$,title:^(kitty)$
# Example windowrule
# windowrule = float,class:^(kitty)$,title:^(kitty)$
# Ignore maximize requests from apps. You'll probably like this.
windowrulev2 = suppressevent maximize, class:.*
windowrule = suppressevent maximize, class:.*
# Fix some dragging issues with XWayland
windowrulev2 = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0
windowrule = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0

@@ -22,5 +22,6 @@
}
]
},
]
}

@@ -2,15 +2,18 @@
// Example blue light filter shader.
//
#version 300 es
precision mediump float;
varying vec2 v_texcoord;
in vec2 v_texcoord;
layout(location = 0) out vec4 fragColor;
uniform sampler2D tex;
void main() {
vec4 pixColor = texture2D(tex, v_texcoord);
vec4 pixColor = texture(tex, v_texcoord);
pixColor[2] *= 0.8;
gl_FragColor = pixColor;
fragColor = pixColor;
}

120
flake.lock generated

@@ -16,11 +16,11 @@
]
},
"locked": {
"lastModified": 1734364797,
"narHash": "sha256-2h1c+P0v3l0Z/ypUSsAPhU/yiSRgFwjVFODWp0S3d/w=",
"lastModified": 1752743471,
"narHash": "sha256-4izhj1j7J4mE8LgljCXSIUDculqOsxxhdoC81VhqizM=",
"owner": "hyprwm",
"repo": "aquamarine",
"rev": "8e77618b403a82fde2105a8e3cd7cabe7ef00952",
"rev": "e31b575d19e7cf8a8f4398e2f9cffe27a1332506",
"type": "github"
},
"original": {
@@ -79,11 +79,11 @@
]
},
"locked": {
"lastModified": 1734364709,
"narHash": "sha256-+2bZJL2u5hva7rSp65OfKJBK+k03T6GB/NCvpoS1OOo=",
"lastModified": 1749155331,
"narHash": "sha256-XR9fsI0zwLiFWfqi/pdS/VD+YNorKb3XIykgTg4l1nA=",
"owner": "hyprwm",
"repo": "hyprcursor",
"rev": "f388aacd22be4a6e4d634fbaf6f75eb0713d239a",
"rev": "45fcc10b4c282746d93ec406a740c43b48b4ef80",
"type": "github"
},
"original": {
@@ -105,11 +105,11 @@
]
},
"locked": {
"lastModified": 1733684019,
"narHash": "sha256-2kYREgmSmbLsmDpLEq96hxVAU3qz8aCvVhF65yCFZHY=",
"lastModified": 1752149140,
"narHash": "sha256-gbh1HL98Fdqu0jJIWN4OJQN7Kkth7+rbkFpSZLm/62A=",
"owner": "hyprwm",
"repo": "hyprgraphics",
"rev": "fb2c0268645a77403af3b8a4ce8fa7ba5917f15d",
"rev": "340494a38b5ec453dfc542c6226481f736cc8a9a",
"type": "github"
},
"original": {
@@ -128,11 +128,11 @@
]
},
"locked": {
"lastModified": 1728345020,
"narHash": "sha256-xGbkc7U/Roe0/Cv3iKlzijIaFBNguasI31ynL2IlEoM=",
"lastModified": 1749046714,
"narHash": "sha256-kymV5FMnddYGI+UjwIw8ceDjdeg7ToDVjbHCvUlhn14=",
"owner": "hyprwm",
"repo": "hyprland-protocols",
"rev": "a7c183800e74f337753de186522b9017a07a8cee",
"rev": "613878cb6f459c5e323aaafe1e6f388ac8a36330",
"type": "github"
},
"original": {
@@ -141,9 +141,44 @@
"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": [
@@ -154,11 +189,11 @@
]
},
"locked": {
"lastModified": 1733940128,
"narHash": "sha256-hmfXWj2GA9cj1QUkPFYtAAeohhs615zL4E3APy3FnvQ=",
"lastModified": 1750371812,
"narHash": "sha256-D868K1dVEACw17elVxRgXC6hOxY+54wIEjURztDWLk8=",
"owner": "hyprwm",
"repo": "hyprland-qtutils",
"rev": "3833097e50473a152dd614d4b468886840b4ea78",
"rev": "b13c7481e37856f322177010bdf75fccacd1adc8",
"type": "github"
},
"original": {
@@ -180,11 +215,11 @@
]
},
"locked": {
"lastModified": 1734364628,
"narHash": "sha256-ii8fzJfI953n/EmIxVvq64ZAwhvwuuPHWfGd61/mJG8=",
"lastModified": 1750371198,
"narHash": "sha256-/iuJ1paQOBoSLqHflRNNGyroqfF/yvPNurxzcCT0cAE=",
"owner": "hyprwm",
"repo": "hyprlang",
"rev": "16e59c1eb13d9fb6de066f54e7555eb5e8a4aba5",
"rev": "cee01452bca58d6cadb3224e21e370de8bc20f0b",
"type": "github"
},
"original": {
@@ -203,11 +238,11 @@
]
},
"locked": {
"lastModified": 1733502241,
"narHash": "sha256-KAUNC4Dgq8WQjYov5auBw/usaHixhacvb7cRDd0AG/k=",
"lastModified": 1752252310,
"narHash": "sha256-06i1pIh6wb+sDeDmWlzuPwIdaFMxLlj1J9I5B9XqSeo=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "104117aed6dd68561be38b50f218190aa47f2cd8",
"rev": "bcabcbada90ed2aacb435dc09b91001819a6dc82",
"type": "github"
},
"original": {
@@ -226,11 +261,11 @@
]
},
"locked": {
"lastModified": 1726874836,
"narHash": "sha256-VKR0sf0PSNCB0wPHVKSAn41mCNVCnegWmgkrneKDhHM=",
"lastModified": 1751897909,
"narHash": "sha256-FnhBENxihITZldThvbO7883PdXC/2dzW4eiNvtoV5Ao=",
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"rev": "500c81a9e1a76760371049a8d99e008ea77aa59e",
"rev": "fcca0c61f988a9d092cbb33e906775014c61579d",
"type": "github"
},
"original": {
@@ -241,11 +276,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1734119587,
"narHash": "sha256-AKU6qqskl0yf2+JdRdD0cfxX4b9x3KKV5RqA6wijmPM=",
"lastModified": 1752687322,
"narHash": "sha256-RKwfXA4OZROjBTQAl9WOZQFm7L8Bo93FQwSJpAiSRvo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3566ab7246670a43abd2ffa913cc62dad9cdf7d5",
"rev": "6e987485eb2c77e5dcc5af4e3c70843711ef9251",
"type": "github"
},
"original": {
@@ -255,37 +290,20 @@
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1730741070,
"narHash": "sha256-edm8WG19kWozJ/GqyYx2VjW99EdhjKwbY3ZwdlPAAlo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d063c1dd113c91ab27959ba540c0d9753409edf3",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
]
},
"locked": {
"lastModified": 1734279981,
"narHash": "sha256-NdaCraHPp8iYMWzdXAt5Nv6sA3MUzlCiGiR586TCwo0=",
"lastModified": 1750779888,
"narHash": "sha256-wibppH3g/E2lxU43ZQHC5yA/7kIKLGxVEnsnVK1BtRg=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "aa9f40c906904ebd83da78e7f328cd8aeaeae785",
"rev": "16ec914f6fb6f599ce988427d9d94efddf25fe6d",
"type": "github"
},
"original": {
@@ -347,11 +365,11 @@
]
},
"locked": {
"lastModified": 1734124279,
"narHash": "sha256-YNpFfiQjYt2o6LGcMN9NkjVvprC8ELrIpLHlbZbclRM=",
"lastModified": 1751300244,
"narHash": "sha256-PFuv1TZVYvQhha0ac53E3YgdtmLShrN0t4T6xqHl0jE=",
"owner": "hyprwm",
"repo": "xdg-desktop-portal-hyprland",
"rev": "0c6861f819f6d31f6195c9864709b2556f00b5cf",
"rev": "6115f3fdcb2c1a57b4a80a69f3c797e47607b90a",
"type": "github"
},
"original": {

@@ -39,7 +39,7 @@
url = "github:hyprwm/hyprland-qtutils";
inputs.nixpkgs.follows = "nixpkgs";
inputs.systems.follows = "systems";
inputs.hyprutils.follows = "hyprutils";
inputs.hyprlang.follows = "hyprlang";
};
hyprlang = {
@@ -102,6 +102,21 @@
hyprland-extras
];
});
pkgsDebugFor = eachSystem (system:
import nixpkgs {
localSystem = system;
overlays = with self.overlays; [
hyprland-debug
];
});
pkgsDebugCrossFor = eachSystem (system: crossSystem:
import nixpkgs {
localSystem = system;
inherit crossSystem;
overlays = with self.overlays; [
hyprland-debug
];
});
in {
overlays = import ./nix/overlays.nix {inherit self lib inputs;};
@@ -123,7 +138,8 @@
};
};
};
});
}
// (import ./nix/tests inputs pkgsFor.${system}));
packages = eachSystem (system: {
default = self.packages.${system}.hyprland;
@@ -131,13 +147,14 @@
(pkgsFor.${system})
# hyprland-packages
hyprland
hyprland-debug
hyprland-legacy-renderer
hyprland-unwrapped
hyprtester
# hyprland-extras
xdg-desktop-portal-hyprland
;
inherit (pkgsDebugFor.${system}) hyprland-debug;
hyprland-cross = (pkgsCrossFor.${system} "aarch64-linux").hyprland;
hyprland-debug-cross = (pkgsDebugCrossFor.${system} "aarch64-linux").hyprland-debug;
});
devShells = eachSystem (system: {
@@ -157,5 +174,11 @@
nixosModules.default = import ./nix/module.nix inputs;
homeManagerModules.default = import ./nix/hm-module.nix self;
# Hydra build jobs
# Recent versions of Hydra can aggregate jobsets from 'hydraJobs' intead of a release.nix
# or similar. Remember to filter large or incompatible attributes here. More eval jobs can
# be added by merging, e.g., self.packages // self.devShells.
hydraJobs = self.packages;
};
}

@@ -22,6 +22,7 @@ commands:
getoption <option> Gets the config option status (values)
globalshortcuts Lists all global shortcuts
hyprpaper ... Issue a hyprpaper request
hyprsunset ... Issue a hyprsunset request
instances Lists all running instances of Hyprland with
their info
keyword <name> <value> Issue a keyword to call a config keyword
@@ -81,6 +82,16 @@ requests:
flags:
See 'hyprctl --help')#";
const std::string_view HYPRSUNSET_HELP = R"#(usage: hyprctl [flags] hyprsunset <request>
requests:
temperature <temp> Enable blue-light filter
identity Disable blue-light filter
gamma <gamma> Enable gamma filter
flags:
See 'hyprctl --help')#";
const std::string_view NOTIFY_HELP = R"#(usage: hyprctl [flags] notify <icon> <time_ms> <color> <message...>
icon:
@@ -135,7 +146,7 @@ regex:
Regular expression by which a window will be searched
property:
See https://wiki.hyprland.org/Configuring/Using-hyprctl/#setprop for list
See https://wiki.hypr.land/Configuring/Using-hyprctl/#setprop for list
of properties
value:

@@ -23,7 +23,7 @@ _hyprctl () {
local words cword
_get_comp_words_by_ref -n "$COMP_WORDBREAKS" words cword
declare -a literals=(resizeactive 2 changegroupactive -r moveintogroup forceallowsinput 4 ::= systeminfo all layouts setprop animationstyle switchxkblayout create denywindowfromgroup headless activebordercolor exec setcursor wayland focusurgentorlast workspacerules movecurrentworkspacetomonitor movetoworkspacesilent hyprpaper alpha inactivebordercolor movegroupwindow movecursortocorner movewindowpixel prev movewindow globalshortcuts clients dimaround setignoregrouplock splash execr monitors 0 forcenoborder -q animations 1 nomaxsize splitratio moveactive pass swapnext devices layers rounding lockactivegroup 5 moveworkspacetomonitor -f -i --quiet forcenodim pin 0 1 forceopaque forcenoshadow setfloating minsize alphaoverride sendshortcut workspaces cyclenext alterzorder togglegroup lockgroups bordersize dpms focuscurrentorlast -1 --batch notify remove instances 1 3 moveoutofgroup killactive 2 movetoworkspace movecursor configerrors closewindow swapwindow tagwindow forcerendererreload centerwindow auto focuswindow seterror nofocus alphafullscreen binds version -h togglespecialworkspace fullscreen windowdancecompat 0 keyword toggleopaque 3 --instance togglefloating renameworkspace alphafullscreenoverride activeworkspace x11 kill forceopaqueoverriden output global dispatch reload forcenoblur -j event --help disable -1 activewindow keepaspectratio dismissnotify focusmonitor movefocus plugin exit workspace fullscreenstate getoption alphainactiveoverride alphainactive decorations settiled config-only descriptions resizewindowpixel fakefullscreen rollinglog swapactiveworkspaces submap next movewindoworgroup cursorpos forcenoanims focusworkspaceoncurrentmonitor maxsize)
declare -a literals=(resizeactive 2 changegroupactive -r moveintogroup forceallowsinput 4 ::= systeminfo all layouts setprop animationstyle switchxkblayout create denywindowfromgroup headless activebordercolor exec setcursor wayland focusurgentorlast workspacerules movecurrentworkspacetomonitor movetoworkspacesilent hyprpaper alpha inactivebordercolor movegroupwindow movecursortocorner movewindowpixel prev movewindow globalshortcuts clients dimaround setignoregrouplock splash execr monitors 0 forcenoborder -q animations 1 nomaxsize splitratio moveactive pass swapnext devices layers rounding lockactivegroup 5 moveworkspacetomonitor -f -i --quiet forcenodim pin 0 1 forceopaque forcenoshadow setfloating minsize alphaoverride sendshortcut workspaces cyclenext alterzorder togglegroup lockgroups bordersize dpms focuscurrentorlast -1 --batch notify remove instances 1 3 moveoutofgroup killactive 2 movetoworkspace movecursor configerrors closewindow swapwindow tagwindow forcerendererreload centerwindow auto focuswindow seterror nofocus alphafullscreen binds version -h togglespecialworkspace fullscreen windowdancecompat 0 keyword toggleopaque 3 --instance togglefloating renameworkspace alphafullscreenoverride activeworkspace x11 kill forceopaqueoverriden output global dispatch reload forcenoblur -j event --help disable -1 activewindow keepaspectratio dismissnotify focusmonitor movefocus plugin exit workspace fullscreenstate getoption alphainactiveoverride alphainactive decorations settiled config-only descriptions resizewindowpixel fakefullscreen rollinglog swapactiveworkspaces submap next movewindoworgroup cursorpos forcenoanims focusworkspaceoncurrentmonitor maxsize sendkeystate)
declare -A literal_transitions
literal_transitions[0]="([120]=14 [43]=2 [125]=21 [81]=2 [3]=21 [51]=2 [50]=2 [128]=2 [89]=2 [58]=21 [8]=2 [10]=2 [11]=3 [130]=4 [13]=5 [97]=6 [101]=2 [102]=21 [133]=7 [100]=2 [137]=2 [22]=2 [19]=2 [140]=8 [25]=2 [143]=2 [107]=9 [146]=10 [69]=2 [33]=2 [34]=2 [78]=21 [114]=2 [37]=2 [151]=2 [116]=2 [121]=13 [123]=21 [39]=11 [42]=21 [79]=15 [118]=12)"
literal_transitions[1]="([81]=2 [51]=2 [50]=2 [128]=2 [8]=2 [89]=2 [10]=2 [11]=3 [130]=4 [13]=5 [97]=6 [101]=2 [133]=7 [100]=2 [22]=2 [19]=2 [137]=2 [140]=8 [25]=2 [143]=2 [107]=9 [146]=10 [69]=2 [33]=2 [34]=2 [114]=2 [37]=2 [151]=2 [116]=2 [39]=11 [118]=12 [121]=13 [120]=14 [79]=15 [43]=2)"

@@ -29,7 +29,7 @@ function _hyprctl
set COMP_CWORD (count $COMP_WORDS)
end
set literals "resizeactive" "2" "changegroupactive" "-r" "moveintogroup" "forceallowsinput" "4" "::=" "systeminfo" "all" "layouts" "setprop" "animationstyle" "switchxkblayout" "create" "denywindowfromgroup" "headless" "activebordercolor" "exec" "setcursor" "wayland" "focusurgentorlast" "workspacerules" "movecurrentworkspacetomonitor" "movetoworkspacesilent" "hyprpaper" "alpha" "inactivebordercolor" "movegroupwindow" "movecursortocorner" "movewindowpixel" "prev" "movewindow" "globalshortcuts" "clients" "dimaround" "setignoregrouplock" "splash" "execr" "monitors" "0" "forcenoborder" "-q" "animations" "1" "nomaxsize" "splitratio" "moveactive" "pass" "swapnext" "devices" "layers" "rounding" "lockactivegroup" "5" "moveworkspacetomonitor" "-f" "-i" "--quiet" "forcenodim" "pin" "0" "1" "forceopaque" "forcenoshadow" "setfloating" "minsize" "alphaoverride" "sendshortcut" "workspaces" "cyclenext" "alterzorder" "togglegroup" "lockgroups" "bordersize" "dpms" "focuscurrentorlast" "-1" "--batch" "notify" "remove" "instances" "1" "3" "moveoutofgroup" "killactive" "2" "movetoworkspace" "movecursor" "configerrors" "closewindow" "swapwindow" "tagwindow" "forcerendererreload" "centerwindow" "auto" "focuswindow" "seterror" "nofocus" "alphafullscreen" "binds" "version" "-h" "togglespecialworkspace" "fullscreen" "windowdancecompat" "0" "keyword" "toggleopaque" "3" "--instance" "togglefloating" "renameworkspace" "alphafullscreenoverride" "activeworkspace" "x11" "kill" "forceopaqueoverriden" "output" "global" "dispatch" "reload" "forcenoblur" "-j" "event" "--help" "disable" "-1" "activewindow" "keepaspectratio" "dismissnotify" "focusmonitor" "movefocus" "plugin" "exit" "workspace" "fullscreenstate" "getoption" "alphainactiveoverride" "alphainactive" "decorations" "settiled" "config-only" "descriptions" "resizewindowpixel" "fakefullscreen" "rollinglog" "swapactiveworkspaces" "submap" "next" "movewindoworgroup" "cursorpos" "forcenoanims" "focusworkspaceoncurrentmonitor" "maxsize"
set literals "resizeactive" "2" "changegroupactive" "-r" "moveintogroup" "forceallowsinput" "4" "::=" "systeminfo" "all" "layouts" "setprop" "animationstyle" "switchxkblayout" "create" "denywindowfromgroup" "headless" "activebordercolor" "exec" "setcursor" "wayland" "focusurgentorlast" "workspacerules" "movecurrentworkspacetomonitor" "movetoworkspacesilent" "hyprpaper" "alpha" "inactivebordercolor" "movegroupwindow" "movecursortocorner" "movewindowpixel" "prev" "movewindow" "globalshortcuts" "clients" "dimaround" "setignoregrouplock" "splash" "execr" "monitors" "0" "forcenoborder" "-q" "animations" "1" "nomaxsize" "splitratio" "moveactive" "pass" "swapnext" "devices" "layers" "rounding" "lockactivegroup" "5" "moveworkspacetomonitor" "-f" "-i" "--quiet" "forcenodim" "pin" "0" "1" "forceopaque" "forcenoshadow" "setfloating" "minsize" "alphaoverride" "sendshortcut" "workspaces" "cyclenext" "alterzorder" "togglegroup" "lockgroups" "bordersize" "dpms" "focuscurrentorlast" "-1" "--batch" "notify" "remove" "instances" "1" "3" "moveoutofgroup" "killactive" "2" "movetoworkspace" "movecursor" "configerrors" "closewindow" "swapwindow" "tagwindow" "forcerendererreload" "centerwindow" "auto" "focuswindow" "seterror" "nofocus" "alphafullscreen" "binds" "version" "-h" "togglespecialworkspace" "fullscreen" "windowdancecompat" "0" "keyword" "toggleopaque" "3" "--instance" "togglefloating" "renameworkspace" "alphafullscreenoverride" "activeworkspace" "x11" "kill" "forceopaqueoverriden" "output" "global" "dispatch" "reload" "forcenoblur" "-j" "event" "--help" "disable" "-1" "activewindow" "keepaspectratio" "dismissnotify" "focusmonitor" "movefocus" "plugin" "exit" "workspace" "fullscreenstate" "getoption" "alphainactiveoverride" "alphainactive" "decorations" "settiled" "config-only" "descriptions" "resizewindowpixel" "fakefullscreen" "rollinglog" "swapactiveworkspaces" "submap" "next" "movewindoworgroup" "cursorpos" "forcenoanims" "focusworkspaceoncurrentmonitor" "maxsize" "sendkeystate"
set descriptions
set descriptions[1] "Resize the active window"

@@ -106,6 +106,7 @@ hyprctl [<OPTIONS>]... <ARGUMENTS>
| (execr) "Execute a raw shell command"
| (pass) "Pass the key to a specified window"
| (sendshortcut) "On shortcut X sends shortcut Y to a specified window"
| (sendkeystate) "Send a key with specific state (down/repeat/up) to a specified window (window must keep focus for events to continue)"
| (killactive) "Close the active window"
| (closewindow) "Close a specified window"
| (workspace) "Change the workspace"

@@ -17,7 +17,7 @@ _hyprctl_cmd_0 () {
}
_hyprctl () {
local -a literals=("resizeactive" "2" "changegroupactive" "-r" "moveintogroup" "forceallowsinput" "4" "::=" "systeminfo" "all" "layouts" "setprop" "animationstyle" "switchxkblayout" "create" "denywindowfromgroup" "headless" "activebordercolor" "exec" "setcursor" "wayland" "focusurgentorlast" "workspacerules" "movecurrentworkspacetomonitor" "movetoworkspacesilent" "hyprpaper" "alpha" "inactivebordercolor" "movegroupwindow" "movecursortocorner" "movewindowpixel" "prev" "movewindow" "globalshortcuts" "clients" "dimaround" "setignoregrouplock" "splash" "execr" "monitors" "0" "forcenoborder" "-q" "animations" "1" "nomaxsize" "splitratio" "moveactive" "pass" "swapnext" "devices" "layers" "rounding" "lockactivegroup" "5" "moveworkspacetomonitor" "-f" "-i" "--quiet" "forcenodim" "pin" "0" "1" "forceopaque" "forcenoshadow" "setfloating" "minsize" "alphaoverride" "sendshortcut" "workspaces" "cyclenext" "alterzorder" "togglegroup" "lockgroups" "bordersize" "dpms" "focuscurrentorlast" "-1" "--batch" "notify" "remove" "instances" "1" "3" "moveoutofgroup" "killactive" "2" "movetoworkspace" "movecursor" "configerrors" "closewindow" "swapwindow" "tagwindow" "forcerendererreload" "centerwindow" "auto" "focuswindow" "seterror" "nofocus" "alphafullscreen" "binds" "version" "-h" "togglespecialworkspace" "fullscreen" "windowdancecompat" "0" "keyword" "toggleopaque" "3" "--instance" "togglefloating" "renameworkspace" "alphafullscreenoverride" "activeworkspace" "x11" "kill" "forceopaqueoverriden" "output" "global" "dispatch" "reload" "forcenoblur" "-j" "event" "--help" "disable" "-1" "activewindow" "keepaspectratio" "dismissnotify" "focusmonitor" "movefocus" "plugin" "exit" "workspace" "fullscreenstate" "getoption" "alphainactiveoverride" "alphainactive" "decorations" "settiled" "config-only" "descriptions" "resizewindowpixel" "fakefullscreen" "rollinglog" "swapactiveworkspaces" "submap" "next" "movewindoworgroup" "cursorpos" "forcenoanims" "focusworkspaceoncurrentmonitor" "maxsize")
local -a literals=("resizeactive" "2" "changegroupactive" "-r" "moveintogroup" "forceallowsinput" "4" "::=" "systeminfo" "all" "layouts" "setprop" "animationstyle" "switchxkblayout" "create" "denywindowfromgroup" "headless" "activebordercolor" "exec" "setcursor" "wayland" "focusurgentorlast" "workspacerules" "movecurrentworkspacetomonitor" "movetoworkspacesilent" "hyprpaper" "alpha" "inactivebordercolor" "movegroupwindow" "movecursortocorner" "movewindowpixel" "prev" "movewindow" "globalshortcuts" "clients" "dimaround" "setignoregrouplock" "splash" "execr" "monitors" "0" "forcenoborder" "-q" "animations" "1" "nomaxsize" "splitratio" "moveactive" "pass" "swapnext" "devices" "layers" "rounding" "lockactivegroup" "5" "moveworkspacetomonitor" "-f" "-i" "--quiet" "forcenodim" "pin" "0" "1" "forceopaque" "forcenoshadow" "setfloating" "minsize" "alphaoverride" "sendshortcut" "workspaces" "cyclenext" "alterzorder" "togglegroup" "lockgroups" "bordersize" "dpms" "focuscurrentorlast" "-1" "--batch" "notify" "remove" "instances" "1" "3" "moveoutofgroup" "killactive" "2" "movetoworkspace" "movecursor" "configerrors" "closewindow" "swapwindow" "tagwindow" "forcerendererreload" "centerwindow" "auto" "focuswindow" "seterror" "nofocus" "alphafullscreen" "binds" "version" "-h" "togglespecialworkspace" "fullscreen" "windowdancecompat" "0" "keyword" "toggleopaque" "3" "--instance" "togglefloating" "renameworkspace" "alphafullscreenoverride" "activeworkspace" "x11" "kill" "forceopaqueoverriden" "output" "global" "dispatch" "reload" "forcenoblur" "-j" "event" "--help" "disable" "-1" "activewindow" "keepaspectratio" "dismissnotify" "focusmonitor" "movefocus" "plugin" "exit" "workspace" "fullscreenstate" "getoption" "alphainactiveoverride" "alphainactive" "decorations" "settiled" "config-only" "descriptions" "resizewindowpixel" "fakefullscreen" "rollinglog" "swapactiveworkspaces" "submap" "next" "movewindoworgroup" "cursorpos" "forcenoanims" "focusworkspaceoncurrentmonitor" "maxsize" "sendkeystate")
local -A descriptions
descriptions[1]="Resize the active window"

@@ -12,28 +12,24 @@
#include <sys/un.h>
#include <pwd.h>
#include <unistd.h>
#include <ranges>
#include <algorithm>
#include <csignal>
#include <format>
#include <ranges>
#include <optional>
#include <charconv>
#include <iostream>
#include <string>
#include <print>
#include <fstream>
#include <string>
#include <vector>
#include <filesystem>
#include <cstdarg>
#include <sys/socket.h>
#include <hyprutils/string/String.hpp>
#include <cstring>
using namespace Hyprutils::String;
#include "Strings.hpp"
#define PAD
std::string instanceSignature;
bool quiet = false;
@@ -42,10 +38,9 @@ struct SInstanceData {
uint64_t time;
uint64_t pid;
std::string wlSocket;
bool valid = true;
};
void log(const std::string& str) {
void log(const std::string_view str) {
if (quiet)
return;
@@ -69,47 +64,74 @@ std::string getRuntimeDir() {
return std::string{XDG} + "/hypr";
}
static std::optional<uint64_t> toUInt64(const std::string_view str) {
uint64_t value = 0;
const auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), value);
if (ec != std::errc() || ptr != str.data() + str.size())
return std::nullopt;
return value;
}
static std::optional<SInstanceData> parseInstance(const std::filesystem::directory_entry& entry) {
if (!entry.is_directory())
return std::nullopt;
const auto lockPath = entry.path() / "hyprland.lock";
std::ifstream ifs(lockPath);
if (!ifs.is_open())
return std::nullopt;
SInstanceData data;
data.id = entry.path().filename().string();
const auto first = std::string_view{data.id}.find_first_of('_');
const auto last = std::string_view{data.id}.find_last_of('_');
if (first == std::string_view::npos || last == std::string_view::npos || last <= first)
return std::nullopt;
auto time = toUInt64(std::string_view{data.id}.substr(first + 1, last - first - 1));
if (!time)
return std::nullopt;
data.time = *time;
std::string line;
if (!std::getline(ifs, line))
return std::nullopt;
auto pid = toUInt64(std::string_view{line});
if (!pid)
return std::nullopt;
data.pid = *pid;
if (!std::getline(ifs, data.wlSocket))
return std::nullopt;
if (std::getline(ifs, line) && !line.empty())
return std::nullopt; // more lines than expected
return data;
}
std::vector<SInstanceData> instances() {
std::vector<SInstanceData> result;
try {
if (!std::filesystem::exists(getRuntimeDir()))
return {};
} catch (std::exception& e) { return {}; }
std::error_code ec;
const auto runtimeDir = getRuntimeDir();
if (!std::filesystem::exists(runtimeDir, ec) || ec)
return result;
for (const auto& el : std::filesystem::directory_iterator(getRuntimeDir())) {
if (!el.is_directory() || !std::filesystem::exists(el.path().string() + "/hyprland.lock"))
continue;
std::filesystem::directory_iterator it(runtimeDir, std::filesystem::directory_options::skip_permission_denied, ec);
if (ec)
return result;
// read lock
SInstanceData* data = &result.emplace_back();
data->id = el.path().filename().string();
try {
data->time = std::stoull(data->id.substr(data->id.find_first_of('_') + 1, data->id.find_last_of('_') - (data->id.find_first_of('_') + 1)));
} catch (std::exception& e) { continue; }
// read file
std::ifstream ifs(el.path().string() + "/hyprland.lock");
int i = 0;
for (std::string line; std::getline(ifs, line); ++i) {
if (i == 0) {
try {
data->pid = std::stoull(line);
} catch (std::exception& e) { continue; }
} else if (i == 1) {
data->wlSocket = line;
} else
break;
for (const auto& el : it) {
if (auto instance = parseInstance(el))
result.emplace_back(std::move(*instance));
}
ifs.close();
}
std::erase_if(result, [](const auto& el) { return kill(el.pid, 0) != 0 && errno == ESRCH; });
std::erase_if(result, [&](const auto& el) { return kill(el.pid, 0) != 0 && errno == ESRCH; });
std::sort(result.begin(), result.end(), [&](const auto& a, const auto& b) { return a.time < b.time; });
std::ranges::sort(result, {}, &SInstanceData::time);
return result;
}
@@ -151,11 +173,19 @@ int rollingRead(const int socket) {
return 0;
}
int request(std::string arg, int minArgs = 0, bool needRoll = false) {
int request(std::string_view arg, int minArgs = 0, bool needRoll = false) {
const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
if (SERVERSOCKET < 0) {
log("Couldn't open a socket (1)");
return 1;
}
auto t = timeval{.tv_sec = 5, .tv_usec = 0};
setsockopt(SERVERSOCKET, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(struct timeval));
if (setsockopt(SERVERSOCKET, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(struct timeval)) < 0) {
log("Couldn't set socket timeout (2)");
return 2;
}
const auto ARGS = std::count(arg.begin(), arg.end(), ' ');
@@ -164,18 +194,11 @@ int request(std::string arg, int minArgs = 0, bool needRoll = false) {
return -1;
}
if (SERVERSOCKET < 0) {
log("Couldn't open a socket (1)");
return 1;
}
if (instanceSignature.empty()) {
log("HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?)");
return 2;
log("HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?) (3)");
return 3;
}
const std::string USERID = std::to_string(getUID());
sockaddr_un serverAddress = {0};
serverAddress.sun_family = AF_UNIX;
@@ -184,39 +207,40 @@ int request(std::string arg, int minArgs = 0, bool needRoll = false) {
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
if (connect(SERVERSOCKET, (sockaddr*)&serverAddress, SUN_LEN(&serverAddress)) < 0) {
log("Couldn't connect to " + socketPath + ". (3)");
return 3;
log("Couldn't connect to " + socketPath + ". (4)");
return 4;
}
auto sizeWritten = write(SERVERSOCKET, arg.c_str(), arg.length());
auto sizeWritten = write(SERVERSOCKET, arg.data(), arg.size());
if (sizeWritten < 0) {
log("Couldn't write (4)");
return 4;
log("Couldn't write (5)");
return 5;
}
if (needRoll)
return rollingRead(SERVERSOCKET);
std::string reply = "";
char buffer[8192] = {0};
constexpr size_t BUFFER_SIZE = 8192;
char buffer[BUFFER_SIZE] = {0};
sizeWritten = read(SERVERSOCKET, buffer, 8192);
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
if (sizeWritten < 0) {
if (errno == EWOULDBLOCK)
log("Hyprland IPC didn't respond in time\n");
log("Couldn't read (5)");
return 5;
log("Couldn't read (6)");
return 6;
}
reply += std::string(buffer, sizeWritten);
while (sizeWritten == 8192) {
sizeWritten = read(SERVERSOCKET, buffer, 8192);
while (sizeWritten == BUFFER_SIZE) {
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
if (sizeWritten < 0) {
log("Couldn't read (5)");
return 5;
log("Couldn't read (6)");
return 6;
}
reply += std::string(buffer, sizeWritten);
}
@@ -228,7 +252,7 @@ int request(std::string arg, int minArgs = 0, bool needRoll = false) {
return 0;
}
int requestHyprpaper(std::string arg) {
int requestIPC(std::string_view filename, std::string_view arg) {
const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
if (SERVERSOCKET < 0) {
@@ -244,9 +268,7 @@ int requestHyprpaper(std::string arg) {
sockaddr_un serverAddress = {0};
serverAddress.sun_family = AF_UNIX;
const std::string USERID = std::to_string(getUID());
std::string socketPath = getRuntimeDir() + "/" + instanceSignature + "/.hyprpaper.sock";
std::string socketPath = getRuntimeDir() + "/" + instanceSignature + "/" + filename;
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
@@ -258,16 +280,16 @@ int requestHyprpaper(std::string arg) {
arg = arg.substr(arg.find_first_of('/') + 1); // strip flags
arg = arg.substr(arg.find_first_of(' ') + 1); // strip "hyprpaper"
auto sizeWritten = write(SERVERSOCKET, arg.c_str(), arg.length());
auto sizeWritten = write(SERVERSOCKET, arg.data(), arg.size());
if (sizeWritten < 0) {
log("Couldn't write (4)");
return 4;
}
constexpr size_t BUFFER_SIZE = 8192;
char buffer[BUFFER_SIZE] = {0};
char buffer[8192] = {0};
sizeWritten = read(SERVERSOCKET, buffer, 8192);
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
if (sizeWritten < 0) {
log("Couldn't read (5)");
@@ -281,11 +303,21 @@ int requestHyprpaper(std::string arg) {
return 0;
}
void batchRequest(std::string arg, bool json) {
std::string commands = arg.substr(arg.find_first_of(' ') + 1);
int requestHyprpaper(std::string_view arg) {
return requestIPC(".hyprpaper.sock", arg);
}
if (json)
int requestHyprsunset(std::string_view arg) {
return requestIPC(".hyprsunset.sock", arg);
}
void batchRequest(std::string_view arg, bool json) {
std::string commands(arg.substr(arg.find_first_of(' ') + 1));
if (json) {
RE2::GlobalReplace(&commands, ";\\s*", ";j/");
commands.insert(0, "j/");
}
std::string rq = "[[BATCH]]" + commands;
request(rq);
@@ -351,7 +383,7 @@ int main(int argc, char** argv) {
parseArgs = false;
continue;
}
if (parseArgs && (ARGS[i][0] == '-') && !isNumber(ARGS[i], true) /* For stuff like -2 */) {
if (parseArgs && (ARGS[i][0] == '-') && !(isNumber(ARGS[i], true) || isNumber(ARGS[i].substr(0, ARGS[i].length() - 1), true)) /* For stuff like -2 or -2, */) {
// parse
if (ARGS[i] == "-j" && !fullArgs.contains("j")) {
fullArgs += "j";
@@ -383,6 +415,8 @@ int main(int argc, char** argv) {
if (cmd == "hyprpaper") {
std::println("{}", HYPRPAPER_HELP);
} else if (cmd == "hyprsunset") {
std::println("{}", HYPRSUNSET_HELP);
} else if (cmd == "notify") {
std::println("{}", NOTIFY_HELP);
} else if (cmd == "output") {
@@ -464,6 +498,8 @@ int main(int argc, char** argv) {
batchRequest(fullRequest, json);
else if (fullRequest.contains("/hyprpaper"))
exitStatus = requestHyprpaper(fullRequest);
else if (fullRequest.contains("/hyprsunset"))
exitStatus = requestHyprsunset(fullRequest);
else if (fullRequest.contains("/switchxkblayout"))
exitStatus = request(fullRequest, 2);
else if (fullRequest.contains("/seterror"))

@@ -9,11 +9,25 @@ file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
set(CMAKE_CXX_STANDARD 23)
pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.2.4)
pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.7.0)
find_package(glaze QUIET)
if (NOT glaze_FOUND)
set(GLAZE_VERSION v5.1.1)
message(STATUS "glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent")
include(FetchContent)
FetchContent_Declare(
glaze
GIT_REPOSITORY https://github.com/stephenberry/glaze.git
GIT_TAG ${GLAZE_VERSION}
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(glaze)
endif()
add_executable(hyprpm ${SRCFILES})
target_link_libraries(hyprpm PUBLIC PkgConfig::hyprpm_deps)
target_link_libraries(hyprpm PUBLIC PkgConfig::hyprpm_deps glaze::glaze)
# binary
install(TARGETS hyprpm)

@@ -14,7 +14,7 @@ hyprpm [<FLAGS>]... <ARGUMENT>
| (list) "List all installed plugins"
| (enable <PLUGINS>) "Load a plugin"
| (disable <PLUGINS>) "Unload a plugin"
| (reload) "Reload all plugins"
| (reload) "Reload plugins to match the enabled/disabled state. Use -f to force reload."
;
<PLUGINS> ::= {{{ hyprpm list | awk '/Plugin/{print $4}' }}};

@@ -1,45 +1,91 @@
#include "DataState.hpp"
#include <sys/stat.h>
#include <toml++/toml.hpp>
#include <print>
#include <filesystem>
#include <sstream>
#include <fstream>
#include "PluginManager.hpp"
#include "../helpers/Die.hpp"
#include "../helpers/Sys.hpp"
#include "../helpers/StringUtils.hpp"
std::string DataState::getDataStatePath() {
const auto HOME = getenv("HOME");
if (!HOME) {
std::println(stderr, "DataState: no $HOME");
throw std::runtime_error("no $HOME");
return "";
static std::string getTempRoot() {
static auto ENV = getenv("XDG_RUNTIME_DIR");
if (!ENV) {
std::cerr << "\nERROR: XDG_RUNTIME_DIR not set!\n";
exit(1);
}
const auto XDG_DATA_HOME = getenv("XDG_DATA_HOME");
const auto STR = ENV + std::string{"/hyprpm/"};
if (XDG_DATA_HOME)
return std::string{XDG_DATA_HOME} + "/hyprpm";
return std::string{HOME} + "/.local/share/hyprpm";
return STR;
}
// write the state to a file
static bool writeState(const std::string& str, const std::string& to) {
// create temp file in a safe temp root
std::ofstream of(getTempRoot() + ".temp-state", std::ios::trunc);
if (!of.good())
return false;
of << str;
of.close();
return NSys::root::install(getTempRoot() + ".temp-state", to, "644");
}
std::filesystem::path DataState::getDataStatePath() {
return std::filesystem::path("/var/cache/hyprpm/" + g_pPluginManager->m_szUsername);
}
std::string DataState::getHeadersPath() {
return getDataStatePath() + "/headersRoot";
return getDataStatePath() / "headersRoot";
}
std::vector<std::filesystem::path> DataState::getPluginStates() {
ensureStateStoreExists();
std::vector<std::filesystem::path> states;
for (const auto& entry : std::filesystem::directory_iterator(getDataStatePath())) {
if (!entry.is_directory() || entry.path().stem() == "headersRoot")
continue;
const auto stateFile = entry.path() / "state.toml";
if (!std::filesystem::exists(stateFile))
continue;
states.emplace_back(stateFile);
}
return states;
}
void DataState::ensureStateStoreExists() {
const auto PATH = getDataStatePath();
if (!std::filesystem::exists(PATH))
std::filesystem::create_directories(PATH);
if (!std::filesystem::exists(getHeadersPath()))
std::filesystem::create_directories(getHeadersPath());
std::error_code ec;
if (!std::filesystem::exists(getHeadersPath(), ec) || ec) {
std::println("{}", infoString("The hyprpm state store doesn't exist. Creating now..."));
if (!std::filesystem::exists("/var/cache/hyprpm/", ec) || ec) {
if (!NSys::root::createDirectory("/var/cache/hyprpm", "755"))
Debug::die("ensureStateStoreExists: Failed to run a superuser cmd");
}
if (!std::filesystem::exists(getDataStatePath(), ec) || ec) {
if (!NSys::root::createDirectory(getDataStatePath().string(), "755"))
Debug::die("ensureStateStoreExists: Failed to run a superuser cmd");
}
if (!NSys::root::createDirectory(getHeadersPath(), "755"))
Debug::die("ensureStateStoreExists: Failed to run a superuser cmd");
}
}
void DataState::addNewPluginRepo(const SPluginRepository& repo) {
ensureStateStoreExists();
const auto PATH = getDataStatePath() + "/" + repo.name;
const auto PATH = getDataStatePath() / repo.name;
std::filesystem::create_directories(PATH);
std::error_code ec;
if (!std::filesystem::exists(PATH, ec) || ec) {
if (!NSys::root::createDirectory(PATH.string(), "755"))
Debug::die("addNewPluginRepo: failed to create cache dir");
}
// clang-format off
auto DATA = toml::table{
{"repository", toml::table{
@@ -50,37 +96,34 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) {
}}
};
for (auto const& p : repo.plugins) {
// copy .so to the good place
if (std::filesystem::exists(p.filename))
std::filesystem::copy_file(p.filename, PATH + "/" + p.name + ".so");
const auto filename = p.name + ".so";
// copy .so to the good place and chmod 755
if (std::filesystem::exists(p.filename)) {
if (!NSys::root::install(p.filename, (PATH / filename).string(), "0755"))
Debug::die("addNewPluginRepo: failed to install so file");
}
DATA.emplace(p.name, toml::table{
{"filename", p.name + ".so"},
{"filename", filename},
{"enabled", p.enabled},
{"failed", p.failed}
});
}
// clang-format on
std::ofstream ofs(PATH + "/state.toml", std::ios::trunc);
ofs << DATA;
ofs.close();
std::stringstream ss;
ss << DATA;
if (!writeState(ss.str(), (PATH / "state.toml").string()))
Debug::die("{}", failureString("Failed to write plugin state"));
}
bool DataState::pluginRepoExists(const std::string& urlOrName) {
ensureStateStoreExists();
const auto PATH = getDataStatePath();
for (const auto& entry : std::filesystem::directory_iterator(PATH)) {
if (!entry.is_directory() || entry.path().stem() == "headersRoot")
continue;
if (!std::filesystem::exists(entry.path().string() + "/state.toml"))
continue;
auto STATE = toml::parse_file(entry.path().string() + "/state.toml");
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("");
@@ -94,31 +137,29 @@ bool DataState::pluginRepoExists(const std::string& urlOrName) {
void DataState::removePluginRepo(const std::string& urlOrName) {
ensureStateStoreExists();
const auto PATH = getDataStatePath();
for (const auto& entry : std::filesystem::directory_iterator(PATH)) {
if (!entry.is_directory() || entry.path().stem() == "headersRoot")
continue;
if (!std::filesystem::exists(entry.path().string() + "/state.toml"))
continue;
auto STATE = toml::parse_file(entry.path().string() + "/state.toml");
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) {
// unload the plugins!!
for (const auto& file : std::filesystem::directory_iterator(entry.path())) {
for (const auto& file : std::filesystem::directory_iterator(stateFile.parent_path())) {
if (!file.path().string().ends_with(".so"))
continue;
g_pPluginManager->loadUnloadPlugin(std::filesystem::absolute(file.path()), false);
}
std::filesystem::remove_all(entry.path());
const auto PATH = stateFile.parent_path().string();
if (!PATH.starts_with("/var/cache/hyprpm") || PATH.contains('\''))
return; // WTF?
// scary!
if (!NSys::root::removeRecursive(PATH))
Debug::die("removePluginRepo: failed to remove dir");
return;
}
}
@@ -129,7 +170,11 @@ void DataState::updateGlobalState(const SGlobalState& state) {
const auto PATH = getDataStatePath();
std::filesystem::create_directories(PATH);
std::error_code ec;
if (!std::filesystem::exists(PATH, ec) || ec) {
if (!NSys::root::createDirectory(PATH.string(), "755"))
Debug::die("updateGlobalState: failed to create dir");
}
// clang-format off
auto DATA = toml::table{
{"state", toml::table{
@@ -139,20 +184,23 @@ void DataState::updateGlobalState(const SGlobalState& state) {
};
// clang-format on
std::ofstream ofs(PATH + "/state.toml", std::ios::trunc);
ofs << DATA;
ofs.close();
std::stringstream ss;
ss << DATA;
if (!writeState(ss.str(), (PATH / "state.toml").string()))
Debug::die("{}", failureString("Failed to write plugin state"));
}
SGlobalState DataState::getGlobalState() {
ensureStateStoreExists();
const auto PATH = getDataStatePath();
const auto stateFile = getDataStatePath() / "state.toml";
if (!std::filesystem::exists(PATH + "/state.toml"))
std::error_code ec;
if (!std::filesystem::exists(stateFile, ec) || ec)
return SGlobalState{};
auto DATA = toml::parse_file(PATH + "/state.toml");
auto DATA = toml::parse_file(stateFile.c_str());
SGlobalState state;
state.headersHashCompiled = DATA["state"]["hash"].value_or("");
@@ -164,18 +212,9 @@ SGlobalState DataState::getGlobalState() {
std::vector<SPluginRepository> DataState::getAllRepositories() {
ensureStateStoreExists();
const auto PATH = getDataStatePath();
std::vector<SPluginRepository> repos;
for (const auto& entry : std::filesystem::directory_iterator(PATH)) {
if (!entry.is_directory() || entry.path().stem() == "headersRoot")
continue;
if (!std::filesystem::exists(entry.path().string() + "/state.toml"))
continue;
auto STATE = toml::parse_file(entry.path().string() + "/state.toml");
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("");
@@ -208,17 +247,8 @@ std::vector<SPluginRepository> DataState::getAllRepositories() {
bool DataState::setPluginEnabled(const std::string& name, bool enabled) {
ensureStateStoreExists();
const auto PATH = getDataStatePath();
for (const auto& entry : std::filesystem::directory_iterator(PATH)) {
if (!entry.is_directory() || entry.path().stem() == "headersRoot")
continue;
if (!std::filesystem::exists(entry.path().string() + "/state.toml"))
continue;
auto STATE = toml::parse_file(entry.path().string() + "/state.toml");
for (const auto& stateFile : getPluginStates()) {
const auto STATE = toml::parse_file(stateFile.c_str());
for (const auto& [key, val] : STATE) {
if (key == "repository")
continue;
@@ -231,11 +261,14 @@ bool DataState::setPluginEnabled(const std::string& name, bool enabled) {
if (FAILED)
return false;
(*STATE[key].as_table()).insert_or_assign("enabled", enabled);
auto modifiedState = STATE;
(*modifiedState[key].as_table()).insert_or_assign("enabled", enabled);
std::ofstream state(entry.path().string() + "/state.toml", std::ios::trunc);
state << STATE;
state.close();
std::stringstream ss;
ss << modifiedState;
if (!writeState(ss.str(), stateFile.string()))
Debug::die("{}", failureString("Failed to write plugin state"));
return true;
}
@@ -243,3 +276,18 @@ bool DataState::setPluginEnabled(const std::string& name, bool enabled) {
return false;
}
void DataState::purgeAllCache() {
std::error_code ec;
if (!std::filesystem::exists(getDataStatePath()) && !ec) {
std::println("{}", infoString("Nothing to do"));
return;
}
const auto PATH = getDataStatePath().string();
if (PATH.contains('\''))
return;
// scary!
if (!NSys::root::removeRecursive(PATH))
Debug::die("Failed to run a superuser cmd");
}

@@ -1,4 +1,5 @@
#pragma once
#include <filesystem>
#include <string>
#include <vector>
#include "Plugin.hpp"
@@ -9,13 +10,15 @@ struct SGlobalState {
};
namespace DataState {
std::string getDataStatePath();
std::filesystem::path getDataStatePath();
std::string getHeadersPath();
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 updateGlobalState(const SGlobalState& state);
void purgeAllCache();
SGlobalState getGlobalState();
bool setPluginEnabled(const std::string& name, bool enabled);
std::vector<SPluginRepository> getAllRepositories();

@@ -0,0 +1,86 @@
#include "HyprlandSocket.hpp"
#include <pwd.h>
#include <sys/socket.h>
#include "../helpers/StringUtils.hpp"
#include <print>
#include <sys/un.h>
#include <unistd.h>
#include <cstring>
static int getUID() {
const auto UID = getuid();
const auto PWUID = getpwuid(UID);
return PWUID ? PWUID->pw_uid : UID;
}
static std::string getRuntimeDir() {
const auto XDG = getenv("XDG_RUNTIME_DIR");
if (!XDG) {
const std::string USERID = std::to_string(getUID());
return "/run/user/" + USERID + "/hypr";
}
return std::string{XDG} + "/hypr";
}
std::string NHyprlandSocket::send(const std::string& cmd) {
const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
if (SERVERSOCKET < 0) {
std::println("{}", failureString("Couldn't open a socket (1)"));
return "";
}
const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
if (!HIS) {
std::println("{}", failureString("HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?) (3)"));
return "";
}
sockaddr_un serverAddress = {0};
serverAddress.sun_family = AF_UNIX;
std::string socketPath = getRuntimeDir() + "/" + HIS + "/.socket.sock";
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
if (connect(SERVERSOCKET, (sockaddr*)&serverAddress, SUN_LEN(&serverAddress)) < 0) {
std::println("{}", failureString("Couldn't connect to " + socketPath + ". (4)"));
return "";
}
auto sizeWritten = write(SERVERSOCKET, cmd.c_str(), cmd.length());
if (sizeWritten < 0) {
std::println("{}", failureString("Couldn't write (5)"));
return "";
}
std::string reply = "";
constexpr size_t BUFFER_SIZE = 8192;
char buffer[BUFFER_SIZE] = {0};
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
if (sizeWritten < 0) {
std::println("{}", failureString("Couldn't read (6)"));
return "";
}
reply += std::string(buffer, sizeWritten);
while (sizeWritten == BUFFER_SIZE) {
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
if (sizeWritten < 0) {
std::println("{}", failureString("Couldn't read (7)"));
return "";
}
reply += std::string(buffer, sizeWritten);
}
close(SERVERSOCKET);
return reply;
}

@@ -0,0 +1,7 @@
#pragma once
#include <string>
namespace NHyprlandSocket {
std::string send(const std::string& cmd);
};

@@ -1,6 +1,12 @@
#include "Manifest.hpp"
#include <toml++/toml.hpp>
#include <iostream>
#include <algorithm>
// Alphanumerics and -_ allowed for plugin names. No magic names.
// [A-Za-z0-9\-_]*
static bool validManifestName(const std::string_view& n) {
return std::ranges::all_of(n, [](const char& c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '-' || c == '_' || c == '=' || (c >= '0' && c <= '9'); });
}
CManifest::CManifest(const eManifestType type, const std::string& path) {
auto manifest = toml::parse_file(path);
@@ -11,11 +17,17 @@ CManifest::CManifest(const eManifestType type, const std::string& path) {
continue;
CManifest::SManifestPlugin plugin;
plugin.name = key;
m_vPlugins.push_back(plugin);
if (!validManifestName(key.str())) {
m_good = false;
return;
}
for (auto& plugin : m_vPlugins) {
plugin.name = key;
m_plugins.push_back(plugin);
}
for (auto& plugin : m_plugins) {
plugin.description = manifest[plugin.name]["description"].value_or("?");
plugin.version = manifest[plugin.name]["version"].value_or("?");
plugin.output = manifest[plugin.name]["build"]["output"].value_or("?");
@@ -37,21 +49,21 @@ CManifest::CManifest(const eManifestType type, const std::string& path) {
}
if (plugin.output.empty() || plugin.buildSteps.empty()) {
m_bGood = false;
m_good = false;
return;
}
}
} else if (type == MANIFEST_HYPRPM) {
m_sRepository.name = manifest["repository"]["name"].value_or("");
m_repository.name = manifest["repository"]["name"].value_or("");
auto authors = manifest["repository"]["authors"].as_array();
if (authors) {
for (auto&& a : *authors) {
m_sRepository.authors.push_back(a.as_string()->value_or("?"));
m_repository.authors.push_back(a.as_string()->value_or("?"));
}
} else {
auto author = manifest["repository"]["author"].value_or("");
if (!std::string{author}.empty())
m_sRepository.authors.push_back(author);
m_repository.authors.push_back(author);
}
auto pins = manifest["repository"]["commit_pins"].as_array();
@@ -59,7 +71,7 @@ CManifest::CManifest(const eManifestType type, const std::string& path) {
for (auto&& pin : *pins) {
auto pinArr = pin.as_array();
if (pinArr && pinArr->get(1))
m_sRepository.commitPins.push_back(std::make_pair<>(pinArr->get(0)->as_string()->get(), pinArr->get(1)->as_string()->get()));
m_repository.commitPins.push_back(std::make_pair<>(pinArr->get(0)->as_string()->get(), pinArr->get(1)->as_string()->get()));
}
}
@@ -68,11 +80,17 @@ CManifest::CManifest(const eManifestType type, const std::string& path) {
continue;
CManifest::SManifestPlugin plugin;
plugin.name = key;
m_vPlugins.push_back(plugin);
if (!validManifestName(key.str())) {
m_good = false;
return;
}
for (auto& plugin : m_vPlugins) {
plugin.name = key;
m_plugins.push_back(plugin);
}
for (auto& plugin : m_plugins) {
plugin.description = manifest[plugin.name]["description"].value_or("?");
plugin.output = manifest[plugin.name]["output"].value_or("?");
plugin.since = manifest[plugin.name]["since_hyprland"].value_or(0);
@@ -94,12 +112,12 @@ CManifest::CManifest(const eManifestType type, const std::string& path) {
}
if (plugin.output.empty() || plugin.buildSteps.empty()) {
m_bGood = false;
m_good = false;
return;
}
}
} else {
// ???
m_bGood = false;
m_good = false;
}
}

@@ -27,8 +27,8 @@ class CManifest {
std::string name;
std::vector<std::string> authors;
std::vector<std::pair<std::string, std::string>> commitPins;
} m_sRepository;
} m_repository;
std::vector<SManifestPlugin> m_vPlugins;
bool m_bGood = true;
std::vector<SManifestPlugin> m_plugins;
bool m_good = true;
};

@@ -4,13 +4,14 @@
#include "../progress/CProgressBar.hpp"
#include "Manifest.hpp"
#include "DataState.hpp"
#include "HyprlandSocket.hpp"
#include "../helpers/Sys.hpp"
#include "../helpers/Die.hpp"
#include <cstdio>
#include <iostream>
#include <array>
#include <filesystem>
#include <print>
#include <thread>
#include <fstream>
#include <algorithm>
#include <format>
@@ -21,6 +22,7 @@
#include <unistd.h>
#include <toml++/toml.hpp>
#include <glaze/glaze.hpp>
#include <hyprutils/string/String.hpp>
#include <hyprutils/os/Process.hpp>
@@ -50,6 +52,13 @@ static std::string getTempRoot() {
return STR;
}
CPluginManager::CPluginManager() {
if (NSys::isSuperuser())
Debug::die("Don't run hyprpm as a superuser.");
m_szUsername = getpwuid(NSys::getUID())->pw_name;
}
SHyprlandVersion CPluginManager::getHyprlandVersion(bool running) {
static bool onceRunning = false;
static bool onceInstalled = false;
@@ -67,7 +76,7 @@ SHyprlandVersion CPluginManager::getHyprlandVersion(bool running) {
else
onceInstalled = true;
const auto HLVERCALL = running ? execAndGet("hyprctl version") : execAndGet("Hyprland --version");
const auto HLVERCALL = running ? NHyprlandSocket::send("/version") : execAndGet("Hyprland --version");
if (m_bVerbose)
std::println("{}", verboseString("{} version returned: {}", running ? "running" : "installed", HLVERCALL));
@@ -83,13 +92,13 @@ SHyprlandVersion CPluginManager::getHyprlandVersion(bool running) {
hlbranch = hlbranch.substr(0, hlbranch.find(" at commit "));
std::string hldate = HLVERCALL.substr(HLVERCALL.find("Date: ") + 6);
hldate = hldate.substr(0, hldate.find("\n"));
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(" "));
hlcommits = hlcommits.substr(0, hlcommits.find(' '));
}
int commits = 0;
@@ -130,7 +139,8 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
const auto HLVER = getHyprlandVersion();
if (!hasDeps()) {
std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio, pkg-config"));
std::println(stderr, "\n{}",
failureString("Could not clone the plugin repository. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio, pkg-config, git, g++, gcc"));
return false;
}
@@ -149,6 +159,11 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
DataState::updateGlobalState(GLOBALSTATE);
}
if (GLOBALSTATE.headersHashCompiled.empty()) {
std::println("\n{}", failureString("Cannot find headers in the global state. Try running hyprpm update first."));
return false;
}
std::cout << Colors::GREEN << "" << Colors::RESET << Colors::RED << " adding a new plugin repository " << Colors::RESET << "from " << url << "\n " << Colors::RED
<< "MAKE SURE" << Colors::RESET << " that you trust the authors. " << Colors::RED << "DO NOT" << Colors::RESET
<< " install random plugins without verifying the code and author.\n "
@@ -226,14 +241,14 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
return false;
}
if (!pManifest->m_bGood) {
if (!pManifest->m_good) {
std::println(stderr, "\n{}", failureString("The provided plugin repository has a corrupted manifest"));
return false;
}
progress.m_iSteps = 2;
progress.printMessageAbove(successString("parsed manifest, found " + std::to_string(pManifest->m_vPlugins.size()) + " plugins:"));
for (auto const& pl : pManifest->m_vPlugins) {
progress.printMessageAbove(successString("parsed manifest, found " + std::to_string(pManifest->m_plugins.size()) + " plugins:"));
for (auto const& pl : pManifest->m_plugins) {
std::string message = "" + pl.name + " by ";
for (auto const& a : pl.authors) {
message += a + ", ";
@@ -246,12 +261,12 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
progress.printMessageAbove(message);
}
if (!pManifest->m_sRepository.commitPins.empty()) {
if (rev.empty() && !pManifest->m_repository.commitPins.empty()) {
// check commit pins
progress.printMessageAbove(infoString("Manifest has {} pins, checking", pManifest->m_sRepository.commitPins.size()));
progress.printMessageAbove(infoString("Manifest has {} pins, checking", pManifest->m_repository.commitPins.size()));
for (auto const& [hl, plugin] : pManifest->m_sRepository.commitPins) {
for (auto const& [hl, plugin] : pManifest->m_repository.commitPins) {
if (hl != HLVER.hash)
continue;
@@ -274,6 +289,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
if (HEADERSSTATUS != HEADERS_OK) {
std::println("\n{}", headerError(HEADERSSTATUS));
std::println("\n{}", infoString("if the problem persists, try running hyprpm purge-cache."));
return false;
}
@@ -282,7 +298,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
progress.m_szCurrentMessage = "Building plugin(s)";
progress.print();
for (auto& p : pManifest->m_vPlugins) {
for (auto& p : pManifest->m_plugins) {
std::string out;
if (p.since > HLVER.commits && HLVER.commits >= 1 /* for --depth 1 clones, we can't check this. */) {
@@ -325,11 +341,11 @@ 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_sRepository.name.empty() ? url.substr(url.find_last_of('/') + 1) : pManifest->m_sRepository.name;
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;
for (auto const& p : pManifest->m_vPlugins) {
for (auto const& p : pManifest->m_plugins) {
repo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, false, p.failed});
}
DataState::addNewPluginRepo(repo);
@@ -378,7 +394,7 @@ eHeadersErrors CPluginManager::headersValid() {
// find headers commit
const std::string& cmd = std::format("PKG_CONFIG_PATH=\"{}/share/pkgconfig\" pkgconf --cflags --keep-system-cflags hyprland", DataState::getHeadersPath());
auto headers = execAndGet(cmd.c_str());
auto headers = execAndGet(cmd);
if (!headers.contains("-I/"))
return HEADERS_MISSING;
@@ -436,7 +452,7 @@ bool CPluginManager::updateHeaders(bool force) {
const auto HLVER = getHyprlandVersion(false);
if (!hasDeps()) {
std::println("\n{}", failureString("Could not update. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio, pkg-config"));
std::println("\n{}", failureString("Could not update. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio, pkg-config, git, g++, gcc"));
return false;
}
@@ -464,7 +480,9 @@ bool CPluginManager::updateHeaders(bool force) {
return false;
}
progress.printMessageAbove(statusString("!", Colors::YELLOW, "Cloning https://github.com/hyprwm/Hyprland, this might take a moment."));
const auto& HL_URL = m_szCustomHlUrl.empty() ? "https://github.com/hyprwm/Hyprland" : m_szCustomHlUrl;
progress.printMessageAbove(statusString("!", Colors::YELLOW, "Cloning {}, this might take a moment.", HL_URL));
const bool bShallow = (HLVER.branch == "main") && !m_bNoShallow;
@@ -475,12 +493,12 @@ bool CPluginManager::updateHeaders(bool force) {
if (m_bVerbose && bShallow)
progress.printMessageAbove(verboseString("will shallow since: {}", SHALLOW_DATE));
std::string ret = execAndGet(std::format("cd {} && git clone --recursive https://github.com/hyprwm/Hyprland hyprland-{}{}", getTempRoot(), USERNAME,
(bShallow ? " --shallow-since='" + SHALLOW_DATE + "'" : "")));
std::string ret =
execAndGet(std::format("cd {} && git clone --recursive {} hyprland-{}{}", getTempRoot(), HL_URL, USERNAME, (bShallow ? " --shallow-since='" + SHALLOW_DATE + "'" : "")));
if (!std::filesystem::exists(WORKINGDIR)) {
progress.printMessageAbove(failureString("Clone failed. Retrying without shallow."));
ret = execAndGet(std::format("cd {} && git clone --recursive https://github.com/hyprwm/hyprland hyprland-{}", getTempRoot(), USERNAME));
ret = execAndGet(std::format("cd {} && git clone --recursive {} hyprland-{}", getTempRoot(), HL_URL, USERNAME));
}
if (!std::filesystem::exists(WORKINGDIR + "/.git")) {
@@ -547,13 +565,21 @@ bool CPluginManager::updateHeaders(bool force) {
progress.m_szCurrentMessage = "Installing sources";
progress.print();
const std::string& cmd =
std::format("sed -i -e \"s#PREFIX = /usr/local#PREFIX = {}#\" {}/Makefile && cd {} && make installheaders", DataState::getHeadersPath(), WORKINGDIR, WORKINGDIR);
std::string cmd = std::format("sed -i -e \"s#PREFIX = /usr/local#PREFIX = {}#\" {}/Makefile", DataState::getHeadersPath(), WORKINGDIR);
if (m_bVerbose)
progress.printMessageAbove(verboseString("installation will run: {}", cmd));
progress.printMessageAbove(verboseString("prepare install will run: {}", cmd));
ret = execAndGet(cmd);
cmd = std::format("make -C '{}' installheaders && chmod -R 644 '{}' && find '{}' -type d -exec chmod a+x {{}} \\;", WORKINGDIR, DataState::getHeadersPath(),
DataState::getHeadersPath());
if (m_bVerbose)
progress.printMessageAbove(verboseString("install will run as sudo: {}", cmd));
// WORKINGDIR and headersPath should not contain anything unsafe. Usernames can't contain cmd exec parts.
ret = NSys::root::runAsSuperuserUnsafe(cmd);
if (m_bVerbose)
std::println("{}", verboseString("installer returned: {}", ret));
@@ -567,9 +593,14 @@ bool CPluginManager::updateHeaders(bool force) {
progress.m_szCurrentMessage = "Done!";
progress.print();
auto GLOBALSTATE = DataState::getGlobalState();
GLOBALSTATE.headersHashCompiled = HLVER.hash;
DataState::updateGlobalState(GLOBALSTATE);
std::print("\n");
} else {
progress.printMessageAbove(failureString("failed to install headers with error code {} ({})", (int)HEADERSVALID, headerErrorShort(HEADERSVALID)));
progress.printMessageAbove(infoString("if the problem persists, try running hyprpm purge-cache."));
progress.m_iSteps = 5;
progress.m_szCurrentMessage = "Failed";
progress.print();
@@ -676,17 +707,17 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
continue;
}
if (!pManifest->m_bGood) {
std::println(stderr, "\n{}", failureString("The provided plugin repository has a corrupted manifest"));
if (!pManifest->m_good) {
std::println(stderr, "\n{}", failureString("The provided plugin repository has a bad manifest"));
continue;
}
if (repo.rev.empty() && !pManifest->m_sRepository.commitPins.empty()) {
if (repo.rev.empty() && !pManifest->m_repository.commitPins.empty()) {
// check commit pins unless a revision is specified
progress.printMessageAbove(infoString("Manifest has {} pins, checking", pManifest->m_sRepository.commitPins.size()));
progress.printMessageAbove(infoString("Manifest has {} pins, checking", pManifest->m_repository.commitPins.size()));
for (auto const& [hl, plugin] : pManifest->m_sRepository.commitPins) {
for (auto const& [hl, plugin] : pManifest->m_repository.commitPins) {
if (hl != HLVER.hash)
continue;
@@ -696,7 +727,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
}
}
for (auto& p : pManifest->m_vPlugins) {
for (auto& p : pManifest->m_plugins) {
std::string out;
if (p.since > HLVER.commits && HLVER.commits >= 1000 /* for shallow clones, we can't check this. 1000 is an arbitrary number I chose. */) {
@@ -738,7 +769,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
if (repohash.length() > 0)
repohash.pop_back();
newrepo.hash = repohash;
for (auto const& p : pManifest->m_vPlugins) {
for (auto const& p : pManifest->m_plugins) {
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});
}
@@ -781,7 +812,7 @@ bool CPluginManager::disablePlugin(const std::string& name) {
return ret;
}
ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState() {
ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState(bool forceReload) {
if (headersValid() != HEADERS_OK) {
std::println(stderr, "\n{}", failureString("headers are not up-to-date, please run hyprpm update."));
return LOADSTATE_HEADERS_OUTDATED;
@@ -790,35 +821,28 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState() {
const auto HOME = getenv("HOME");
const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
if (!HOME || !HIS) {
std::println(stderr, "PluginManager: no $HOME or HIS");
std::println(stderr, "PluginManager: no $HOME or $HYPRLAND_INSTANCE_SIGNATURE");
return LOADSTATE_FAIL;
}
const auto HYPRPMPATH = DataState::getDataStatePath() + "/";
const auto HYPRPMPATH = DataState::getDataStatePath();
auto pluginLines = execAndGet("hyprctl plugins list | grep Plugin");
const auto json = glz::read_json<glz::json_t::array_t>(NHyprlandSocket::send("j/plugins list"));
if (!json) {
std::println(stderr, "PluginManager: couldn't parse plugin list output");
return LOADSTATE_FAIL;
}
std::vector<std::string> loadedPlugins;
for (const auto& plugin : json.value()) {
if (!plugin.is_object() || !plugin.contains("name")) {
std::println(stderr, "PluginManager: couldn't parse plugin object");
return LOADSTATE_FAIL;
}
loadedPlugins.emplace_back(plugin["name"].get<std::string>());
}
std::println("{}", successString("Ensuring plugin load state"));
// iterate line by line
while (!pluginLines.empty()) {
auto plLine = pluginLines.substr(0, pluginLines.find('\n'));
if (pluginLines.find('\n') != std::string::npos)
pluginLines = pluginLines.substr(pluginLines.find('\n') + 1);
else
pluginLines = "";
if (plLine.back() != ':')
continue;
plLine = plLine.substr(7);
plLine = plLine.substr(0, plLine.find(" by "));
loadedPlugins.push_back(plLine);
}
// get state
const auto REPOS = DataState::getAllRepositories();
@@ -849,11 +873,11 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState() {
// (and Hyprland needs to restart)
bool hyprlandVersionMismatch = false;
// unload disabled plugins
// unload disabled plugins (or all if forceReload is true)
for (auto const& p : loadedPlugins) {
if (!enabled(p)) {
if (forceReload || !enabled(p)) {
// unload
if (!loadUnloadPlugin(HYPRPMPATH + repoForName(p) + "/" + p + ".so", false)) {
if (!loadUnloadPlugin(HYPRPMPATH / repoForName(p) / (p + ".so"), false)) {
std::println("{}", infoString("{} will be unloaded after restarting Hyprland", p));
hyprlandVersionMismatch = true;
} else
@@ -867,10 +891,10 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState() {
if (!p.enabled)
continue;
if (std::find_if(loadedPlugins.begin(), loadedPlugins.end(), [&](const auto& other) { return other == p.name; }) != loadedPlugins.end())
if (!forceReload && std::find_if(loadedPlugins.begin(), loadedPlugins.end(), [&](const auto& other) { return other == p.name; }) != loadedPlugins.end())
continue;
if (!loadUnloadPlugin(HYPRPMPATH + repoForName(p.name) + "/" + p.filename, true)) {
if (!loadUnloadPlugin(HYPRPMPATH / repoForName(p.name) / p.filename, true)) {
std::println("{}", infoString("{} will be loaded after restarting Hyprland", p.name));
hyprlandVersionMismatch = true;
} else
@@ -888,14 +912,15 @@ bool CPluginManager::loadUnloadPlugin(const std::string& path, bool load) {
auto HLVER = getHyprlandVersion(true);
if (state.headersHashCompiled != HLVER.hash) {
std::println("{}", infoString("Running Hyprland version differs from plugin state, please restart Hyprland."));
if (load)
std::println("{}", infoString("Running Hyprland version ({}) differs from plugin state ({}), please restart Hyprland.", HLVER.hash, state.headersHashCompiled));
return false;
}
if (load)
execAndGet("hyprctl plugin load " + path);
NHyprlandSocket::send("/plugin load " + path);
else
execAndGet("hyprctl plugin unload " + path);
NHyprlandSocket::send("/plugin unload " + path);
return true;
}
@@ -920,7 +945,7 @@ void CPluginManager::listAllPlugins() {
}
void CPluginManager::notify(const eNotifyIcons icon, uint32_t color, int durationMs, const std::string& message) {
execAndGet("hyprctl notify " + std::to_string((int)icon) + " " + std::to_string(durationMs) + " " + std::to_string(color) + " " + message);
NHyprlandSocket::send("/notify " + std::to_string((int)icon) + " " + std::to_string(durationMs) + " " + std::to_string(color) + " " + message);
}
std::string CPluginManager::headerError(const eHeadersErrors err) {
@@ -953,11 +978,15 @@ std::string CPluginManager::headerErrorShort(const eHeadersErrors err) {
}
bool CPluginManager::hasDeps() {
std::vector<std::string> deps = {"meson", "cpio", "cmake", "pkg-config"};
bool hasAllDeps = true;
std::vector<std::string> deps = {"meson", "cpio", "cmake", "pkg-config", "g++", "gcc", "git"};
for (auto const& d : deps) {
if (!execAndGet("command -v " + d).contains("/"))
return false;
if (!execAndGet("command -v " + d).contains("/")) {
std::println(stderr, "{}", failureString("Missing dependency: {}", d));
hasAllDeps = false;
}
}
return true;
return hasAllDeps;
}

@@ -40,6 +40,8 @@ struct SHyprlandVersion {
class CPluginManager {
public:
CPluginManager();
bool addNewPluginRepo(const std::string& url, const std::string& rev);
bool removePluginRepo(const std::string& urlOrName);
@@ -51,7 +53,7 @@ class CPluginManager {
bool enablePlugin(const std::string& name);
bool disablePlugin(const std::string& name);
ePluginLoadStateReturn ensurePluginsLoadState();
ePluginLoadStateReturn ensurePluginsLoadState(bool forceReload = false);
bool loadUnloadPlugin(const std::string& path, bool load);
SHyprlandVersion getHyprlandVersion(bool running = true);
@@ -62,6 +64,7 @@ class CPluginManager {
bool m_bVerbose = false;
bool m_bNoShallow = false;
std::string m_szCustomHlUrl, m_szUsername;
// will delete recursively if exists!!
bool createSafeDirectory(const std::string& path);

@@ -0,0 +1,15 @@
#pragma once
#include <format>
#include <iostream>
// NOLINTNEXTLINE
namespace Debug {
template <typename... Args>
void die(std::format_string<Args...> fmt, Args&&... args) {
const std::string logMsg = std::vformat(fmt.get(), std::make_format_args(args...));
std::cout << "\n[ERR] " << logMsg << "\n";
exit(1);
}
};

168
hyprpm/src/helpers/Sys.cpp Normal file

@@ -0,0 +1,168 @@
#include "Sys.hpp"
#include "Die.hpp"
#include "StringUtils.hpp"
#include <pwd.h>
#include <unistd.h>
#include <sstream>
#include <print>
#include <filesystem>
#include <algorithm>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/string/VarList.hpp>
using namespace Hyprutils::OS;
using namespace Hyprutils::String;
inline constexpr std::array<std::string_view, 3> SUPERUSER_BINARIES = {
"sudo",
"doas",
"run0",
};
static std::string validSubinsAsStr() {
std::ostringstream oss;
auto it = SUPERUSER_BINARIES.begin();
if (it != SUPERUSER_BINARIES.end()) {
oss << *it++;
for (; it != SUPERUSER_BINARIES.end(); ++it)
oss << ", " << *it;
}
return oss.str();
}
static bool executableExistsInPath(const std::string& exe) {
const char* PATHENV = std::getenv("PATH");
if (!PATHENV)
return false;
CVarList paths(PATHENV, 0, ':', true);
std::error_code ec;
for (const auto& PATH : paths) {
std::filesystem::path candidate = std::filesystem::path(PATH) / exe;
if (!std::filesystem::exists(candidate, ec) || ec)
continue;
if (!std::filesystem::is_regular_file(candidate, ec) || ec)
continue;
auto perms = std::filesystem::status(candidate, ec).permissions();
if (ec)
continue;
if ((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none)
return true;
}
return false;
}
static std::string subin() {
static std::string bin;
static bool once = true;
if (!once)
return bin;
for (const auto& BIN : SUPERUSER_BINARIES) {
if (!executableExistsInPath(std::string{BIN}))
continue;
bin = BIN;
break;
}
once = false;
if (bin.empty())
Debug::die("{}", failureString("No valid superuser binary present. Supported: {}", validSubinsAsStr()));
return bin;
}
static bool verifyStringValid(const std::string& s) {
return std::ranges::none_of(s, [](const char& c) { return c == '`' || c == '$' || c == '(' || c == ')' || c == '\'' || c == '"'; });
}
int NSys::getUID() {
const auto UID = getuid();
const auto PWUID = getpwuid(UID);
return PWUID ? PWUID->pw_uid : UID;
}
int NSys::getEUID() {
const auto UID = geteuid();
const auto PWUID = getpwuid(UID);
return PWUID ? PWUID->pw_uid : UID;
}
bool NSys::isSuperuser() {
return getuid() != geteuid() || geteuid() == 0;
}
void NSys::root::cacheSudo() {
// "caches" the sudo so that the prompt later doesn't pop up in a weird spot
// sudo will not ask us again for a moment
CProcess proc(subin(), {"echo", "hyprland"});
proc.runSync();
}
void NSys::root::dropSudo() {
if (subin() != "sudo") {
std::println("{}", infoString("Don't know how to drop timestamp for '{}', ignoring.", subin()));
return;
}
CProcess proc(subin(), {"-k"});
proc.runSync();
}
bool NSys::root::createDirectory(const std::string& path, const std::string& mode) {
if (!verifyStringValid(path))
return false;
if (!std::ranges::all_of(mode, [](const char& c) { return c >= '0' && c <= '9'; }))
return false;
CProcess proc(subin(), {"mkdir", "-p", "-m", mode, path});
return proc.runSync() && proc.exitCode() == 0;
}
bool NSys::root::removeRecursive(const std::string& path) {
if (!verifyStringValid(path))
return false;
std::error_code ec;
const std::string PATH_ABSOLUTE = std::filesystem::canonical(path, ec);
if (ec)
return false;
if (!PATH_ABSOLUTE.starts_with("/var/cache/hyprpm"))
return false;
CProcess proc(subin(), {"rm", "-fr", PATH_ABSOLUTE});
return proc.runSync() && proc.exitCode() == 0;
}
bool NSys::root::install(const std::string& what, const std::string& where, const std::string& mode) {
if (!verifyStringValid(what) || !verifyStringValid(where))
return false;
if (!std::ranges::all_of(mode, [](const char& c) { return c >= '0' && c <= '9'; }))
return false;
CProcess proc(subin(), {"install", "-m" + mode, "-o", "0", "-g", "0", what, where});
return proc.runSync() && proc.exitCode() == 0;
}
std::string NSys::root::runAsSuperuserUnsafe(const std::string& cmd) {
CProcess proc(subin(), {"/bin/sh", "-c", cmd});
if (!proc.runSync())
return "";
return proc.stdOut();
}

@@ -0,0 +1,23 @@
#pragma once
#include <string>
namespace NSys {
bool isSuperuser();
int getUID();
int getEUID();
// NOLINTNEXTLINE
namespace root {
void cacheSudo();
void dropSudo();
//
[[nodiscard("Discarding could lead to vulnerabilities and bugs")]] bool createDirectory(const std::string& path, const std::string& mode);
[[nodiscard("Discarding could lead to vulnerabilities and bugs")]] bool removeRecursive(const std::string& path);
[[nodiscard("Discarding could lead to vulnerabilities and bugs")]] bool install(const std::string& what, const std::string& where, const std::string& mode);
// Do not use this unless absolutely necessary!
std::string runAsSuperuserUnsafe(const std::string& cmd);
};
};

@@ -2,33 +2,36 @@
#include "helpers/StringUtils.hpp"
#include "core/PluginManager.hpp"
#include "core/DataState.hpp"
#include "helpers/Sys.hpp"
#include <cstdio>
#include <vector>
#include <string>
#include <print>
#include <chrono>
#include <thread>
#include <hyprutils/utils/ScopeGuard.hpp>
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
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
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
--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.
)#";
@@ -45,6 +48,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;
std::string customHlUrl;
for (int i = 1; i < argc; ++i) {
if (ARGS[i].starts_with("-")) {
@@ -59,6 +63,13 @@ int main(int argc, char** argv, char** envp) {
verbose = true;
} else if (ARGS[i] == "--no-shallow" || ARGS[i] == "-s") {
noShallow = true;
} else if (ARGS[i] == "--hl-url") {
if (i + 1 >= argc) {
std::println(stderr, "Missing argument for --hl-url");
return 1;
}
customHlUrl = ARGS[i + 1];
i++;
} else if (ARGS[i] == "--force" || ARGS[i] == "-f") {
force = true;
std::println("{}", statusString("!", Colors::RED, "Using --force, I hope you know what you are doing."));
@@ -66,19 +77,19 @@ int main(int argc, char** argv, char** envp) {
std::println(stderr, "Unrecognized option {}", ARGS[i]);
return 1;
}
} else {
} else
command.push_back(ARGS[i]);
}
}
if (command.empty()) {
std::println(stderr, "{}", HELP);
return 0;
return 1;
}
g_pPluginManager = std::make_unique<CPluginManager>();
g_pPluginManager->m_bVerbose = verbose;
g_pPluginManager->m_bNoShallow = noShallow;
g_pPluginManager->m_szCustomHlUrl = customHlUrl;
if (command[0] == "add") {
if (command.size() < 2) {
@@ -87,21 +98,40 @@ int main(int argc, char** argv, char** envp) {
}
std::string rev = "";
if (command.size() >= 3) {
if (command.size() >= 3)
rev = command[2];
const auto HLVER = g_pPluginManager->getHyprlandVersion();
auto GLOBALSTATE = DataState::getGlobalState();
if (GLOBALSTATE.headersHashCompiled != HLVER.hash) {
std::println(stderr, "{}", failureString("Headers outdated, please run hyprpm update."));
return 1;
}
NSys::root::cacheSudo();
CScopeGuard x([] { NSys::root::dropSudo(); });
g_pPluginManager->updateHeaders(false);
return g_pPluginManager->addNewPluginRepo(command[1], rev) ? 0 : 1;
} else if (command[0] == "remove") {
if (ARGS.size() < 2) {
if (command.size() < 2) {
std::println(stderr, "{}", failureString("Not enough args for remove."));
return 1;
}
NSys::root::cacheSudo();
CScopeGuard x([] { NSys::root::dropSudo(); });
return g_pPluginManager->removePluginRepo(command[1]) ? 0 : 1;
} else if (command[0] == "update") {
NSys::root::cacheSudo();
CScopeGuard x([] { NSys::root::dropSudo(); });
bool headersValid = g_pPluginManager->headersValid() == HEADERS_OK;
bool headers = g_pPluginManager->updateHeaders(force);
if (headers) {
const auto HLVER = g_pPluginManager->getHyprlandVersion(false);
auto GLOBALSTATE = DataState::getGlobalState();
@@ -122,7 +152,7 @@ int main(int argc, char** argv, char** envp) {
} else if (notify)
g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Couldn't update headers");
} else if (command[0] == "enable") {
if (ARGS.size() < 2) {
if (command.size() < 2) {
std::println(stderr, "{}", failureString("Not enough args for enable."));
return 1;
}
@@ -132,6 +162,9 @@ int main(int argc, char** argv, char** envp) {
return 1;
}
NSys::root::cacheSudo();
CScopeGuard x([] { NSys::root::dropSudo(); });
auto ret = g_pPluginManager->ensurePluginsLoadState();
if (ret == LOADSTATE_HYPRLAND_UPDATED)
@@ -150,13 +183,18 @@ int main(int argc, char** argv, char** envp) {
return 1;
}
NSys::root::cacheSudo();
CScopeGuard x([] { NSys::root::dropSudo(); });
auto ret = g_pPluginManager->ensurePluginsLoadState();
if (ret != LOADSTATE_OK)
return 1;
} else if (command[0] == "reload") {
auto ret = g_pPluginManager->ensurePluginsLoadState();
auto ret = g_pPluginManager->ensurePluginsLoadState(force);
if (ret != LOADSTATE_OK && notify) {
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;
@@ -165,9 +203,16 @@ int main(int argc, char** argv, char** envp) {
break;
default: break;
}
}
return 1;
} else if (notify && !notifyFail) {
g_pPluginManager->notify(ICON_OK, 0, 4000, "[hyprpm] Loaded plugins");
}
} else if (command[0] == "purge-cache") {
NSys::root::cacheSudo();
CScopeGuard x([] { NSys::root::dropSudo(); });
DataState::purgeAllCache();
} else if (command[0] == "list") {
g_pPluginManager->listAllPlugins();
} else {

@@ -8,6 +8,7 @@ executable(
dependency('hyprutils', version: '>= 0.1.1'),
dependency('threads'),
dependency('tomlplusplus'),
dependency('glaze', method: 'cmake'),
],
install: true,
)

@@ -1,82 +1,78 @@
#include "CProgressBar.hpp"
#include <sys/ioctl.h>
#include <algorithm>
#include <unistd.h>
#include <cmath>
#include <format>
#include <print>
#include <stdio.h>
#include <unistd.h>
#include <cstdio>
#include <algorithm>
#include <sstream>
#include "../helpers/Colors.hpp"
void CProgressBar::printMessageAbove(const std::string& msg) {
struct winsize w;
static winsize getTerminalSize() {
winsize w{};
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
return w;
}
std::string spaces;
spaces.reserve(w.ws_col);
for (size_t i = 0; i < w.ws_col; ++i) {
spaces += ' ';
}
static void clearCurrentLine() {
std::print("\r\33[2K"); // ansi escape sequence to clear entire line
}
std::println("\r{}\r{}", spaces, msg);
print();
void CProgressBar::printMessageAbove(const std::string& msg) {
clearCurrentLine();
std::print("\r{}\n", msg);
print(); // reprint bar underneath
}
void CProgressBar::print() {
struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
const auto w = getTerminalSize();
if (m_bFirstPrint)
if (m_bFirstPrint) {
std::print("\n");
m_bFirstPrint = false;
std::string spaces;
spaces.reserve(w.ws_col);
for (size_t i = 0; i < w.ws_col; ++i) {
spaces += ' ';
}
std::print("\r{}\r", spaces);
clearCurrentLine();
std::string message = "";
float percentDone = 0;
if (m_fPercentage >= 0)
float percentDone = 0.0f;
if (m_fPercentage >= 0.0f)
percentDone = m_fPercentage;
else
percentDone = (float)m_iSteps / (float)m_iMaxSteps;
const auto BARWIDTH = std::clamp(w.ws_col - static_cast<unsigned long>(m_szCurrentMessage.length()) - 2, 0UL, 50UL);
// draw bar
message += std::string{" "} + Colors::GREEN;
size_t i = 0;
for (; i < std::floor(percentDone * BARWIDTH); ++i) {
message += "";
else {
// check for divide-by-zero
percentDone = m_iMaxSteps > 0 ? static_cast<float>(m_iSteps) / m_iMaxSteps : 0.0f;
}
// clamp to ensure no overflows (sanity check)
percentDone = std::clamp(percentDone, 0.0f, 1.0f);
const size_t BARWIDTH = std::clamp<size_t>(w.ws_col - m_szCurrentMessage.length() - 2, 0, 50);
std::ostringstream oss;
oss << ' ' << Colors::GREEN;
size_t filled = static_cast<size_t>(std::floor(percentDone * BARWIDTH));
size_t i = 0;
for (; i < filled; ++i)
oss << "";
if (i < BARWIDTH) {
i++;
message += std::string{""} + Colors::RESET;
for (; i < BARWIDTH; ++i) {
message += "";
}
oss << "" << Colors::RESET;
++i;
for (; i < BARWIDTH; ++i)
oss << "";
} else
message += Colors::RESET;
oss << Colors::RESET;
// draw progress
if (m_fPercentage >= 0)
message += " " + std::format("{}%", static_cast<int>(percentDone * 100.0)) + " ";
if (m_fPercentage >= 0.0f)
oss << " " << std::format("{}%", static_cast<int>(percentDone * 100.0)) << ' ';
else
message += " " + std::format("{} / {}", m_iSteps, m_iMaxSteps) + " ";
// draw message
std::print("{} {}", message, m_szCurrentMessage);
oss << " " << std::format("{} / {}", m_iSteps, m_iMaxSteps) << ' ';
std::print("{} {}", oss.str(), m_szCurrentMessage);
std::fflush(stdout);
}

30
hyprtester/CMakeLists.txt Normal file

@@ -0,0 +1,30 @@
cmake_minimum_required(VERSION 3.19)
project(hyprtester DESCRIPTION "Hyprland test suite")
include(GNUInstallDirs)
set(CMAKE_CXX_STANDARD 26)
find_package(PkgConfig REQUIRED)
pkg_check_modules(hyprtester_deps REQUIRED IMPORTED_TARGET hyprutils>=0.5.0)
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
add_executable(hyprtester ${SRCFILES})
add_custom_command(
TARGET hyprtester
POST_BUILD
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/plugin/build.sh
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/plugin)
target_link_libraries(hyprtester PUBLIC PkgConfig::hyprtester_deps)
install(TARGETS hyprtester)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/test.conf
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/plugin/hyprtestplugin.so
DESTINATION ${CMAKE_INSTALL_PREFIX}/lib)

@@ -0,0 +1,16 @@
CXXFLAGS = -shared -fPIC --no-gnu-unique -g -std=c++2b -Wno-c++11-narrowing
INCLUDES = `pkg-config --cflags pixman-1 libdrm pangocairo libinput libudev wayland-server xkbcommon`
LIBS = `pkg-config --libs pangocairo`
SRC = src/main.cpp
TARGET = hyprtestplugin.so
all: $(TARGET)
$(TARGET): $(SRC)
$(CXX) $(CXXFLAGS) -I../.. -I../../protocols $(INCLUDES) $^ $> -o $@ $(LIBS) -O2
clean:
rm -f ./$(TARGET)
.PHONY: all clean

4
hyprtester/plugin/build.sh Executable file

@@ -0,0 +1,4 @@
#!/bin/sh
make clean
make all

@@ -0,0 +1,5 @@
#pragma once
#include <src/plugins/PluginAPI.hpp>
inline HANDLE PHANDLE = nullptr;

@@ -0,0 +1,62 @@
#include <unistd.h>
#include <src/includes.hpp>
#include <sstream>
#include <any>
#define private public
#include <src/config/ConfigManager.hpp>
#include <src/config/ConfigDescriptions.hpp>
#include <src/layout/IHyprLayout.hpp>
#include <src/managers/LayoutManager.hpp>
#include <src/Compositor.hpp>
#undef private
#include "globals.hpp"
// Do NOT change this function.
APICALL EXPORT std::string PLUGIN_API_VERSION() {
return HYPRLAND_API_VERSION;
}
static SDispatchResult test(std::string in) {
bool success = true;
std::string errors = "";
if (g_pConfigManager->m_configValueNumber != CONFIG_OPTIONS.size() + 1 /* autogenerated is special */) {
errors += "config value number mismatches descriptions size\n";
success = false;
}
return SDispatchResult{
.success = success,
.error = errors,
};
}
// Trigger a snap move event for the active window
static SDispatchResult snapMove(std::string in) {
const auto PLASTWINDOW = g_pCompositor->m_lastWindow.lock();
if (!PLASTWINDOW->m_isFloating)
return {.success = false, .error = "Window must be floating"};
Vector2D pos = PLASTWINDOW->m_realPosition->goal();
Vector2D size = PLASTWINDOW->m_realSize->goal();
g_pLayoutManager->getCurrentLayout()->performSnap(pos, size, PLASTWINDOW, MBIND_MOVE, -1, size);
*PLASTWINDOW->m_realPosition = pos.round();
return {};
}
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
PHANDLE = handle;
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:test", ::test);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:snapmove", ::snapMove);
return {"hyprtestplugin", "hyprtestplugin", "Vaxry", "1.0"};
}
APICALL EXPORT void PLUGIN_EXIT() {
;
}

17
hyprtester/src/Log.hpp Normal file

@@ -0,0 +1,17 @@
#pragma once
#include <string>
#include <format>
#include <print>
namespace NLog {
template <typename... Args>
//NOLINTNEXTLINE
void log(std::format_string<Args...> fmt, Args&&... args) {
std::string logMsg = "";
logMsg += std::vformat(fmt.get(), std::make_format_args(args...));
std::println("{}", logMsg);
std::fflush(stdout);
}
}

@@ -0,0 +1,136 @@
#include "hyprctlCompat.hpp"
#include "shared.hpp"
#include <pwd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <filesystem>
#include <fstream>
#include <algorithm>
#include <csignal>
#include <cerrno>
#include <print>
static int getUID() {
const auto UID = getuid();
const auto PWUID = getpwuid(UID);
return PWUID ? PWUID->pw_uid : UID;
}
static std::string getRuntimeDir() {
const auto XDG = getenv("XDG_RUNTIME_DIR");
if (!XDG) {
const std::string USERID = std::to_string(getUID());
return "/run/user/" + USERID + "/hypr";
}
return std::string{XDG} + "/hypr";
}
std::vector<SInstanceData> instances() {
std::vector<SInstanceData> result;
try {
if (!std::filesystem::exists(getRuntimeDir()))
return {};
} catch (std::exception& e) { return {}; }
for (const auto& el : std::filesystem::directory_iterator(getRuntimeDir())) {
if (!el.is_directory() || !std::filesystem::exists(el.path().string() + "/hyprland.lock"))
continue;
// read lock
SInstanceData* data = &result.emplace_back();
data->id = el.path().filename().string();
try {
data->time = std::stoull(data->id.substr(data->id.find_first_of('_') + 1, data->id.find_last_of('_') - (data->id.find_first_of('_') + 1)));
} catch (std::exception& e) { continue; }
// read file
std::ifstream ifs(el.path().string() + "/hyprland.lock");
int i = 0;
for (std::string line; std::getline(ifs, line); ++i) {
if (i == 0) {
try {
data->pid = std::stoull(line);
} catch (std::exception& e) { continue; }
} else if (i == 1) {
data->wlSocket = line;
} else
break;
}
ifs.close();
}
std::erase_if(result, [&](const auto& el) { return kill(el.pid, 0) != 0 && errno == ESRCH; });
std::sort(result.begin(), result.end(), [&](const auto& a, const auto& b) { return a.time < b.time; });
return result;
}
std::string getFromSocket(const std::string& cmd) {
const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
auto t = timeval{.tv_sec = 5, .tv_usec = 0};
setsockopt(SERVERSOCKET, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(struct timeval));
if (SERVERSOCKET < 0) {
std::println("socket: Couldn't open a socket (1)");
return "";
}
sockaddr_un serverAddress = {0};
serverAddress.sun_family = AF_UNIX;
std::string socketPath = getRuntimeDir() + "/" + HIS + "/.socket.sock";
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
if (connect(SERVERSOCKET, (sockaddr*)&serverAddress, SUN_LEN(&serverAddress)) < 0) {
std::println("Couldn't connect to {}. (3)", socketPath);
return "";
}
auto sizeWritten = write(SERVERSOCKET, cmd.c_str(), cmd.length());
if (sizeWritten < 0) {
std::println("Couldn't write (4)");
return "";
}
std::string reply = "";
char buffer[8192] = {0};
sizeWritten = read(SERVERSOCKET, buffer, 8192);
if (sizeWritten < 0) {
if (errno == EWOULDBLOCK)
std::println("Hyprland IPC didn't respond in time");
std::println("Couldn't read (5)");
return "";
}
reply += std::string(buffer, sizeWritten);
while (sizeWritten == 8192) {
sizeWritten = read(SERVERSOCKET, buffer, 8192);
if (sizeWritten < 0) {
std::println("Couldn't read (5)");
return "";
}
reply += std::string(buffer, sizeWritten);
}
close(SERVERSOCKET);
return reply;
}

@@ -0,0 +1,16 @@
#pragma once
#include <vector>
#include <string>
#include <cstdint>
struct SInstanceData {
std::string id;
uint64_t time;
uint64_t pid;
std::string wlSocket;
bool valid = true;
};
std::vector<SInstanceData> instances();
std::string getFromSocket(const std::string& cmd);

240
hyprtester/src/main.cpp Normal file

@@ -0,0 +1,240 @@
// This is a tester for Hyprland. It will launch the built binary in ./build/Hyprland
// in headless mode and test various things.
// for now it's quite basic and limited, but will be expanded in the future.
// NOTE: This tester has to be ran from its directory!!
// Some TODO:
// - Add a plugin built alongside so that we can do more detailed tests (e.g. simulating keystrokes)
// - test coverage
// - maybe figure out a way to do some visual tests too?
// Required runtime deps for checks:
// - kitty
// - xeyes
#include "shared.hpp"
#include "hyprctlCompat.hpp"
#include "tests/main/tests.hpp"
#include "tests/plugin/plugin.hpp"
#include <filesystem>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <csignal>
#include <cerrno>
#include <chrono>
#include <thread>
#include <print>
#include "Log.hpp"
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
#define SP CSharedPointer
static int ret = 0;
static SP<CProcess> hyprlandProc;
static const std::string cwd = std::filesystem::current_path().string();
//
static bool launchHyprland(std::string configPath, std::string binaryPath) {
if (binaryPath == "") {
std::error_code ec;
if (!std::filesystem::exists(cwd + "/../build/Hyprland", ec) || ec) {
NLog::log("{}No Hyprland binary", Colors::RED);
return false;
}
binaryPath = cwd + "/../build/Hyprland";
}
if (configPath == "") {
std::error_code ec;
if (!std::filesystem::exists(cwd + "/test.conf", ec) || ec) {
NLog::log("{}No test config", Colors::RED);
return false;
}
configPath = cwd + "/test.conf";
}
NLog::log("{}Launching Hyprland", Colors::YELLOW);
hyprlandProc = makeShared<CProcess>(binaryPath, std::vector<std::string>{"--config", configPath});
hyprlandProc->addEnv("HYPRLAND_HEADLESS_ONLY", "1");
NLog::log("{}Launched async process", Colors::YELLOW);
return hyprlandProc->runAsync();
}
static bool hyprlandAlive() {
NLog::log("{}hyprlandAlive", Colors::YELLOW);
kill(hyprlandProc->pid(), 0);
return errno != ESRCH;
}
static void help() {
NLog::log("usage: hyprtester [arg [...]].\n");
NLog::log(R"(Arguments:
--help -h - Show this message again
--config FILE -c FILE - Specify config file to use
--binary FILE -b FILE - Specify Hyprland binary to use
--plugin FILE -p FILE - Specify the location of the test plugin)");
}
int main(int argc, char** argv, char** envp) {
std::string configPath = "";
std::string binaryPath = "";
std::string pluginPath = std::filesystem::current_path().string();
std::vector<std::string> args{argv + 1, argv + argc};
for (auto it = args.begin(); it != args.end(); it++) {
if (*it == "--config" || *it == "-c") {
if (std::next(it) == args.end()) {
help();
return 1;
}
configPath = *std::next(it);
try {
configPath = std::filesystem::canonical(configPath);
if (!std::filesystem::is_regular_file(configPath)) {
throw std::exception();
}
} catch (...) {
std::println(stderr, "[ ERROR ] Config file '{}' doesn't exist!", configPath);
help();
return 1;
}
it++;
continue;
} else if (*it == "--binary" || *it == "-b") {
if (std::next(it) == args.end()) {
help();
return 1;
}
binaryPath = *std::next(it);
try {
binaryPath = std::filesystem::canonical(binaryPath);
if (!std::filesystem::is_regular_file(binaryPath)) {
throw std::exception();
}
} catch (...) {
std::println(stderr, "[ ERROR ] Binary '{}' doesn't exist!", binaryPath);
help();
return 1;
}
it++;
continue;
} else if (*it == "--plugin" || *it == "-p") {
if (std::next(it) == args.end()) {
help();
return 1;
}
pluginPath = *std::next(it);
try {
pluginPath = std::filesystem::canonical(pluginPath);
if (!std::filesystem::is_regular_file(pluginPath)) {
throw std::exception();
}
} catch (...) {
std::println(stderr, "[ ERROR ] plugin '{}' doesn't exist!", pluginPath);
help();
return 1;
}
it++;
continue;
} else if (*it == "--help" || *it == "-h") {
help();
return 0;
} else {
std::println(stderr, "[ ERROR ] Unknown option '{}' !", *it);
help();
return 1;
}
}
NLog::log("{}launching hl", Colors::YELLOW);
if (!launchHyprland(configPath, binaryPath)) {
NLog::log("{}well it failed", Colors::RED);
return 1;
}
// hyprland has launched, let's check if it's alive after 10s
std::this_thread::sleep_for(std::chrono::milliseconds(10000));
NLog::log("{}slept for 10s", Colors::YELLOW);
if (!hyprlandAlive()) {
NLog::log("{}Hyprland failed to launch", Colors::RED);
return 1;
}
// wonderful, we are in. Let's get the instance signature.
NLog::log("{}trying to get INSTANCES", Colors::YELLOW);
const auto INSTANCES = instances();
if (INSTANCES.empty()) {
NLog::log("{}Hyprland failed to launch (2)", Colors::RED);
return 1;
}
HIS = INSTANCES.back().id;
WLDISPLAY = INSTANCES.back().wlSocket;
NLog::log("{}trying to get create headless output", Colors::YELLOW);
getFromSocket("/output create headless");
NLog::log("{}trying to load plugin", Colors::YELLOW);
if (const auto R = getFromSocket(std::format("/plugin load {}", pluginPath)); R != "ok") {
NLog::log("{}Failed to load the test plugin: {}", Colors::RED, R);
getFromSocket("/dispatch exit 1");
return 1;
}
NLog::log("{}Loaded plugin", Colors::YELLOW);
for (const auto& fn : testFns) {
EXPECT(fn(), true);
}
NLog::log("{}running plugin test", Colors::YELLOW);
EXPECT(testPlugin(), true);
// kill hyprland
NLog::log("{}dispatching exit", Colors::YELLOW);
getFromSocket("/dispatch exit");
NLog::log("\n{}Summary:\n\tPASSED: {}{}{}/{}\n\tFAILED: {}{}{}/{}\n{}", Colors::RESET, Colors::GREEN, TESTS_PASSED, Colors::RESET, TESTS_PASSED + TESTS_FAILED, Colors::RED,
TESTS_FAILED, Colors::RESET, TESTS_PASSED + TESTS_FAILED, (TESTS_FAILED > 0 ? std::string{Colors::RED} + "\nSome tests failed.\n" : ""));
kill(hyprlandProc->pid(), SIGKILL);
hyprlandProc.reset();
return ret || TESTS_FAILED;
}

90
hyprtester/src/shared.hpp Normal file

@@ -0,0 +1,90 @@
// Stolen from hyprutils
#pragma once
#include <iostream>
inline std::string HIS = "";
inline std::string WLDISPLAY = "";
inline int TESTS_PASSED = 0;
inline int TESTS_FAILED = 0;
namespace Colors {
constexpr const char* RED = "\x1b[31m";
constexpr const char* GREEN = "\x1b[32m";
constexpr const char* YELLOW = "\x1b[33m";
constexpr const char* BLUE = "\x1b[34m";
constexpr const char* MAGENTA = "\x1b[35m";
constexpr const char* CYAN = "\x1b[36m";
constexpr const char* RESET = "\x1b[0m";
};
#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__); \
ret = 1; \
TESTS_FAILED++; \
} else { \
NLog::log("{}Passed: {}{}. Got {}", Colors::GREEN, Colors::RESET, #expr, val); \
TESTS_PASSED++; \
}
#define EXPECT_VECTOR2D(expr, val) \
do { \
const auto& RESULT = expr; \
const auto& EXPECTED = val; \
if (!(std::abs(RESULT.x - EXPECTED.x) < 1e-6 && std::abs(RESULT.y - EXPECTED.y) < 1e-6)) { \
NLog::log("{}Failed: {}{}, expected [{}, {}], got [{}, {}]. Source: {}@{}.", Colors::RED, Colors::RESET, #expr, EXPECTED.x, EXPECTED.y, RESULT.x, RESULT.y, __FILE__, \
__LINE__); \
ret = 1; \
TESTS_FAILED++; \
} else { \
NLog::log("{}Passed: {}{}. Got [{}, {}].", Colors::GREEN, Colors::RESET, #expr, RESULT.x, RESULT.y); \
TESTS_PASSED++; \
} \
} while (0)
#define EXPECT_CONTAINS(haystack, needle) \
if (const auto EXPECTED = needle; !std::string{haystack}.contains(EXPECTED)) { \
NLog::log("{}Failed: {}{} should contain {} but doesn't. Source: {}@{}. Haystack is:\n{}", Colors::RED, Colors::RESET, #haystack, EXPECTED, __FILE__, __LINE__, \
std::string{haystack}); \
ret = 1; \
TESTS_FAILED++; \
} else { \
NLog::log("{}Passed: {}{} contains {}.", Colors::GREEN, Colors::RESET, #haystack, EXPECTED); \
TESTS_PASSED++; \
}
#define EXPECT_NOT_CONTAINS(haystack, needle) \
if (std::string{haystack}.contains(needle)) { \
NLog::log("{}Failed: {}{} shouldn't contain {} but does. Source: {}@{}. Haystack is:\n{}", Colors::RED, Colors::RESET, #haystack, #needle, __FILE__, __LINE__, \
std::string{haystack}); \
ret = 1; \
TESTS_FAILED++; \
} else { \
NLog::log("{}Passed: {}{} doesn't contain {}.", Colors::GREEN, Colors::RESET, #haystack, #needle); \
TESTS_PASSED++; \
}
#define EXPECT_STARTS_WITH(str, what) \
if (!std::string{str}.starts_with(what)) { \
NLog::log("{}Failed: {}{} should start with {} but doesn't. Source: {}@{}. String is:\n{}", Colors::RED, Colors::RESET, #str, #what, __FILE__, __LINE__, \
std::string{str}); \
ret = 1; \
TESTS_FAILED++; \
} else { \
NLog::log("{}Passed: {}{} starts with {}.", Colors::GREEN, Colors::RESET, #str, #what); \
TESTS_PASSED++; \
}
#define EXPECT_COUNT_STRING(str, what, no) \
if (Tests::countOccurrences(str, what) != no) { \
NLog::log("{}Failed: {}{} should contain {} {} times, but doesn't. Source: {}@{}. String is:\n{}", Colors::RED, Colors::RESET, #str, #what, no, __FILE__, __LINE__, \
std::string{str}); \
ret = 1; \
TESTS_FAILED++; \
} else { \
NLog::log("{}Passed: {}{} contains {} {} times.", Colors::GREEN, Colors::RESET, #str, #what, no); \
TESTS_PASSED++; \
}
#define OK(x) EXPECT(x, "ok")

@@ -0,0 +1,22 @@
#include "../../Log.hpp"
#include "tests.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
static int ret = 0;
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
static bool test() {
NLog::log("{}Testing animations", Colors::GREEN);
auto str = getFromSocket("/animations");
NLog::log("{}Testing bezier curve output from `hyprctl animations`", Colors::YELLOW);
{EXPECT_CONTAINS(str, std::format("beziers:\n\n\tname: quick\n\t\tX0: 0.15\n\t\tY0: 0.00\n\t\tX1: 0.10\n\t\tY1: 1.00"))};
return !ret;
}
REGISTER_TEST_FN(test)

@@ -0,0 +1,179 @@
#include "tests.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <print>
#include <thread>
#include <chrono>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <csignal>
#include <cerrno>
#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 groups", Colors::GREEN);
// test on workspace "window"
NLog::log("{}Dispatching workspace `groups`", Colors::YELLOW);
getFromSocket("/dispatch workspace name:groups");
NLog::log("{}Spawning kittyProcA", Colors::YELLOW);
auto kittyProcA = Tests::spawnKitty();
if (!kittyProcA) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
NLog::log("{}Expecting 1 window", Colors::YELLOW);
EXPECT(Tests::windowCount(), 1);
// check kitty properties. One kitty should take the entire screen, minus the gaps.
NLog::log("{}Check kitty dimensions", Colors::YELLOW);
{
auto str = getFromSocket("/clients");
EXPECT_COUNT_STRING(str, "at: 22,22", 1);
EXPECT_COUNT_STRING(str, "size: 1876,1036", 1);
EXPECT_COUNT_STRING(str, "fullscreen: 0", 1);
}
// group the kitty
NLog::log("{}Enable group and groupbar", Colors::YELLOW);
OK(getFromSocket("/dispatch togglegroup"));
OK(getFromSocket("/keyword group:groupbar:enabled 1"));
// check the height of the window now
NLog::log("{}Recheck kitty dimensions", Colors::YELLOW);
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 22,43");
EXPECT_CONTAINS(str, "size: 1876,1015");
}
// disable the groupbar for ease of testing for now
NLog::log("{}Disable groupbar", Colors::YELLOW);
OK(getFromSocket("r/keyword group:groupbar:enabled 0"));
// kill all
NLog::log("{}Kill windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Spawn kitty again", Colors::YELLOW);
kittyProcA = Tests::spawnKitty();
if (!kittyProcA) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
NLog::log("{}Group kitty", Colors::YELLOW);
OK(getFromSocket("/dispatch togglegroup"));
// check the height of the window now
NLog::log("{}Check kitty dimensions 2", Colors::YELLOW);
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 22,22");
EXPECT_CONTAINS(str, "size: 1876,1036");
}
NLog::log("{}Spawn kittyProcB", Colors::YELLOW);
auto kittyProcB = Tests::spawnKitty();
if (!kittyProcB) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
NLog::log("{}Expecting 2 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 2);
size_t lastActiveKittyIdx = 0;
NLog::log("{}Get last active kitty id", Colors::YELLOW);
try {
auto str = getFromSocket("/activewindow");
lastActiveKittyIdx = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
} catch (...) {
NLog::log("{}Fail at getting prop", Colors::RED);
ret = 1;
}
// test cycling through
NLog::log("{}Test cycling through grouped windows", Colors::YELLOW);
OK(getFromSocket("/dispatch changegroupactive f"));
try {
auto str = getFromSocket("/activewindow");
EXPECT(lastActiveKittyIdx != std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16), true);
} catch (...) {
NLog::log("{}Fail at getting prop", Colors::RED);
ret = 1;
}
getFromSocket("/dispatch changegroupactive f");
try {
auto str = getFromSocket("/activewindow");
EXPECT(lastActiveKittyIdx, std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16));
} catch (...) {
NLog::log("{}Fail at getting prop", Colors::RED);
ret = 1;
}
NLog::log("{}Disable autogrouping", Colors::YELLOW);
OK(getFromSocket("/keyword group:auto_group false"));
NLog::log("{}Spawn kittyProcC", Colors::YELLOW);
auto kittyProcC = Tests::spawnKitty();
if (!kittyProcC) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
NLog::log("{}Expecting 3 windows 2", Colors::YELLOW);
EXPECT(Tests::windowCount(), 3);
{
auto str = getFromSocket("/clients");
EXPECT_COUNT_STRING(str, "at: 22,22", 2);
}
OK(getFromSocket("/dispatch movefocus l"));
OK(getFromSocket("/dispatch changegroupactive 1"));
OK(getFromSocket("/keyword group:auto_group true"));
OK(getFromSocket("/keyword group:insert_after_current false"));
NLog::log("{}Spawn kittyProcD", Colors::YELLOW);
auto kittyProcD = Tests::spawnKitty();
if (!kittyProcD) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
NLog::log("{}Expecting 4 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 4);
OK(getFromSocket("/dispatch changegroupactive 3"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, std::format("pid: {}", kittyProcD->pid()));
}
// kill all
NLog::log("{}Kill windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);
return !ret;
}
REGISTER_TEST_FN(test)

@@ -0,0 +1,144 @@
#include "tests.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <print>
#include <thread>
#include <chrono>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <csignal>
#include <cerrno>
#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 config: misc:", Colors::GREEN);
NLog::log("{}Testing close_special_on_empty", Colors::YELLOW);
OK(getFromSocket("/keyword misc:close_special_on_empty false"));
OK(getFromSocket("/dispatch workspace special:test"));
Tests::spawnKitty();
{
auto str = getFromSocket("/monitors");
EXPECT_CONTAINS(str, "special workspace: -");
}
Tests::killAllWindows();
{
auto str = getFromSocket("/monitors");
EXPECT_CONTAINS(str, "special workspace: -");
}
Tests::spawnKitty();
OK(getFromSocket("/keyword misc:close_special_on_empty true"));
Tests::killAllWindows();
{
auto str = getFromSocket("/monitors");
EXPECT_NOT_CONTAINS(str, "special workspace: -");
}
NLog::log("{}Testing new_window_takes_over_fullscreen", Colors::YELLOW);
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 0"));
Tests::spawnKitty("kitty_A");
OK(getFromSocket("/dispatch fullscreen 0"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 2");
EXPECT_CONTAINS(str, "kitty_A");
}
Tests::spawnKitty("kitty_B");
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 2");
EXPECT_CONTAINS(str, "kitty_A");
}
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 1"));
Tests::spawnKitty("kitty_C");
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 2");
EXPECT_CONTAINS(str, "kitty_C");
}
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 2"));
Tests::spawnKitty("kitty_D");
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 0");
EXPECT_CONTAINS(str, "kitty_D");
}
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 0"));
Tests::killAllWindows();
NLog::log("{}Testing exit_window_retains_fullscreen", Colors::YELLOW);
OK(getFromSocket("/keyword misc:exit_window_retains_fullscreen false"));
Tests::spawnKitty("kitty_A");
Tests::spawnKitty("kitty_B");
OK(getFromSocket("/dispatch fullscreen 0"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 2");
}
OK(getFromSocket("/dispatch killwindow activewindow"));
Tests::waitUntilWindowsN(1);
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 0");
}
Tests::spawnKitty("kitty_B");
OK(getFromSocket("/dispatch fullscreen 0"));
OK(getFromSocket("/keyword misc:exit_window_retains_fullscreen true"));
OK(getFromSocket("/dispatch killwindow activewindow"));
Tests::waitUntilWindowsN(1);
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 2");
}
// 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);

@@ -0,0 +1,175 @@
#include <hyprutils/math/Vector2D.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/os/Process.hpp>
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include "../shared.hpp"
#include "tests.hpp"
using Hyprutils::Math::Vector2D;
static int ret = 0;
static bool spawnFloatingKitty() {
if (!Tests::spawnKitty()) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
OK(getFromSocket("/dispatch setfloating active"));
OK(getFromSocket("/dispatch resizeactive exact 100 100"));
return true;
}
static void expectSocket(const std::string& CMD) {
if (const auto RESULT = getFromSocket(CMD); RESULT != "ok") {
NLog::log("{}Failed: {}getFromSocket({}), expected ok, got {}. Source: {}@{}.", Colors::RED, Colors::RESET, CMD, RESULT, __FILE__, __LINE__);
ret = 1;
TESTS_FAILED++;
} else {
NLog::log("{}Passed: {}getFromSocket({}). Got ok", Colors::GREEN, Colors::RESET, CMD);
TESTS_PASSED++;
}
}
static void expectSnapMove(const Vector2D FROM, const Vector2D* TO) {
const Vector2D& A = FROM;
const Vector2D& B = TO ? *TO : FROM;
if (TO)
NLog::log("{}Expecting snap to ({},{}) when window is moved to ({},{})", Colors::YELLOW, B.x, B.y, A.x, A.y);
else
NLog::log("{}Expecting no snap when window is moved to ({},{})", Colors::YELLOW, A.x, A.y);
expectSocket(std::format("/dispatch moveactive exact {} {}", A.x, A.y));
expectSocket("/dispatch plugin:test:snapmove");
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("at: {},{}", B.x, B.y));
}
static void testWindowSnap(const bool RESPECTGAPS) {
const double BORDERSIZE = 2;
const double WINDOWSIZE = 100;
const double OTHER = 500;
const double WINDOWGAP = 8;
const double GAPSIN = 5;
const double GAP = (RESPECTGAPS ? 2 * GAPSIN : 0) + (2 * BORDERSIZE);
const double END = GAP + WINDOWSIZE;
double x;
Vector2D predict;
x = WINDOWGAP + END;
expectSnapMove({OTHER + x, OTHER}, nullptr);
expectSnapMove({OTHER - x, OTHER}, nullptr);
expectSnapMove({OTHER, OTHER + x}, nullptr);
expectSnapMove({OTHER, OTHER - x}, nullptr);
x -= 1;
expectSnapMove({OTHER + x, OTHER}, &(predict = {OTHER + END, OTHER}));
expectSnapMove({OTHER - x, OTHER}, &(predict = {OTHER - END, OTHER}));
expectSnapMove({OTHER, OTHER + x}, &(predict = {OTHER, OTHER + END}));
expectSnapMove({OTHER, OTHER - x}, &(predict = {OTHER, OTHER - END}));
}
static void testMonitorSnap(const bool RESPECTGAPS, const bool OVERLAP) {
const double BORDERSIZE = 2;
const double WINDOWSIZE = 100;
const double MONITORGAP = 10;
const double GAPSOUT = 20;
const double RESP = (RESPECTGAPS ? GAPSOUT : 0);
const double GAP = RESP + (OVERLAP ? 0 : BORDERSIZE);
const double END = GAP + WINDOWSIZE;
double x;
Vector2D predict;
x = MONITORGAP + GAP;
expectSnapMove({x, x}, nullptr);
x -= 1;
expectSnapMove({x, x}, &(predict = {GAP, GAP}));
x = MONITORGAP + END;
expectSnapMove({1920 - x, 1080 - x}, nullptr);
x -= 1;
expectSnapMove({1920 - x, 1080 - x}, &(predict = {1920 - END, 1080 - END}));
// test reserved area
const double RESERVED = 200;
const double RGAP = RESERVED + RESP + BORDERSIZE;
const double REND = RGAP + WINDOWSIZE;
x = MONITORGAP + RGAP;
expectSnapMove({x, x}, nullptr);
x -= 1;
expectSnapMove({x, x}, &(predict = {RGAP, RGAP}));
x = MONITORGAP + REND;
expectSnapMove({1920 - x, 1080 - x}, nullptr);
x -= 1;
expectSnapMove({1920 - x, 1080 - x}, &(predict = {1920 - REND, 1080 - REND}));
}
static bool test() {
NLog::log("{}Testing snap", Colors::GREEN);
// move to monitor HEADLESS-2
NLog::log("{}Moving to monitor HEADLESS-2", Colors::YELLOW);
OK(getFromSocket("/dispatch focusmonitor HEADLESS-2"));
NLog::log("{}Adding reserved monitor area to HEADLESS-2", Colors::YELLOW);
OK(getFromSocket("/keyword monitor HEADLESS-2,addreserved,200,200,200,200"));
// test on workspace "snap"
NLog::log("{}Dispatching workspace `snap`", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace name:snap"));
// spawn a kitty terminal and move to (500,500)
NLog::log("{}Spawning kittyProcA", Colors::YELLOW);
if (!spawnFloatingKitty())
return false;
NLog::log("{}Expecting 1 window", Colors::YELLOW);
EXPECT(Tests::windowCount(), 1);
NLog::log("{}Move the kitty window to (500,500)", Colors::YELLOW);
OK(getFromSocket("/dispatch moveactive exact 500 500"));
// spawn a second kitty terminal
NLog::log("{}Spawning kittyProcB", Colors::YELLOW);
if (!spawnFloatingKitty())
return false;
NLog::log("{}Expecting 2 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 2);
NLog::log("");
testWindowSnap(false);
testMonitorSnap(false, false);
NLog::log("\n{}Turning on respect_gaps", Colors::YELLOW);
OK(getFromSocket("/keyword general:snap:respect_gaps true"));
testWindowSnap(true);
testMonitorSnap(true, false);
NLog::log("\n{}Turning on border_overlap", Colors::YELLOW);
OK(getFromSocket("/keyword general:snap:respect_gaps false"));
OK(getFromSocket("/keyword general:snap:border_overlap true"));
testMonitorSnap(false, true);
NLog::log("\n{}Turning on both border_overlap and respect_gaps", Colors::YELLOW);
OK(getFromSocket("/keyword general:snap:respect_gaps true"));
testMonitorSnap(true, true);
// kill all
NLog::log("\n{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);
NLog::log("{}Reloading the config", Colors::YELLOW);
OK(getFromSocket("/reload"));
return !ret;
}
REGISTER_TEST_FN(test)

@@ -0,0 +1,12 @@
#pragma once
#include <vector>
#include <functional>
inline std::vector<std::function<bool()>> testFns;
#define REGISTER_TEST_FN(fn) \
static auto _register_fn = [] { \
testFns.emplace_back(fn); \
return 1; \
}();

@@ -0,0 +1,98 @@
#include "tests.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <print>
#include <thread>
#include <chrono>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <csignal>
#include <cerrno>
#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 windows", Colors::GREEN);
// test on workspace "window"
NLog::log("{}Switching to workspace `window`", Colors::YELLOW);
getFromSocket("/dispatch workspace name:window");
NLog::log("{}Spawning kittyProcA", Colors::YELLOW);
auto kittyProcA = Tests::spawnKitty();
if (!kittyProcA) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
NLog::log("{}Expecting 1 window", Colors::YELLOW);
EXPECT(Tests::windowCount(), 1);
// check kitty properties. One kitty should take the entire screen, as this is smart gaps
NLog::log("{}Expecting kitty to take up the whole screen", Colors::YELLOW);
{
auto str = getFromSocket("/clients");
EXPECT(str.contains("at: 0,0"), true);
EXPECT(str.contains("size: 1920,1080"), true);
EXPECT(str.contains("fullscreen: 0"), true);
}
NLog::log("{}Spawning kittyProcB", Colors::YELLOW);
auto kittyProcB = Tests::spawnKitty();
if (!kittyProcB) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
NLog::log("{}Expecting 2 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 2);
// open xeyes
NLog::log("{}Spawning xeyes", Colors::YELLOW);
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;
}
}
NLog::log("{}Expecting 3 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 3);
NLog::log("{}Checking props of xeyes", Colors::YELLOW);
// check some window props of xeyes, try to tile them
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "floating: 1");
getFromSocket("/dispatch settiled class:XEyes");
std::this_thread::sleep_for(std::chrono::milliseconds(200));
str = getFromSocket("/clients");
EXPECT_NOT_CONTAINS(str, "floating: 1");
}
// 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)

@@ -0,0 +1,358 @@
#include "tests.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <print>
#include <thread>
#include <chrono>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <csignal>
#include <cerrno>
#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 workspaces", Colors::GREEN);
EXPECT(Tests::windowCount(), 0);
// test on workspace "window"
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
NLog::log("{}Spawning kittyProc on ws 1", Colors::YELLOW);
auto kittyProcA = Tests::spawnKitty();
if (!kittyProcA) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
NLog::log("{}Switching to workspace 3", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 3"));
NLog::log("{}Spawning kittyProc on ws 3", Colors::YELLOW);
auto kittyProcB = Tests::spawnKitty();
if (!kittyProcB) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
NLog::log("{}Switching to workspace +1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace +1"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 2 (2)");
}
// check if the other workspaces are alive
{
auto str = getFromSocket("/workspaces");
EXPECT_CONTAINS(str, "workspace ID 3 (3)");
EXPECT_CONTAINS(str, "workspace ID 1 (1)");
}
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
{
auto str = getFromSocket("/workspaces");
EXPECT_NOT_CONTAINS(str, "workspace ID 2 (2)");
}
NLog::log("{}Switching to workspace m+1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace m+1"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
}
NLog::log("{}Switching to workspace -1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace -1"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 2 (2)");
}
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
NLog::log("{}Switching to workspace r+1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace r+1"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 2 (2)");
}
NLog::log("{}Switching to workspace r+1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace r+1"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
}
NLog::log("{}Switching to workspace r~1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace r~1"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 1 (1)");
}
NLog::log("{}Switching to workspace empty", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace empty"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 2 (2)");
}
NLog::log("{}Switching to workspace previous", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace previous"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 1 (1)");
}
NLog::log("{}Switching to workspace name:TEST_WORKSPACE_NULL", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace name:TEST_WORKSPACE_NULL"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID -1337 (TEST_WORKSPACE_NULL)");
}
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
// add a new monitor
NLog::log("{}Adding a new monitor", Colors::YELLOW);
EXPECT(getFromSocket("/output create headless"), "ok")
// should take workspace 2
{
auto str = getFromSocket("/monitors");
EXPECT_CONTAINS(str, "active workspace: 2 (2)");
EXPECT_CONTAINS(str, "active workspace: 1 (1)");
EXPECT_CONTAINS(str, "HEADLESS-3");
}
// focus the first monitor
OK(getFromSocket("/dispatch focusmonitor 0"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 1 (1)");
}
NLog::log("{}Switching to workspace r+1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace r+1"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
}
NLog::log("{}Switching to workspace r~2", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch workspace r~2"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
}
NLog::log("{}Switching to workspace m+1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace m+1"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 1 (1)");
}
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
// no OK: this will throw an error as it should
getFromSocket("/dispatch workspace 1");
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 1 (1)");
}
NLog::log("{}Testing back_and_forth", Colors::YELLOW);
OK(getFromSocket("/keyword binds:workspace_back_and_forth true"));
OK(getFromSocket("/dispatch workspace 1"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
}
OK(getFromSocket("/keyword binds:workspace_back_and_forth false"));
NLog::log("{}Testing hide_special_on_workspace_change", Colors::YELLOW);
OK(getFromSocket("/keyword binds:hide_special_on_workspace_change true"));
OK(getFromSocket("/dispatch workspace special:HELLO"));
{
auto str = getFromSocket("/monitors");
EXPECT_CONTAINS(str, "special workspace: -");
EXPECT_CONTAINS(str, "special:HELLO");
}
// no OK: will err (it shouldnt prolly but oh well)
getFromSocket("/dispatch workspace 3");
{
auto str = getFromSocket("/monitors");
EXPECT_COUNT_STRING(str, "special workspace: 0 ()", 2);
}
OK(getFromSocket("/keyword binds:hide_special_on_workspace_change false"));
NLog::log("{}Testing allow_workspace_cycles", Colors::YELLOW);
OK(getFromSocket("/keyword binds:allow_workspace_cycles true"));
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch workspace 3"));
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch workspace previous"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
}
OK(getFromSocket("/dispatch workspace previous"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 1 (1)");
}
OK(getFromSocket("/dispatch workspace previous"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_STARTS_WITH(str, "workspace ID 3 (3)");
}
OK(getFromSocket("/keyword binds:allow_workspace_cycles false"));
OK(getFromSocket("/dispatch workspace 1"));
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
// spawn 3 kitties
NLog::log("{}Testing focus_preferred_method", Colors::YELLOW);
OK(getFromSocket("/keyword dwindle:force_split 2"));
Tests::spawnKitty("kitty_A");
Tests::spawnKitty("kitty_B");
Tests::spawnKitty("kitty_C");
OK(getFromSocket("/keyword dwindle:force_split 0"));
// focus kitty 2: will be top right (dwindle)
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
// resize it to be a bit taller
OK(getFromSocket("/dispatch resizeactive +20 +20"));
// now we test focus methods.
OK(getFromSocket("/keyword binds:focus_preferred_method 0"));
OK(getFromSocket("/dispatch focuswindow class:kitty_C"));
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch movefocus r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_C");
}
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/keyword binds:focus_preferred_method 1"));
OK(getFromSocket("/dispatch movefocus r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_B");
}
NLog::log("{}Testing movefocus_cycles_fullscreen", Colors::YELLOW);
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
OK(getFromSocket("/dispatch focusmonitor HEADLESS-3"));
Tests::spawnKitty("kitty_D");
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_D");
}
OK(getFromSocket("/dispatch focusmonitor l"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_A");
}
OK(getFromSocket("/keyword binds:movefocus_cycles_fullscreen false"));
OK(getFromSocket("/dispatch fullscreen 0"));
OK(getFromSocket("/dispatch movefocus r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_D");
}
OK(getFromSocket("/dispatch focusmonitor l"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_A");
}
OK(getFromSocket("/keyword binds:movefocus_cycles_fullscreen true"));
OK(getFromSocket("/dispatch movefocus r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_B");
}
// destroy the headless output
OK(getFromSocket("/output remove HEADLESS-3"));
// 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)

@@ -0,0 +1,21 @@
#include "plugin.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <print>
#include <thread>
#include <chrono>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <csignal>
#include <cerrno>
#include "../shared.hpp"
bool testPlugin() {
const auto RESPONSE = getFromSocket("/dispatch plugin:test:test");
if (RESPONSE != "ok") {
NLog::log("{}Plugin tests failed, plugin returned:\n{}{}", Colors::RED, Colors::RESET, RESPONSE);
return false;
}
return true;
}

@@ -0,0 +1,3 @@
#pragma once
bool testPlugin();

@@ -0,0 +1,92 @@
#include "shared.hpp"
#include <csignal>
#include <cerrno>
#include <thread>
#include <print>
#include "../shared.hpp"
#include "../hyprctlCompat.hpp"
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
CUniquePointer<CProcess> Tests::spawnKitty(const std::string& class_) {
const auto COUNT_BEFORE = windowCount();
CUniquePointer<CProcess> kitty = makeUnique<CProcess>("kitty", class_.empty() ? std::vector<std::string>{} : std::vector<std::string>{"--class", class_});
kitty->addEnv("WAYLAND_DISPLAY", WLDISPLAY);
kitty->runAsync();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
// wait while kitty spawns
int counter = 0;
while (processAlive(kitty->pid()) && windowCount() == COUNT_BEFORE) {
counter++;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (counter > 50)
return nullptr;
}
if (!processAlive(kitty->pid()))
return nullptr;
return kitty;
}
bool Tests::processAlive(pid_t pid) {
errno = 0;
int ret = kill(pid, 0);
return ret != -1 || errno != ESRCH;
}
int Tests::windowCount() {
return countOccurrences(getFromSocket("/clients"), "focusHistoryID: ");
}
int Tests::countOccurrences(const std::string& in, const std::string& what) {
int cnt = 0;
auto pos = in.find(what);
while (pos != std::string::npos) {
cnt++;
pos = in.find(what, pos + what.length() - 1);
}
return cnt;
}
bool Tests::killAllWindows() {
auto str = getFromSocket("/clients");
auto pos = str.find("Window ");
while (pos != std::string::npos) {
auto pos2 = str.find(" -> ", pos);
getFromSocket("/dispatch killwindow address:0x" + str.substr(pos + 7, pos2 - pos - 7));
pos = str.find("Window ", pos + 5);
}
int counter = 0;
while (Tests::windowCount() != 0) {
counter++;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (counter > 50) {
std::println("{}Timed out waiting for windows to close", Colors::RED);
return false;
}
}
return true;
}
void Tests::waitUntilWindowsN(int n) {
int counter = 0;
while (Tests::windowCount() != n) {
counter++;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (counter > 50) {
std::println("{}Timed out waiting for windows", Colors::RED);
return;
}
}
}

@@ -0,0 +1,17 @@
#pragma once
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <sys/types.h>
#include "../Log.hpp"
//NOLINTNEXTLINE
namespace Tests {
Hyprutils::Memory::CUniquePointer<Hyprutils::OS::CProcess> spawnKitty(const std::string& class_ = "");
bool processAlive(pid_t pid);
int windowCount();
int countOccurrences(const std::string& in, const std::string& what);
bool killAllWindows();
void waitUntilWindowsN(int n);
};

313
hyprtester/test.conf Normal file

@@ -0,0 +1,313 @@
# This is an example Hyprland config file.
# Refer to the wiki for more information.
# https://wiki.hyprland.org/Configuring/
# Please note not all available settings / options are set here.
# For a full list, see the wiki
# You can split this configuration into multiple files
# Create your files separately and then link them to this file like this:
# source = ~/.config/hypr/myColors.conf
################
### MONITORS ###
################
# See https://wiki.hyprland.org/Configuring/Monitors/
monitor=HEADLESS-1,1920x1080@60,auto-right,1
monitor=HEADLESS-2,1920x1080@60,auto-right,1
monitor=HEADLESS-3,1920x1080@60,auto-right,1
monitor=HEADLESS-4,1920x1080@60,auto-right,1
monitor=HEADLESS-5,1920x1080@60,auto-right,1
monitor=HEADLESS-6,1920x1080@60,auto-right,1
monitor=,disabled
###################
### MY PROGRAMS ###
###################
# See https://wiki.hyprland.org/Configuring/Keywords/
# Set programs that you use
$terminal = kitty
$fileManager = dolphin
$menu = wofi --show drun
#################
### AUTOSTART ###
#################
# Autostart necessary processes (like notifications daemons, status bars, etc.)
# Or execute your favorite apps at launch like this:
# exec-once = $terminal
# exec-once = nm-applet &
# exec-once = waybar & hyprpaper & firefox
#############################
### ENVIRONMENT VARIABLES ###
#############################
# See https://wiki.hyprland.org/Configuring/Environment-variables/
env = XCURSOR_SIZE,24
env = HYPRCURSOR_SIZE,24
#####################
### LOOK AND FEEL ###
#####################
# Refer to https://wiki.hyprland.org/Configuring/Variables/
# https://wiki.hyprland.org/Configuring/Variables/#general
general {
gaps_in = 5
gaps_out = 20
border_size = 2
snap {
enabled = true
window_gap = 8
monitor_gap = 10
respect_gaps = false
border_overlap = false
}
# https://wiki.hyprland.org/Configuring/Variables/#variable-types for info about colors
col.active_border = rgba(33ccffee) rgba(00ff99ee) 45deg
col.inactive_border = rgba(595959aa)
# Set to true enable resizing windows by clicking and dragging on borders and gaps
resize_on_border = false
# Please see https://wiki.hyprland.org/Configuring/Tearing/ before you turn this on
allow_tearing = false
layout = dwindle
}
# https://wiki.hyprland.org/Configuring/Variables/#decoration
decoration {
rounding = 10
rounding_power = 2
# Change transparency of focused and unfocused windows
active_opacity = 1.0
inactive_opacity = 1.0
shadow {
enabled = true
range = 4
render_power = 3
color = rgba(1a1a1aee)
}
# https://wiki.hyprland.org/Configuring/Variables/#blur
blur {
enabled = true
size = 3
passes = 1
vibrancy = 0.1696
}
}
# https://wiki.hyprland.org/Configuring/Variables/#animations
animations {
enabled = 0
# Default animations, see https://wiki.hyprland.org/Configuring/Animations/ for more
bezier = easeOutQuint,0.23,1,0.32,1
bezier = easeInOutCubic,0.65,0.05,0.36,1
bezier = linear,0,0,1,1
bezier = almostLinear,0.5,0.5,0.75,1.0
bezier = quick,0.15,0,0.1,1
animation = global, 1, 10, default
animation = border, 1, 5.39, easeOutQuint
animation = windows, 1, 4.79, easeOutQuint
animation = windowsIn, 1, 4.1, easeOutQuint, popin 87%
animation = windowsOut, 1, 1.49, linear, popin 87%
animation = fadeIn, 1, 1.73, almostLinear
animation = fadeOut, 1, 1.46, almostLinear
animation = fade, 1, 3.03, quick
animation = layers, 1, 3.81, easeOutQuint
animation = layersIn, 1, 4, easeOutQuint, fade
animation = layersOut, 1, 1.5, linear, fade
animation = fadeLayersIn, 1, 1.79, almostLinear
animation = fadeLayersOut, 1, 1.39, almostLinear
animation = workspaces, 1, 1.94, almostLinear, fade
animation = workspacesIn, 1, 1.21, almostLinear, fade
animation = workspacesOut, 1, 1.94, almostLinear, fade
}
# Ref https://wiki.hyprland.org/Configuring/Workspace-Rules/
# "Smart gaps" / "No gaps when only"
# uncomment all if you wish to use that.
# workspace = w[tv1], gapsout:0, gapsin:0
# workspace = f[1], gapsout:0, gapsin:0
# windowrulev2 = bordersize 0, floating:0, onworkspace:w[tv1]
# windowrulev2 = rounding 0, floating:0, onworkspace:w[tv1]
# windowrulev2 = bordersize 0, floating:0, onworkspace:f[1]
# windowrulev2 = rounding 0, floating:0, onworkspace:f[1]
# See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more
dwindle {
pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
preserve_split = true # You probably want this
}
# See https://wiki.hyprland.org/Configuring/Master-Layout/ for more
master {
new_status = master
}
# https://wiki.hyprland.org/Configuring/Variables/#misc
misc {
force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers
disable_hyprland_logo = false # If true disables the random hyprland logo / anime girl background. :(
}
#############
### INPUT ###
#############
# https://wiki.hyprland.org/Configuring/Variables/#input
input {
kb_layout = us
kb_variant =
kb_model =
kb_options =
kb_rules =
follow_mouse = 1
sensitivity = 0 # -1.0 - 1.0, 0 means no modification.
touchpad {
natural_scroll = false
}
}
# https://wiki.hyprland.org/Configuring/Variables/#gestures
gestures {
workspace_swipe = false
}
# Example per-device config
# See https://wiki.hyprland.org/Configuring/Keywords/#per-device-input-configs for more
device {
name = epic-mouse-v1
sensitivity = -0.5
}
###################
### KEYBINDINGS ###
###################
# See https://wiki.hyprland.org/Configuring/Keywords/
$mainMod = SUPER # Sets "Windows" key as main modifier
# Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more
bind = $mainMod, Q, exec, $terminal
bind = $mainMod, C, killactive,
bind = $mainMod, M, exit,
bind = $mainMod, E, exec, $fileManager
bind = $mainMod, V, togglefloating,
bind = $mainMod, R, exec, $menu
bind = $mainMod, P, pseudo, # dwindle
bind = $mainMod, J, togglesplit, # dwindle
# Move focus with mainMod + arrow keys
bind = $mainMod, left, movefocus, l
bind = $mainMod, right, movefocus, r
bind = $mainMod, up, movefocus, u
bind = $mainMod, down, movefocus, d
# Switch workspaces with mainMod + [0-9]
bind = $mainMod, 1, workspace, 1
bind = $mainMod, 2, workspace, 2
bind = $mainMod, 3, workspace, 3
bind = $mainMod, 4, workspace, 4
bind = $mainMod, 5, workspace, 5
bind = $mainMod, 6, workspace, 6
bind = $mainMod, 7, workspace, 7
bind = $mainMod, 8, workspace, 8
bind = $mainMod, 9, workspace, 9
bind = $mainMod, 0, workspace, 10
# Move active window to a workspace with mainMod + SHIFT + [0-9]
bind = $mainMod SHIFT, 1, movetoworkspace, 1
bind = $mainMod SHIFT, 2, movetoworkspace, 2
bind = $mainMod SHIFT, 3, movetoworkspace, 3
bind = $mainMod SHIFT, 4, movetoworkspace, 4
bind = $mainMod SHIFT, 5, movetoworkspace, 5
bind = $mainMod SHIFT, 6, movetoworkspace, 6
bind = $mainMod SHIFT, 7, movetoworkspace, 7
bind = $mainMod SHIFT, 8, movetoworkspace, 8
bind = $mainMod SHIFT, 9, movetoworkspace, 9
bind = $mainMod SHIFT, 0, movetoworkspace, 10
# Example special workspace (scratchpad)
bind = $mainMod, S, togglespecialworkspace, magic
bind = $mainMod SHIFT, S, movetoworkspace, special:magic
# Scroll through existing workspaces with mainMod + scroll
bind = $mainMod, mouse_down, workspace, e+1
bind = $mainMod, mouse_up, workspace, e-1
# Move/resize windows with mainMod + LMB/RMB and dragging
bindm = $mainMod, mouse:272, movewindow
bindm = $mainMod, mouse:273, resizewindow
# Laptop multimedia keys for volume and LCD brightness
bindel = ,XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+
bindel = ,XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
bindel = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
bindel = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
bindel = ,XF86MonBrightnessUp, exec, brightnessctl s 10%+
bindel = ,XF86MonBrightnessDown, exec, brightnessctl s 10%-
# Requires playerctl
bindl = , XF86AudioNext, exec, playerctl next
bindl = , XF86AudioPause, exec, playerctl play-pause
bindl = , XF86AudioPlay, exec, playerctl play-pause
bindl = , XF86AudioPrev, exec, playerctl previous
##############################
### WINDOWS AND WORKSPACES ###
##############################
# See https://wiki.hyprland.org/Configuring/Window-Rules/ for more
# See https://wiki.hyprland.org/Configuring/Workspace-Rules/ for workspace rules
# Example windowrule v1
# windowrule = float, ^(kitty)$
# Example windowrule v2
# windowrulev2 = float,class:^(kitty)$,title:^(kitty)$
# Ignore maximize requests from apps. You'll probably like this.
windowrulev2 = suppressevent maximize, class:.*
# 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]

@@ -31,12 +31,16 @@ if cpp_compiler.check_header('execinfo.h')
add_project_arguments('-DHAS_EXECINFO', language: 'cpp')
endif
aquamarine = dependency('aquamarine', version: '>=0.4.5')
aquamarine = dependency('aquamarine', version: '>=0.9.0')
hyprcursor = dependency('hyprcursor', version: '>=0.1.7')
hyprgraphics = dependency('hyprgraphics', version: '>= 0.1.1')
hyprgraphics = dependency('hyprgraphics', version: '>= 0.1.3')
hyprlang = dependency('hyprlang', version: '>= 0.3.2')
hyprutils = dependency('hyprutils', version: '>= 0.2.3')
hyprutils = dependency('hyprutils', version: '>= 0.8.1')
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')
@@ -58,6 +62,7 @@ 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)
@@ -72,19 +77,17 @@ if (systemd_option.enabled())
subdir('systemd')
endif
if get_option('legacy_renderer').enabled()
add_project_arguments('-DLEGACY_RENDERER', language: 'cpp')
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*', check: true)
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)
@@ -101,11 +104,14 @@ endif
subdir('protocols')
subdir('src')
subdir('hyprctl')
subdir('hyprpm/src')
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')

@@ -1,5 +1,5 @@
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('legacy_renderer', type: 'feature', value: 'disabled', description: 'Enable legacy renderer')
option('hyprpm', type: 'feature', value: 'enabled', description: 'Enable hyprpm')
option('tracy_enable', type: 'boolean', value: false , description: 'Enable profiling')

@@ -5,12 +5,15 @@
pkg-config,
pkgconf,
makeWrapper,
cmake,
meson,
ninja,
aquamarine,
binutils,
cairo,
epoll-shim,
git,
glaze,
hyprcursor,
hyprgraphics,
hyprland-protocols,
@@ -24,7 +27,7 @@
libinput,
libxkbcommon,
libuuid,
mesa,
libgbm,
pango,
pciutils,
re2,
@@ -38,7 +41,6 @@
xwayland,
debug ? false,
enableXWayland ? true,
legacyRenderer ? false,
withSystemd ? lib.meta.availableOn stdenv.hostPlatform systemd,
wrapRuntimeDeps ? true,
version ? "git",
@@ -49,13 +51,14 @@
enableNvidiaPatches ? false,
nvidiaPatches ? false,
hidpiXWayland ? false,
legacyRenderer ? false,
}: let
inherit (builtins) baseNameOf foldl';
inherit (builtins) foldl' readFile;
inherit (lib.asserts) assertMsg;
inherit (lib.attrsets) mapAttrsToList;
inherit (lib.lists) flatten concatLists optional optionals;
inherit (lib.sources) cleanSourceWith cleanSource;
inherit (lib.strings) hasSuffix makeBinPath optionalString mesonBool mesonEnable;
inherit (lib.strings) makeBinPath optionalString mesonBool mesonEnable trim;
fs = lib.fileset;
adapters = flatten [
stdenvAdapters.useMoldLinker
@@ -66,17 +69,34 @@
in
assert assertMsg (!nvidiaPatches) "The option `nvidiaPatches` has been removed.";
assert assertMsg (!enableNvidiaPatches) "The option `enableNvidiaPatches` has been removed.";
assert assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been removed. Please refer https://wiki.hyprland.org/Configuring/XWayland";
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.";
customStdenv.mkDerivation (finalAttrs: {
pname = "hyprland${optionalString debug "-debug"}";
inherit version;
src = cleanSourceWith {
filter = name: _type: let
baseName = baseNameOf (toString name);
in
! (hasSuffix ".nix" baseName);
src = cleanSource ../.;
src = fs.toSource {
root = ../.;
fileset =
fs.intersection
# allows non-flake builds to only include files tracked by git
(fs.gitTracked ../.)
(fs.unions [
../assets/hyprland-portals.conf
../assets/install
../hyprctl
../hyprland.pc.in
../LICENSE
../meson_options.txt
../protocols
../src
../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") ../.)
]);
};
postPatch = ''
@@ -91,7 +111,7 @@ in
DATE = date;
DIRTY = optionalString (commit == "") "dirty";
HASH = commit;
TAG = "v${builtins.readFile "${finalAttrs.src}/VERSION"}";
TAG = "v${trim (readFile "${finalAttrs.src}/VERSION")}";
depsBuildBuild = [
pkg-config
@@ -102,6 +122,7 @@ in
makeWrapper
meson
ninja
cmake # needed for glaze
pkg-config
];
@@ -116,6 +137,7 @@ in
aquamarine
cairo
git
glaze
hyprcursor
hyprgraphics
hyprland-protocols
@@ -126,7 +148,7 @@ in
libinput
libuuid
libxkbcommon
mesa
libgbm
pango
pciutils
re2
@@ -137,6 +159,7 @@ in
wayland-scanner
xorg.libXcursor
]
(optionals customStdenv.hostPlatform.isBSD [epoll-shim])
(optionals customStdenv.hostPlatform.isMusl [libexecinfo])
(optionals enableXWayland [
xorg.libxcb
@@ -149,16 +172,19 @@ in
(optional withSystemd systemd)
];
strictDeps = true;
mesonBuildType =
if debug
then "debugoptimized"
then "debug"
else "release";
mesonFlags = flatten [
(mapAttrsToList mesonEnable {
"xwayland" = enableXWayland;
"legacy_renderer" = legacyRenderer;
"systemd" = withSystemd;
"uwsm" = false;
"hyprpm" = false;
})
(mapAttrsToList mesonBool {
"b_pch" = false;

60
nix/hyprtester.nix Normal file

@@ -0,0 +1,60 @@
{
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 = ''
cmake -S . -B .
cmake --build . --target generate-protocol-headers -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
cd hyprtester
'';
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";
};
})

201
nix/lib.nix Normal file

@@ -0,0 +1,201 @@
lib: let
inherit (lib)
attrNames
filterAttrs
foldl
generators
partition
;
inherit (lib.strings)
concatMapStrings
hasPrefix
;
/**
Convert a structured Nix attribute set into Hyprland's configuration format.
This function takes a nested attribute set and converts it into Hyprland-compatible
configuration syntax, supporting top, bottom, and regular command sections.
Commands are flattened using the `flattenAttrs` function, and attributes are formatted as
`key = value` pairs. Lists are expanded as duplicate keys to match Hyprland's expected format.
Configuration:
* `topCommandsPrefixes` - A list of prefixes to define **top** commands (default: `["$"]`).
* `bottomCommandsPrefixes` - A list of prefixes to define **bottom** commands (default: `[]`).
Attention:
- The function ensures top commands appear **first** and bottom commands **last**.
- The generated configuration is a **single string**, suitable for writing to a config file.
- Lists are converted into multiple entries, ensuring compatibility with Hyprland.
# Inputs
Structured function argument:
: topCommandsPrefixes (optional, default: `["$"]`)
: A list of prefixes that define **top** commands. Any key starting with one of these
prefixes will be placed at the beginning of the configuration.
: bottomCommandsPrefixes (optional, default: `[]`)
: A list of prefixes that define **bottom** commands. Any key starting with one of these
prefixes will be placed at the end of the configuration.
Value:
: The attribute set to be converted to Hyprland configuration format.
# Type
```
toHyprlang :: AttrSet -> AttrSet -> String
```
# Examples
:::{.example}
```nix
let
config = {
"$mod" = "SUPER";
monitor = {
"HDMI-A-1" = "1920x1080@60,0x0,1";
};
exec = [
"waybar"
"dunst"
];
};
in lib.toHyprlang {} config
```
**Output:**
```nix
"$mod = SUPER"
"monitor:HDMI-A-1 = 1920x1080@60,0x0,1"
"exec = waybar"
"exec = dunst"
```
:::
*/
toHyprlang = {
topCommandsPrefixes ? ["$"],
bottomCommandsPrefixes ? [],
}: attrs: let
toHyprlang' = attrs: let
# Specially configured `toKeyValue` generator with support for duplicate keys
# and a legible key-value separator.
mkCommands = generators.toKeyValue {
mkKeyValue = generators.mkKeyValueDefault {} " = ";
listsAsDuplicateKeys = true;
indent = ""; # No indent, since we don't have nesting
};
# Flatten the attrset, combining keys in a "path" like `"a:b:c" = "x"`.
# Uses `flattenAttrs` with a colon separator.
commands = flattenAttrs (p: k: "${p}:${k}") attrs;
# General filtering function to check if a key starts with any prefix in a given list.
filterCommands = list: n:
foldl (acc: prefix: acc || hasPrefix prefix n) false list;
# Partition keys into top commands and the rest
result = partition (filterCommands topCommandsPrefixes) (attrNames commands);
topCommands = filterAttrs (n: _: builtins.elem n result.right) commands;
remainingCommands = removeAttrs commands result.right;
# Partition remaining commands into bottom commands and regular commands
result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong;
bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands;
regularCommands = removeAttrs remainingCommands result2.right;
in
# Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands.
concatMapStrings mkCommands [
topCommands
regularCommands
bottomCommands
];
in
toHyprlang' attrs;
/**
Flatten a nested attribute set into a flat attribute set, using a custom key separator function.
This function recursively traverses a nested attribute set and produces a flat attribute set
where keys are joined using a user-defined function (`pred`). It allows transforming deeply
nested structures into a single-level attribute set while preserving key-value relationships.
Configuration:
* `pred` - A function `(string -> string -> string)` defining how keys should be concatenated.
# Inputs
Structured function argument:
: pred (required)
: A function that determines how parent and child keys should be combined into a single key.
It takes a `prefix` (parent key) and `key` (current key) and returns the joined key.
Value:
: The nested attribute set to be flattened.
# Type
```
flattenAttrs :: (String -> String -> String) -> AttrSet -> AttrSet
```
# Examples
:::{.example}
```nix
let
nested = {
a = "3";
b = { c = "4"; d = "5"; };
};
separator = (prefix: key: "${prefix}.${key}"); # Use dot notation
in lib.flattenAttrs separator nested
```
**Output:**
```nix
{
"a" = "3";
"b.c" = "4";
"b.d" = "5";
}
```
:::
*/
flattenAttrs = pred: attrs: let
flattenAttrs' = prefix: attrs:
builtins.foldl' (
acc: key: let
value = attrs.${key};
newKey =
if prefix == ""
then key
else pred prefix key;
in
acc
// (
if builtins.isAttrs value
then flattenAttrs' newKey value
else {"${newKey}" = value;}
)
) {} (builtins.attrNames attrs);
in
flattenAttrs' "" attrs;
in
{
inherit flattenAttrs toHyprlang;
}

@@ -5,17 +5,149 @@ inputs: {
...
}: let
inherit (pkgs.stdenv.hostPlatform) system;
selflib = import ./lib.nix lib;
cfg = config.programs.hyprland;
package = inputs.self.packages.${system}.hyprland;
portalPackage = inputs.self.packages.${system}.xdg-desktop-portal-hyprland.override {
hyprland = cfg.finalPackage;
};
in {
config = {
options = {
programs.hyprland = {
package = lib.mkDefault package;
portalPackage = lib.mkDefault portalPackage;
plugins = lib.mkOption {
type = with lib.types; listOf (either package path);
default = [];
description = ''
List of Hyprland plugins to use. Can either be packages or
absolute plugin paths.
'';
};
settings = lib.mkOption {
type = with lib.types; let
valueType =
nullOr (oneOf [
bool
int
float
str
path
(attrsOf valueType)
(listOf valueType)
])
// {
description = "Hyprland configuration value";
};
in
valueType;
default = {};
description = ''
Hyprland configuration written in Nix. Entries with the same key
should be written as lists. Variables' and colors' names should be
quoted. See <https://wiki.hypr.land> for more examples.
Special categories (e.g `devices`) should be written as
`"devices[device-name]"`.
::: {.note}
Use the [](#programs.hyprland.plugins) option to
declare plugins.
:::
'';
example = lib.literalExpression ''
{
decoration = {
shadow_offset = "0 5";
"col.shadow" = "rgba(00000099)";
};
"$mod" = "SUPER";
bindm = [
# mouse movements
"$mod, mouse:272, movewindow"
"$mod, mouse:273, resizewindow"
"$mod ALT, mouse:272, resizewindow"
];
}
'';
};
extraConfig = lib.mkOption {
type = lib.types.lines;
default = "";
example = ''
# window resize
bind = $mod, S, submap, resize
submap = resize
binde = , right, resizeactive, 10 0
binde = , left, resizeactive, -10 0
binde = , up, resizeactive, 0 -10
binde = , down, resizeactive, 0 10
bind = , escape, submap, reset
submap = reset
'';
description = ''
Extra configuration lines to add to `/etc/xdg/hypr/hyprland.conf`.
'';
};
topPrefixes = lib.mkOption {
type = with lib.types; listOf str;
default = ["$" "bezier"];
example = ["$" "bezier" "source"];
description = ''
List of prefix of attributes to put at the top of the config.
'';
};
bottomPrefixes = lib.mkOption {
type = with lib.types; listOf str;
default = [];
example = ["source"];
description = ''
List of prefix of attributes to put at the bottom of the config.
'';
};
};
};
config = lib.mkMerge [
{
programs.hyprland = {
package = lib.mkDefault inputs.self.packages.${system}.hyprland;
portalPackage = lib.mkDefault inputs.self.packages.${system}.xdg-desktop-portal-hyprland;
};
}
(lib.mkIf cfg.enable {
environment.etc."xdg/hypr/hyprland.conf" = let
shouldGenerate = cfg.extraConfig != "" || cfg.settings != {} || cfg.plugins != [];
pluginsToHyprlang = plugins:
selflib.toHyprlang {
topCommandsPrefixes = cfg.topPrefixes;
bottomCommandsPrefixes = cfg.bottomPrefixes;
}
{
"exec-once" = let
mkEntry = entry:
if lib.types.package.check entry
then "${entry}/lib/lib${entry.pname}.so"
else entry;
hyprctl = lib.getExe' config.programs.hyprland.package "hyprctl";
in
map (p: "${hyprctl} plugin load ${mkEntry p}") cfg.plugins;
};
in
lib.mkIf shouldGenerate {
text =
lib.optionalString (cfg.plugins != [])
(pluginsToHyprlang cfg.plugins)
+ lib.optionalString (cfg.settings != {})
(selflib.toHyprlang {
topCommandsPrefixes = cfg.topPrefixes;
bottomCommandsPrefixes = cfg.bottomPrefixes;
}
cfg.settings)
+ lib.optionalString (cfg.extraConfig != "") cfg.extraConfig;
};
})
];
}

@@ -8,7 +8,7 @@
(builtins.substring 4 2 longDate)
(builtins.substring 6 2 longDate)
]);
version = lib.removeSuffix "\n" (builtins.readFile ../VERSION);
ver = lib.removeSuffix "\n" (builtins.readFile ../VERSION);
in {
# Contains what a user is most likely to care about:
# Hyprland itself, XDPH and the Share Picker.
@@ -33,25 +33,28 @@ in {
# Hyprland packages themselves
(final: _prev: let
date = mkDate (self.lastModifiedDate or "19700101");
version = "${ver}+date=${date}_${self.shortRev or "dirty"}";
in {
hyprland = final.callPackage ./default.nix {
stdenv = final.gcc14Stdenv;
version = "${version}+date=${date}_${self.shortRev or "dirty"}";
stdenv = final.gcc15Stdenv;
commit = self.rev or "";
revCount = self.sourceInfo.revCount or "";
inherit date;
inherit date version;
};
hyprland-unwrapped = final.hyprland.override {wrapRuntimeDeps = false;};
# Build major libs with debug to get as much info as possible in a stacktrace
hyprland-debug = final.hyprland.override {
aquamarine = final.aquamarine.override {debug = true;};
hyprutils = final.hyprutils.override {debug = true;};
debug = true;
hyprtester = final.callPackage ./hyprtester.nix {
inherit version;
};
hyprland-legacy-renderer = final.hyprland.override {legacyRenderer = true;};
# deprecated packages
hyprland-legacy-renderer =
builtins.trace ''
hyprland-legacy-renderer was removed. Please use the hyprland package.
Legacy renderer is no longer supported.
''
final.hyprland;
hyprland-nvidia =
builtins.trace ''
hyprland-nvidia was removed. Please use the hyprland package.
@@ -62,12 +65,24 @@ in {
hyprland-hidpi =
builtins.trace ''
hyprland-hidpi was removed. Please use the hyprland package.
For more information, refer to https://wiki.hyprland.org/Configuring/XWayland.
For more information, refer to https://wiki.hypr.land/Configuring/XWayland.
''
final.hyprland;
})
];
# Debug
hyprland-debug = lib.composeManyExtensions [
# Dependencies
self.overlays.hyprland-packages
(final: prev: {
aquamarine = prev.aquamarine.override {debug = true;};
hyprutils = prev.hyprutils.override {debug = true;};
hyprland-debug = prev.hyprland.override {debug = true;};
})
];
# Packages for extra software recommended for usage with Hyprland,
# including forked or patched packages for compatibility.
hyprland-extras = lib.composeManyExtensions [

86
nix/tests/default.nix Normal file

@@ -0,0 +1,86 @@
inputs: pkgs: let
flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system};
hyprland = flake.hyprland;
in {
tests = pkgs.testers.runNixOSTest {
name = "hyprland-tests";
nodes.machine = {pkgs, ...}: {
environment.systemPackages = with pkgs; [
flake.hyprtester
# Programs needed for tests
kitty
xorg.xeyes
];
# Enabled by default for some reason
services.speechd.enable = false;
environment.variables = {
"AQ_TRACE" = "1";
"HYPRLAND_TRACE" = "1";
"XDG_RUNTIME_DIR" = "/tmp";
"XDG_CACHE_HOME" = "/tmp";
};
programs.hyprland = {
enable = true;
package = hyprland;
# We don't need portals in this test, so we don't set portalPackage
};
# Test configuration
environment.etc."test.conf".source = "${flake.hyprtester}/share/hypr/test.conf";
# Disable portals
xdg.portal.enable = pkgs.lib.mkForce false;
# Autologin root into tty
services.getty.autologinUser = "alice";
system.stateVersion = "24.11";
users.users.alice = {
isNormalUser = true;
};
virtualisation = {
cores = 4;
# Might crash with less
memorySize = 8192;
resolution = {
x = 1920;
y = 1080;
};
# Doesn't seem to do much, thought it would fix XWayland crashing
qemu.options = ["-vga none -device virtio-gpu-pci"];
};
};
testScript = ''
# Wait for tty to be up
machine.wait_for_unit("multi-user.target")
# 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]}'")
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/testerlog")
machine.copy_from_vm("/tmp/hyprlog")
machine.copy_from_vm("/tmp/exit_status")
# Print logs for visibility in CI
_, out = machine.execute("cat /tmp/testerlog")
print(f"Hyprtester log:\n{out}")
# Finally - shutdown
machine.shutdown()
'';
};
}

@@ -1,16 +1,25 @@
#!/usr/bin/env -S nix shell nixpkgs#jq -c bash
# Update inputs when the Mesa version is outdated. We don't want
# Update inputs when the Mesa or QT version is outdated. We don't want
# incompatibilities between the user's system and Hyprland.
# get the current Nixpkgs revision
REV=$(jq <flake.lock '.nodes.nixpkgs.locked.rev' -r)
# check versions for current and remote nixpkgs' mesa
CRT_VER=$(nix eval --raw github:nixos/nixpkgs/"$REV"#mesa.version)
NEW_VER=$(nix eval --raw github:nixos/nixpkgs/nixos-unstable#mesa.version)
if [ "$CRT_VER" != "$NEW_VER" ]; then
echo "Updating Mesa $CRT_VER -> $NEW_VER and flake inputs"
get_ver() {
nix eval --raw "github:nixos/nixpkgs/$1#$2"
}
# check versions for current and remote nixpkgs'
MESA_OLD=$(get_ver "$REV" mesa.version)
MESA_NEW=$(get_ver nixos-unstable mesa.version)
QT_OLD=$(get_ver "$REV" kdePackages.qtbase.version)
QT_NEW=$(get_ver nixos-unstable kdePackages.qtbase.version)
if [ "$MESA_OLD" != "$MESA_NEW" ] || [ "$QT_OLD" != "$QT_NEW" ]; then
echo "Updating flake inputs..."
echo "Mesa: $MESA_OLD -> $MESA_NEW"
echo "Qt: $QT_OLD -> $QT_NEW"
# update inputs to latest versions
nix flake update

@@ -0,0 +1,366 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="frog_color_management_v1">
<copyright>
Copyright © 2023 Joshua Ashton for Valve Software
Copyright © 2023 Xaver Hugl
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<description summary="experimental color management protocol">
The aim of this color management extension is to get HDR games working quickly,
and have an easy way to test implementations in the wild before the upstream
protocol is ready to be merged.
For that purpose it's intentionally limited and cut down and does not serve
all uses cases.
</description>
<interface name="frog_color_management_factory_v1" version="1">
<description summary="color management factory">
The color management factory singleton creates color managed surface objects.
</description>
<request name="destroy" type="destructor"></request>
<request name="get_color_managed_surface">
<description summary="create color management interface for surface">
</description>
<arg name="surface" type="object" interface="wl_surface"
summary="target surface" />
<arg name="callback" type="new_id" interface="frog_color_managed_surface"
summary="new color managed surface object" />
</request>
</interface>
<interface name="frog_color_managed_surface" version="1">
<description summary="color managed surface">
Interface for changing surface color management and HDR state.
An implementation must: support every part of the version
of the frog_color_managed_surface interface it exposes.
Including all known enums associated with a given version.
</description>
<request name="destroy" type="destructor">
<description summary="destroy color managed surface">
Destroying the color managed surface resets all known color
state for the surface back to 'undefined' implementation-specific
values.
</description>
</request>
<enum name="transfer_function">
<description summary="known transfer functions">
Extended information on the transfer functions described
here can be found in the Khronos Data Format specification:
https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html
</description>
<entry name="undefined" value="0"
summary="specifies undefined, implementation-specific handling of the surface's transfer function." />
<entry name="srgb" value="1"
summary="specifies the sRGB non-linear EOTF. An implementation may: display this as Gamma 2.2 for the purposes of being consistent with content rendering across displays, rendering_intent and user expectations." />
<entry name="gamma_22" value="2" summary="specifies gamma 2.2 power curve as the EOTF" />
<entry name="st2084_pq" value="3"
summary="specifies the SMPTE ST2084 Perceptual Quantizer (PQ) EOTF" />
<entry name="scrgb_linear" value="4"
summary="specifies the scRGB (extended sRGB) linear EOTF. Note: Primaries outside the gamut triangle specified can be expressed with negative values for this transfer function." />
</enum>
<request name="set_known_transfer_function">
<description summary="sets a known transfer function for a surface" />
<arg name="transfer_function" type="uint" enum="transfer_function"
summary="transfer function for the surface" />
</request>
<enum name="primaries">
<description summary="known primaries" />
<entry name="undefined" value="0"
summary="specifies undefined, implementation-specific handling" />
<entry name="rec709" value="1" summary="specifies Rec.709/sRGB primaries with D65 white point" />
<entry name="rec2020" value="2"
summary="specifies Rec.2020/HDR10 primaries with D65 white point" />
</enum>
<request name="set_known_container_color_volume">
<description summary="sets the container color volume (primaries) for a surface" />
<arg name="primaries" type="uint" enum="primaries" summary="primaries for the surface" />
</request>
<enum name="render_intent">
<description summary="known render intents">
Extended information on render intents described
here can be found in ICC.1:2022:
https://www.color.org/specification/ICC.1-2022-05.pdf
</description>
<entry name="perceptual" value="0" summary="perceptual" />
</enum>
<request name="set_render_intent">
<description summary="sets the render intent for a surface">
NOTE: On a surface with "perceptual" (default) render intent, handling of the container's
color volume
is implementation-specific, and may differ between different transfer functions it is paired
with:
ie. sRGB + 709 rendering may have it's primaries widened to more of the available display's
gamut
to be be more pleasing for the viewer.
Compared to scRGB Linear + 709 being treated faithfully as 709
(including utilizing negatives out of the 709 gamut triangle)
</description>
<arg name="render_intent" type="uint" enum="render_intent"
summary="render intent for the surface" />
</request>
<request name="set_hdr_metadata">
<description summary="set HDR metadata for a surface">
Forwards HDR metadata from the client to the compositor.
HDR Metadata Infoframe as per CTA 861.G spec.
Usage of this HDR metadata is implementation specific and
outside of the scope of this protocol.
</description>
<arg name="mastering_display_primary_red_x" type="uint">
<description summary="red primary x coordinate">
Mastering Red Color Primary X Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="mastering_display_primary_red_y" type="uint">
<description summary="red primary y coordinate">
Mastering Red Color Primary Y Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="mastering_display_primary_green_x" type="uint">
<description summary="green primary x coordinate">
Mastering Green Color Primary X Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="mastering_display_primary_green_y" type="uint">
<description summary="green primary y coordinate">
Mastering Green Color Primary Y Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="mastering_display_primary_blue_x" type="uint">
<description summary="blue primary x coordinate">
Mastering Blue Color Primary X Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="mastering_display_primary_blue_y" type="uint">
<description summary="blue primary y coordinate">
Mastering Blue Color Primary Y Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="mastering_white_point_x" type="uint">
<description summary="white point x coordinate">
Mastering White Point X Coordinate of the Data.
These are coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="mastering_white_point_y" type="uint">
<description summary="white point y coordinate">
Mastering White Point Y Coordinate of the Data.
These are coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="max_display_mastering_luminance" type="uint">
<description summary="max display mastering luminance">
Max Mastering Display Luminance.
This value is coded as an unsigned 16-bit value in units of 1 cd/m2,
where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2.
</description>
</arg>
<arg name="min_display_mastering_luminance" type="uint">
<description summary="min display mastering luminance">
Min Mastering Display Luminance.
This value is coded as an unsigned 16-bit value in units of
0.0001 cd/m2, where 0x0001 represents 0.0001 cd/m2 and 0xFFFF
represents 6.5535 cd/m2.
</description>
</arg>
<arg name="max_cll" type="uint">
<description summary="max content light level">
Max Content Light Level.
This value is coded as an unsigned 16-bit value in units of 1 cd/m2,
where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2.
</description>
</arg>
<arg name="max_fall" type="uint">
<description summary="max frame average light level">
Max Frame Average Light Level.
This value is coded as an unsigned 16-bit value in units of 1 cd/m2,
where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2.
</description>
</arg>
</request>
<event name="preferred_metadata">
<description summary="preferred metadata for a surface">
Current preferred metadata for a surface.
The application should use this information to tone-map its buffers
to this target before committing.
This metadata does not necessarily correspond to any physical output, but
rather what the compositor thinks would be best for a given surface.
</description>
<arg name="transfer_function" type="uint" enum="transfer_function">
<description summary="output's current transfer function">
Specifies a known transfer function that corresponds to the
output the surface is targeting.
</description>
</arg>
<arg name="output_display_primary_red_x" type="uint">
<description summary="red primary x coordinate">
Output Red Color Primary X Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="output_display_primary_red_y" type="uint">
<description summary="red primary y coordinate">
Output Red Color Primary Y Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="output_display_primary_green_x" type="uint">
<description summary="green primary x coordinate">
Output Green Color Primary X Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="output_display_primary_green_y" type="uint">
<description summary="green primary y coordinate">
Output Green Color Primary Y Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="output_display_primary_blue_x" type="uint">
<description summary="blue primary x coordinate">
Output Blue Color Primary X Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="output_display_primary_blue_y" type="uint">
<description summary="blue primary y coordinate">
Output Blue Color Primary Y Coordinate of the Data.
Coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="output_white_point_x" type="uint">
<description summary="white point x coordinate">
Output White Point X Coordinate of the Data.
These are coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="output_white_point_y" type="uint">
<description summary="white point y coordinate">
Output White Point Y Coordinate of the Data.
These are coded as unsigned 16-bit values in units of
0.00002, where 0x0000 represents zero and 0xC350
represents 1.0000.
</description>
</arg>
<arg name="max_luminance" type="uint">
<description summary="maximum luminance">
Max Output Luminance
The max luminance in nits that the output is capable of rendering in small areas.
Content should: not exceed this value to avoid clipping.
This value is coded as an unsigned 16-bit value in units of 1 cd/m2,
where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2.
</description>
</arg>
<arg name="min_luminance" type="uint">
<description summary="minimum luminance">
Min Output Luminance
The min luminance that the output is capable of rendering.
Content should: not exceed this value to avoid clipping.
This value is coded as an unsigned 16-bit value in units of
0.0001 cd/m2, where 0x0001 represents 0.0001 cd/m2 and 0xFFFF
represents 6.5535 cd/m2.
</description>
</arg>
<arg name="max_full_frame_luminance" type="uint">
<description summary="maximum full frame luminance">
Max Full Frame Luminance
The max luminance in nits that the output is capable of rendering for the
full frame sustained.
This value is coded as an unsigned 16-bit value in units of 1 cd/m2,
where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2.
</description>
</arg>
</event>
</interface>
</protocol>

@@ -1,13 +1,13 @@
wayland_protos = dependency(
'wayland-protocols',
version: '>=1.32',
version: '>=1.43',
fallback: 'wayland-protocols',
default_options: ['tests=false'],
)
hyprland_protos = dependency(
'hyprland-protocols',
version: '>=0.4',
version: '>=0.6.4',
fallback: 'hyprland-protocols',
)
@@ -33,10 +33,15 @@ protocols = [
'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',
@@ -66,6 +71,11 @@ protocols = [
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',
]
wl_protocols = []
@@ -81,7 +91,7 @@ foreach protocol : protocols
endforeach
# wayland.xml generation
wayland_scanner = dependency('wayland-scanner')
wayland_scanner = dependency('wayland-scanner', native: true)
wayland_scanner_datadir = wayland_scanner.get_variable('pkgdatadir')
wayland_xml = wayland_scanner_datadir / 'wayland.xml'

File diff suppressed because it is too large Load Diff

@@ -0,0 +1,24 @@
#!/bin/sh
SHADERS_SRC="./src/render/shaders/glsl"
echo "-- Generating shader includes"
if [ ! -d ./src/render/shaders ]; then
mkdir ./src/render/shaders
fi
echo '#pragma once' > ./src/render/shaders/Shaders.hpp
echo '#include <map>' >> ./src/render/shaders/Shaders.hpp
echo 'static const std::map<std::string, std::string> SHADERS = {' >> ./src/render/shaders/Shaders.hpp
for filename in `ls ${SHADERS_SRC}`; do
echo "-- ${filename}"
{ echo 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/render/shaders/${filename}.inc
echo "{\"${filename}\"," >> ./src/render/shaders/Shaders.hpp
echo "#include \"./${filename}.inc\"" >> ./src/render/shaders/Shaders.hpp
echo "}," >> ./src/render/shaders/Shaders.hpp
done
echo '};' >> ./src/render/shaders/Shaders.hpp

@@ -1,4 +1,13 @@
#!/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)}

File diff suppressed because it is too large Load Diff

@@ -1,38 +1,20 @@
#pragma once
#include <memory>
#include <list>
#include <sys/resource.h>
#include "defines.hpp"
#include "debug/Log.hpp"
#include "events/Events.hpp"
#include "config/ConfigManager.hpp"
#include "managers/ThreadManager.hpp"
#include <ranges>
#include "managers/XWaylandManager.hpp"
#include "managers/input/InputManager.hpp"
#include "managers/LayoutManager.hpp"
#include "managers/KeybindManager.hpp"
#include "managers/AnimationManager.hpp"
#include "managers/EventManager.hpp"
#include "managers/ProtocolManager.hpp"
#include "managers/SessionLockManager.hpp"
#include "managers/HookSystemManager.hpp"
#include "debug/HyprDebugOverlay.hpp"
#include "debug/HyprNotificationOverlay.hpp"
#include "helpers/Monitor.hpp"
#include "desktop/Workspace.hpp"
#include "desktop/Window.hpp"
#include "render/Renderer.hpp"
#include "render/OpenGL.hpp"
#include "hyprerror/HyprError.hpp"
#include "plugins/PluginSystem.hpp"
#include "helpers/Watchdog.hpp"
#include "protocols/types/ColorManagement.hpp"
#include <aquamarine/backend/Backend.hpp>
#include <aquamarine/output/Output.hpp>
class CWLSurfaceResource;
struct SWorkspaceRule;
enum eManagersInitStage : uint8_t {
STAGE_PRIORITY = 0,
@@ -42,71 +24,76 @@ enum eManagersInitStage : uint8_t {
class CCompositor {
public:
CCompositor();
CCompositor(bool onlyConfig = false);
~CCompositor();
wl_display* m_sWLDisplay;
wl_event_loop* m_sWLEventLoop;
int m_iDRMFD = -1;
bool m_bInitialized = false;
SP<Aquamarine::CBackend> m_pAqBackend;
wl_display* m_wlDisplay = nullptr;
wl_event_loop* m_wlEventLoop = nullptr;
int m_drmFD = -1;
bool m_initialized = false;
SP<Aquamarine::CBackend> m_aqBackend;
std::string m_szHyprTempDataRoot = "";
std::string m_hyprTempDataRoot = "";
std::string m_szWLDisplaySocket = "";
std::string m_szInstanceSignature = "";
std::string m_szInstancePath = "";
std::string m_szCurrentSplash = "error";
std::string m_wlDisplaySocket = "";
std::string m_instanceSignature = "";
std::string m_instancePath = "";
std::string m_currentSplash = "error";
std::vector<PHLMONITOR> m_vMonitors;
std::vector<PHLMONITOR> m_vRealMonitors; // for all monitors, even those turned off
std::vector<PHLWINDOW> m_vWindows;
std::vector<PHLLS> m_vLayers;
std::vector<PHLWORKSPACE> m_vWorkspaces;
std::vector<PHLWINDOWREF> m_vWindowsFadingOut;
std::vector<PHLLSREF> m_vSurfacesFadingOut;
std::vector<PHLMONITOR> m_monitors;
std::vector<PHLMONITOR> m_realMonitors; // for all monitors, even those turned off
std::vector<PHLWINDOW> m_windows;
std::vector<PHLLS> m_layers;
std::vector<PHLWINDOWREF> m_windowsFadingOut;
std::vector<PHLLSREF> m_surfacesFadingOut;
std::unordered_map<std::string, MONITORID> m_mMonitorIDMap;
std::unordered_map<std::string, MONITORID> m_monitorIDMap;
std::unordered_map<std::string, WORKSPACEID> m_seenMonitorWorkspaceMap; // map of seen monitor names to workspace IDs
void initServer(std::string socketName, int socketFd);
void startCompositor();
void stopCompositor();
void cleanup();
void createLockFile();
void removeLockFile();
void bumpNofile();
void restoreNofile();
WP<CWLSurfaceResource> m_pLastFocus;
PHLWINDOWREF m_pLastWindow;
PHLMONITORREF m_pLastMonitor;
WP<CWLSurfaceResource> m_lastFocus;
PHLWINDOWREF m_lastWindow;
PHLMONITORREF m_lastMonitor;
std::vector<PHLWINDOWREF> m_vWindowFocusHistory; // first element is the most recently focused.
std::vector<PHLWINDOWREF> m_windowFocusHistory; // first element is the most recently focused
bool m_bReadyToProcess = false;
bool m_bSessionActive = true;
bool m_bDPMSStateON = true;
bool m_bUnsafeState = false; // unsafe state is when there is no monitors.
bool m_bNextIsUnsafe = false;
PHLMONITORREF m_pUnsafeOutput; // fallback output for the unsafe state
bool m_bIsShuttingDown = false;
bool m_bFinalRequests = false;
bool m_bDesktopEnvSet = false;
bool m_bEnableXwayland = true;
bool m_readyToProcess = false;
bool m_sessionActive = true;
bool m_dpmsStateOn = true;
bool m_unsafeState = false; // unsafe state is when there is no monitors
PHLMONITORREF m_unsafeOutput; // fallback output for the unsafe state
bool m_isShuttingDown = false;
bool m_finalRequests = false;
bool m_desktopEnvSet = false;
bool m_wantsXwayland = true;
bool m_onlyConfigVerification = false;
// ------------------------------------------------- //
auto getWorkspaces() {
return std::views::filter(m_workspaces, [](const auto& e) { return e; });
}
void registerWorkspace(PHLWORKSPACE w);
//
PHLMONITOR getMonitorFromID(const MONITORID&);
PHLMONITOR getMonitorFromName(const std::string&);
PHLMONITOR getMonitorFromDesc(const std::string&);
PHLMONITOR getMonitorFromCursor();
PHLMONITOR getMonitorFromVector(const Vector2D&);
void removeWindowFromVectorSafe(PHLWINDOW);
void focusWindow(PHLWINDOW, SP<CWLSurfaceResource> pSurface = nullptr);
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*);
SP<CWLSurfaceResource> vectorToLayerSurface(const Vector2D&, std::vector<PHLLSREF>*, Vector2D*, PHLLS*, bool aboveLockscreen = false);
SP<CWLSurfaceResource> vectorToLayerPopupSurface(const Vector2D&, PHLMONITOR monitor, Vector2D*, PHLLS*);
SP<CWLSurfaceResource> vectorWindowToSurface(const Vector2D&, PHLWINDOW, Vector2D& sl);
Vector2D vectorToSurfaceLocal(const Vector2D&, PHLWINDOW, SP<CWLSurfaceResource>);
@@ -117,14 +104,14 @@ class CCompositor {
PHLWORKSPACE getWorkspaceByID(const WORKSPACEID&);
PHLWORKSPACE getWorkspaceByName(const std::string&);
PHLWORKSPACE getWorkspaceByString(const std::string&);
void sanityCheckWorkspaces();
PHLWINDOW getUrgentWindow();
bool isWindowActive(PHLWINDOW);
void changeWindowZOrder(PHLWINDOW, bool);
void cleanupFadingOut(const MONITORID& monid);
PHLWINDOW getWindowInDirection(PHLWINDOW, char);
PHLWINDOW getNextWindowOnWorkspace(PHLWINDOW, bool focusableOnly = false, std::optional<bool> floating = {});
PHLWINDOW getPrevWindowOnWorkspace(PHLWINDOW, bool focusableOnly = false, std::optional<bool> floating = {});
PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, char dir, PHLWINDOW ignoreWindow = nullptr, bool useVectorAngles = false);
PHLWINDOW getWindowCycle(PHLWINDOW cur, bool focusableOnly = false, std::optional<bool> floating = std::nullopt, bool visible = false, bool prev = false);
PHLWINDOW getWindowCycleHist(PHLWINDOWREF cur, bool focusableOnly = false, std::optional<bool> floating = std::nullopt, bool visible = false, bool next = false);
WORKSPACEID getNextAvailableNamedWorkspace();
bool isPointOnAnyMonitor(const Vector2D&);
bool isPointOnReservedArea(const Vector2D& point, const PHLMONITOR monitor = nullptr);
@@ -140,7 +127,6 @@ class CCompositor {
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 changeWindowFullscreenModeInternal(const PHLWINDOW PWINDOW, const eFullscreenMode MODE, const bool ON);
void changeWindowFullscreenModeClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE, const bool ON);
void updateFullscreenFadeOnWorkspace(PHLWORKSPACE);
PHLWINDOW getX11Parent(PHLWINDOW);
@@ -167,10 +153,14 @@ class CCompositor {
void setPreferredScaleForSurface(SP<CWLSurfaceResource> pSurface, double scale);
void setPreferredTransformForSurface(SP<CWLSurfaceResource> pSurface, wl_output_transform transform);
void updateSuspendedStates();
PHLWINDOW windowForCPointer(CWindow*);
void onNewMonitor(SP<Aquamarine::IOutput> output);
void ensurePersistentWorkspacesPresent(const std::vector<SWorkspaceRule>& rules, PHLWORKSPACE pWorkspace = nullptr);
std::string explicitConfigPath;
NColorManagement::SImageDescription getPreferredImageDescription();
bool shouldChangePreferredImageDescription();
bool supportsDrmSyncobjTimeline() const;
std::string m_explicitConfigPath;
private:
void initAllSignals();
@@ -179,10 +169,17 @@ class CCompositor {
void setRandomSplash();
void initManagers(eManagersInitStage stage);
void prepareFallbackOutput();
void createLockFile();
void removeLockFile();
void setMallocThreshold();
uint64_t m_iHyprlandPID = 0;
bool m_bDrmSyncobjTimelineSupported = false;
uint64_t m_hyprlandPID = 0;
wl_event_source* m_critSigSource = nullptr;
rlimit m_sOriginalNofile = {0};
rlimit m_originalNofile = {};
std::vector<PHLWORKSPACEREF> m_workspaces;
};
inline std::unique_ptr<CCompositor> g_pCompositor;
inline UP<CCompositor> g_pCompositor;

@@ -3,6 +3,8 @@
#include "helpers/math/Math.hpp"
#include <functional>
#include <any>
#include <string>
#include <algorithm>
#include <hyprutils/math/Box.hpp>
enum eIcons : uint8_t {
@@ -50,8 +52,14 @@ struct SHyprCtlCommand {
std::function<std::string(eHyprCtlOutputFormat, std::string)> fn;
};
typedef int64_t WINDOWID;
typedef int64_t MONITORID;
typedef int64_t WORKSPACEID;
struct SDispatchResult {
bool passEvent = false;
bool success = true;
std::string error;
};
typedef std::function<void(void*, SCallbackInfo&, std::any)> HOOK_CALLBACK_FN;
using WINDOWID = int64_t;
using MONITORID = int64_t;
using WORKSPACEID = int64_t;
using HOOK_CALLBACK_FN = std::function<void(void*, SCallbackInfo&, std::any)>;

@@ -2,16 +2,18 @@
#include "../defines.hpp"
#include "../helpers/varlist/VarList.hpp"
#include <vector>
#include <map>
enum eConfigValueDataTypes : int8_t {
CVD_TYPE_INVALID = -1,
CVD_TYPE_GRADIENT = 0,
CVD_TYPE_CSS_VALUE = 1
CVD_TYPE_CSS_VALUE = 1,
CVD_TYPE_FONT_WEIGHT = 2,
};
class ICustomConfigValueData {
public:
virtual ~ICustomConfigValueData() = 0;
virtual ~ICustomConfigValueData() = default;
virtual eConfigValueDataTypes getDataType() = 0;
@@ -22,7 +24,7 @@ class CGradientValueData : public ICustomConfigValueData {
public:
CGradientValueData() = default;
CGradientValueData(CHyprColor col) {
m_vColors.push_back(col);
m_colors.push_back(col);
updateColorsOk();
};
virtual ~CGradientValueData() = default;
@@ -32,39 +34,39 @@ class CGradientValueData : public ICustomConfigValueData {
}
void reset(CHyprColor col) {
m_vColors.clear();
m_vColors.emplace_back(col);
m_fAngle = 0;
m_colors.clear();
m_colors.emplace_back(col);
m_angle = 0;
updateColorsOk();
}
void updateColorsOk() {
m_vColorsOkLabA.clear();
for (auto& c : m_vColors) {
m_colorsOkLabA.clear();
for (auto& c : m_colors) {
const auto OKLAB = c.asOkLab();
m_vColorsOkLabA.emplace_back(OKLAB.l);
m_vColorsOkLabA.emplace_back(OKLAB.a);
m_vColorsOkLabA.emplace_back(OKLAB.b);
m_vColorsOkLabA.emplace_back(c.a);
m_colorsOkLabA.emplace_back(OKLAB.l);
m_colorsOkLabA.emplace_back(OKLAB.a);
m_colorsOkLabA.emplace_back(OKLAB.b);
m_colorsOkLabA.emplace_back(c.a);
}
}
/* Vector containing the colors */
std::vector<CHyprColor> m_vColors;
std::vector<CHyprColor> m_colors;
/* Vector containing pure colors for shoving into opengl */
std::vector<float> m_vColorsOkLabA;
std::vector<float> m_colorsOkLabA;
/* Float corresponding to the angle (rad) */
float m_fAngle = 0;
float m_angle = 0;
//
bool operator==(const CGradientValueData& other) const {
if (other.m_vColors.size() != m_vColors.size() || m_fAngle != other.m_fAngle)
if (other.m_colors.size() != m_colors.size() || m_angle != other.m_angle)
return false;
for (size_t i = 0; i < m_vColors.size(); ++i)
if (m_vColors[i] != other.m_vColors[i])
for (size_t i = 0; i < m_colors.size(); ++i)
if (m_colors[i] != other.m_colors[i])
return false;
return true;
@@ -72,28 +74,28 @@ class CGradientValueData : public ICustomConfigValueData {
virtual std::string toString() {
std::string result;
for (auto& c : m_vColors) {
for (auto& c : m_colors) {
result += std::format("{:x} ", c.getAsHex());
}
result += std::format("{}deg", (int)(m_fAngle * 180.0 / M_PI));
result += std::format("{}deg", (int)(m_angle * 180.0 / M_PI));
return result;
}
};
class CCssGapData : public ICustomConfigValueData {
public:
CCssGapData() : top(0), right(0), bottom(0), left(0) {};
CCssGapData(int64_t global) : top(global), right(global), bottom(global), left(global) {};
CCssGapData(int64_t vertical, int64_t horizontal) : top(vertical), right(horizontal), bottom(vertical), left(horizontal) {};
CCssGapData(int64_t top, int64_t horizontal, int64_t bottom) : top(top), right(horizontal), bottom(bottom), left(horizontal) {};
CCssGapData(int64_t top, int64_t right, int64_t bottom, int64_t left) : top(top), right(right), bottom(bottom), left(left) {};
CCssGapData() : m_top(0), m_right(0), m_bottom(0), m_left(0) {};
CCssGapData(int64_t global) : m_top(global), m_right(global), m_bottom(global), m_left(global) {};
CCssGapData(int64_t vertical, int64_t horizontal) : m_top(vertical), m_right(horizontal), m_bottom(vertical), m_left(horizontal) {};
CCssGapData(int64_t top, int64_t horizontal, int64_t bottom) : m_top(top), m_right(horizontal), m_bottom(bottom), m_left(horizontal) {};
CCssGapData(int64_t top, int64_t right, int64_t bottom, int64_t left) : m_top(top), m_right(right), m_bottom(bottom), m_left(left) {};
/* Css like directions */
int64_t top;
int64_t right;
int64_t bottom;
int64_t left;
int64_t m_top;
int64_t m_right;
int64_t m_bottom;
int64_t m_left;
void parseGapData(CVarList varlist) {
switch (varlist.size()) {
@@ -122,10 +124,10 @@ class CCssGapData : public ICustomConfigValueData {
}
void reset(int64_t global) {
top = global;
right = global;
bottom = global;
left = global;
m_top = global;
m_right = global;
m_bottom = global;
m_left = global;
}
virtual eConfigValueDataTypes getDataType() {
@@ -133,6 +135,44 @@ class CCssGapData : public ICustomConfigValueData {
}
virtual std::string toString() {
return std::format("{} {} {} {}", top, right, bottom, left);
return std::format("{} {} {} {}", m_top, m_right, m_bottom, m_left);
}
};
class CFontWeightConfigValueData : public ICustomConfigValueData {
public:
CFontWeightConfigValueData() = default;
CFontWeightConfigValueData(const char* weight) {
parseWeight(weight);
}
int64_t m_value = 400; // default to normal weight
virtual eConfigValueDataTypes getDataType() {
return CVD_TYPE_FONT_WEIGHT;
}
virtual std::string toString() {
return std::format("{}", m_value);
}
void parseWeight(const std::string& strWeight) {
auto lcWeight{strWeight};
std::ranges::transform(strWeight, lcWeight.begin(), ::tolower);
// values taken from Pango weight enums
const auto WEIGHTS = std::map<std::string, int>{
{"thin", 100}, {"ultralight", 200}, {"light", 300}, {"semilight", 350}, {"book", 380}, {"normal", 400},
{"medium", 500}, {"semibold", 600}, {"bold", 700}, {"ultrabold", 800}, {"heavy", 900}, {"ultraheavy", 1000},
};
auto weight = WEIGHTS.find(lcWeight);
if (weight != WEIGHTS.end())
m_value = weight->second;
else {
int w_i = std::stoi(strWeight);
if (w_i < 100 || w_i > 1000)
m_value = 400;
}
}
};

@@ -32,6 +32,13 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_STRING_SHORT,
.data = SConfigOptionDescription::SStringData{"20"},
},
SConfigOptionDescription{
.value = "general:float_gaps",
.description = "gaps between windows and monitor edges for floating windows\n\nsupports css style gaps (top, right, bottom, left -> 5 10 15 20). \n-1 means default "
"gaps_in/gaps_out\n0 means no gaps",
.type = CONFIG_OPTION_STRING_SHORT,
.data = SConfigOptionDescription::SStringData{"0"},
},
SConfigOptionDescription{
.value = "general:gaps_workspaces",
.description = "gaps between workspaces. Stacks with gaps_out.",
@@ -128,6 +135,12 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "general:snap:respect_gaps",
.description = "if true, snapping will respect gaps between windows",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
/*
* decoration:
@@ -139,6 +152,12 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{0, 0, 20},
},
SConfigOptionDescription{
.value = "decoration:rounding_power",
.description = "rouding power of corners (2 is a circle)",
.type = CONFIG_OPTION_FLOAT,
.data = SConfigOptionDescription::SFloatData{2, 2, 10},
},
SConfigOptionDescription{
.value = "decoration:active_opacity",
.description = "opacity of active windows. [0.0 - 1.0]",
@@ -241,104 +260,110 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_STRING_LONG,
.data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET?
},
SConfigOptionDescription{
.value = "decoration:border_part_of_window",
.description = "whether the border should be treated as a part of the window.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
/*
* blur:
*/
SConfigOptionDescription{
.value = "blur:enabled",
.value = "decoration:blur:enabled",
.description = "enable kawase window background blur",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "blur:size",
.value = "decoration:blur:size",
.description = "blur size (distance)",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{8, 0, 100},
},
SConfigOptionDescription{
.value = "blur:passes",
.value = "decoration:blur:passes",
.description = "the amount of passes to perform",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{1, 0, 10},
},
SConfigOptionDescription{
.value = "blur:ignore_opacity",
.value = "decoration:blur:ignore_opacity",
.description = "make the blur layer ignore the opacity of the window",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "blur:new_optimizations",
.value = "decoration:blur:new_optimizations",
.description = "whether to enable further optimizations to the blur. Recommended to leave on, as it will massively improve performance.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "blur:xray",
.value = "decoration:blur:xray",
.description = "if enabled, floating windows will ignore tiled windows in their blur. Only available if blur_new_optimizations is true. Will reduce overhead on floating "
"blur significantly.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "blur:noise",
.value = "decoration:blur:noise",
.description = "how much noise to apply. [0.0 - 1.0]",
.type = CONFIG_OPTION_FLOAT,
.data = SConfigOptionDescription::SFloatData{0.0117, 0, 1},
},
SConfigOptionDescription{
.value = "blur:contrast",
.value = "decoration:blur:contrast",
.description = "contrast modulation for blur. [0.0 - 2.0]",
.type = CONFIG_OPTION_FLOAT,
.data = SConfigOptionDescription::SFloatData{0.8916, 0, 2},
},
SConfigOptionDescription{
.value = "blur:brightness",
.value = "decoration:blur:brightness",
.description = "brightness modulation for blur. [0.0 - 2.0]",
.type = CONFIG_OPTION_FLOAT,
.data = SConfigOptionDescription::SFloatData{0.8172, 0, 2},
},
SConfigOptionDescription{
.value = "blur:vibrancy",
.value = "decoration:blur:vibrancy",
.description = "Increase saturation of blurred colors. [0.0 - 1.0]",
.type = CONFIG_OPTION_FLOAT,
.data = SConfigOptionDescription::SFloatData{0.1696, 0, 1},
},
SConfigOptionDescription{
.value = "blur:vibrancy_darkness",
.value = "decoration:blur:vibrancy_darkness",
.description = "How strong the effect of vibrancy is on dark areas . [0.0 - 1.0]",
.type = CONFIG_OPTION_FLOAT,
.data = SConfigOptionDescription::SFloatData{0, 0, 1},
},
SConfigOptionDescription{
.value = "blur:special",
.value = "decoration:blur:special",
.description = "whether to blur behind the special workspace (note: expensive)",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "blur:popups",
.value = "decoration:blur:popups",
.description = "whether to blur popups (e.g. right-click menus)",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "blur:popups_ignorealpha",
.value = "decoration:blur:popups_ignorealpha",
.description = "works like ignorealpha in layer rules. If pixel opacity is below set value, will not blur. [0.0 - 1.0]",
.type = CONFIG_OPTION_FLOAT,
.data = SConfigOptionDescription::SFloatData{0.2, 0, 1},
},
SConfigOptionDescription{
.value = "blur:input_methods",
.value = "decoration:blur:input_methods",
.description = "whether to blur input methods (e.g. fcitx5)",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "blur:input_methods_ignorealpha",
.value = "decoration:blur:input_methods_ignorealpha",
.description = "works like ignorealpha in layer rules. If pixel opacity is below set value, will not blur. [0.0 - 1.0]",
.type = CONFIG_OPTION_FLOAT,
.data = SConfigOptionDescription::SFloatData{0.2, 0, 1},
@@ -360,6 +385,12 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "animations:workspace_wraparound",
.description = "changes the direction of slide animations between the first and last workspaces",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
/*
* input:
@@ -495,6 +526,12 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{1, 0, 3},
},
SConfigOptionDescription{
.value = "input:follow_mouse_threshold",
.description = "The smallest distance in logical pixels the mouse needs to travel for the window under it to get focused. Works only with follow_mouse = 1.",
.type = CONFIG_OPTION_FLOAT,
.data = SConfigOptionDescription::SFloatData{},
},
SConfigOptionDescription{
.value = "input:focus_on_close",
.description = "Controls the window focus behavior when a window is closed. When set to 0, focus will shift to the next window candidate. When set to 1, focus will shift "
@@ -586,9 +623,10 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
},
SConfigOptionDescription{
.value = "input:touchpad:drag_lock",
.description = "When enabled, lifting the finger off for a short time while dragging will not drop the dragged item.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
.description = "When enabled, lifting the finger off while dragging will not drop the dragged item. 0 -> disabled, 1 -> enabled with timeout, 2 -> enabled sticky."
"dragging will not drop the dragged item.",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{0, 0, 2},
},
SConfigOptionDescription{
.value = "input:touchpad:tap-and-drag",
@@ -596,6 +634,24 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "input:touchpad:flip_x",
.description = "Inverts the horizontal movement of the touchpad",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "input:touchpad:flip_y",
.description = "Inverts the vertical movement of the touchpad",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "input:touchpad:drag_3fg",
.description = "Three Finger Drag 0 -> disabled, 1 -> 3 finger, 2 -> 4 finger",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{0, 0, 2},
},
/*
* input:touchdevice:
@@ -803,25 +859,25 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "general:col.border_active",
.value = "group:col.border_active",
.description = "border color for inactive windows",
.type = CONFIG_OPTION_GRADIENT,
.data = SConfigOptionDescription::SGradientData{"0x66ffff00"},
},
SConfigOptionDescription{
.value = "general:col.border_inactive",
.value = "group:col.border_inactive",
.description = "border color for the active window",
.type = CONFIG_OPTION_GRADIENT,
.data = SConfigOptionDescription::SGradientData{"0x66777700"},
},
SConfigOptionDescription{
.value = "general:col.border_locked_active",
.value = "group:col.border_locked_inactive",
.description = "inactive border color for window that cannot be added to a group (see denywindowfromgroup dispatcher)",
.type = CONFIG_OPTION_GRADIENT,
.data = SConfigOptionDescription::SGradientData{"0x66ff5500"},
},
SConfigOptionDescription{
.value = "general:col.border_locked_inactive",
.value = "group:col.border_locked_active",
.description = "active border color for window that cannot be added to a group",
.type = CONFIG_OPTION_GRADIENT,
.data = SConfigOptionDescription::SGradientData{"0x66775500"},
@@ -867,6 +923,18 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_STRING_SHORT,
.data = SConfigOptionDescription::SStringData{STRVAL_EMPTY}, //##TODO UNSET?
},
SConfigOptionDescription{
.value = "group:groupbar:font_weight_active",
.description = "weight of the font used to display active groupbar titles",
.type = CONFIG_OPTION_STRING_SHORT,
.data = SConfigOptionDescription::SStringData{"normal"},
},
SConfigOptionDescription{
.value = "group:groupbar:font_weight_inactive",
.description = "weight of the font used to display inactive groupbar titles",
.type = CONFIG_OPTION_STRING_SHORT,
.data = SConfigOptionDescription::SStringData{"normal"},
},
SConfigOptionDescription{
.value = "group:groupbar:font_size",
.description = "font size of groupbar title",
@@ -877,7 +945,7 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.value = "group:groupbar:gradients",
.description = "enables gradients",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "group:groupbar:height",
@@ -885,6 +953,18 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{14, 1, 64},
},
SConfigOptionDescription{
.value = "group:groupbar:indicator_gap",
.description = "height of the gap between the groupbar indicator and title",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{0, 0, 64},
},
SConfigOptionDescription{
.value = "group:groupbar:indicator_height",
.description = "height of the groupbar indicator",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{3, 1, 64},
},
SConfigOptionDescription{
.value = "group:groupbar:stacked",
.description = "render the groupbar as a vertical stack",
@@ -909,12 +989,54 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "group:groupbar:rounding",
.description = "how much to round the groupbar",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{1, 0, 20},
},
SConfigOptionDescription{
.value = "group:groupbar:gradient_rounding",
.description = "how much to round the groupbar gradient",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{1, 0, 20},
},
SConfigOptionDescription{
.value = "group:groupbar:round_only_edges",
.description = "if yes, will only round at the groupbar edges",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "group:groupbar:gradient_round_only_edges",
.description = "if yes, will only round at the groupbar gradient edges",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "group:groupbar:text_color",
.description = "controls the group bar text color",
.description = "color for window titles in the groupbar",
.type = CONFIG_OPTION_COLOR,
.data = SConfigOptionDescription::SColorData{0xffffffff},
},
SConfigOptionDescription{
.value = "group:groupbar:text_color_inactive",
.description = "color for inactive windows' titles in the groupbar (if unset, defaults to text_color)",
.type = CONFIG_OPTION_COLOR,
.data = SConfigOptionDescription::SColorData{}, //TODO: UNSET?
},
SConfigOptionDescription{
.value = "group:groupbar:text_color_locked_active",
.description = "color for the active window's title in a locked group (if unset, defaults to text_color)",
.type = CONFIG_OPTION_COLOR,
.data = SConfigOptionDescription::SColorData{}, //TODO: UNSET?
},
SConfigOptionDescription{
.value = "group:groupbar:text_color_locked_inactive",
.description = "color for inactive windows' titles in locked groups (if unset, defaults to text_color_inactive)",
.type = CONFIG_OPTION_COLOR,
.data = SConfigOptionDescription::SColorData{}, //TODO: UNSET?
},
SConfigOptionDescription{
.value = "group:groupbar:col.active",
.description = "active group border color",
@@ -939,6 +1061,30 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_COLOR,
.data = SConfigOptionDescription::SColorData{0x66775500},
},
SConfigOptionDescription{
.value = "group:groupbar:gaps_out",
.description = "gap between gradients and window",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{2, 0, 20},
},
SConfigOptionDescription{
.value = "group:groupbar:gaps_in",
.description = "gap between gradients",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{2, 0, 20},
},
SConfigOptionDescription{
.value = "group:groupbar:keep_upper_gap",
.description = "keep an upper gap above gradient",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "group:groupbar:text_offset",
.description = "set an offset for a text",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SRangeData{0, -20, 20},
},
/*
* misc:
@@ -988,9 +1134,9 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
},
SConfigOptionDescription{
.value = "misc:vrr",
.description = " controls the VRR (Adaptive Sync) of your monitors. 0 - off, 1 - on, 2 - fullscreen only [0/1/2]",
.description = " controls the VRR (Adaptive Sync) of your monitors. 0 - off, 1 - on, 2 - fullscreen only, 3 - fullscreen with game or video content type [0/1/2/3]",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{0, 0, 2},
.data = SConfigOptionDescription::SRangeData{.value = 0, .min = 0, .max = 3},
},
SConfigOptionDescription{
.value = "misc:mouse_move_enables_dpms",
@@ -1067,20 +1213,14 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "misc:render_ahead_of_time",
.description = "[Warning: buggy] starts rendering before your monitor displays a frame in order to lower latency",
.value = "misc:allow_session_lock_restore",
.description = "if true, will allow you to restart a lockscreen app in case it crashes (red screen of death)",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "misc:render_ahead_safezone",
.description = "how many ms of safezone to add to rendering ahead of time. Recommended 1-2.",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{1, 1, 10},
},
SConfigOptionDescription{
.value = "misc:allow_session_lock_restore",
.description = "if true, will allow you to restart a lockscreen app in case it crashes (red screen of death)",
.value = "misc:session_lock_xray",
.description = "keep rendering workspaces below your lockscreen",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
@@ -1145,6 +1285,18 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{1000, 0, 5000},
},
SConfigOptionDescription{
.value = "misc:enable_anr_dialog",
.description = "whether to enable the ANR (app not responding) dialog when your apps hang",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.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},
},
/*
* binds:
@@ -1168,6 +1320,12 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "binds:hide_special_on_workspace_change",
.description = "If enabled, changing the active workspace (including to itself) will hide the special workspace on the monitor where the newly active workspace resides.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "binds:allow_workspace_cycles",
.description = "If enabled, workspaces dont forget their previous workspace, so cycles can be created by switching to the first workspace in a sequence, then endlessly "
@@ -1198,7 +1356,7 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.value = "binds:movefocus_cycles_fullscreen",
.description = "If enabled, when on a fullscreen window, movefocus will cycle fullscreen, if not, it will move the focus in a direction.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "binds:movefocus_cycles_groupfirst",
@@ -1224,6 +1382,12 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "binds:drag_threshold",
.description = "Movement threshold in pixels for window dragging and c/g bind flags. 0 to disable and grab on mousedown.",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{0, 0, INT_MAX},
},
/*
* xwayland:
@@ -1247,6 +1411,12 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "xwayland:create_abstract_socket",
.description = "Create the abstract Unix domain socket for XWayland",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
/*
* opengl:
@@ -1258,34 +1428,63 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "opengl:force_introspection",
.description = "forces introspection at all times. Introspection is aimed at reducing GPU usage in certain cases, but might cause graphical glitches on nvidia. 0 - "
"nothing, 1 - force always on, 2 - force always on if nvidia",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{2, 0, 2},
},
/*
* render:
*/
SConfigOptionDescription{
.value = "render:explicit_sync",
.description = "Whether to enable explicit sync support. Requires a hyprland restart. 0 - no, 1 - yes, 2 - auto based on the gpu driver",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{2, 0, 2},
},
SConfigOptionDescription{
.value = "render:explicit_sync_kms",
.description = "Whether to enable explicit sync support for the KMS layer. Requires explicit_sync to be enabled. 0 - no, 1 - yes, 2 - auto based on the gpu driver",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{2, 0, 2},
},
SConfigOptionDescription{
.value = "render:direct_scanout",
.description = "Enables direct scanout. Direct scanout attempts to reduce lag when there is only one fullscreen application on a screen (e.g. game). It is also "
"recommended to set this to false if the fullscreen application shows graphical glitches.",
"recommended to set this to false if the fullscreen application shows graphical glitches. 0 - off, 1 - on, 2 - auto (on with content type 'game')",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{.value = 0, .min = 0, .max = 2},
},
SConfigOptionDescription{
.value = "render:expand_undersized_textures",
.description = "Whether to expand textures that have not yet resized to be larger, or to just stretch them instead.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "render:xp_mode",
.description = "Disable back buffer and bottom layer rendering.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "render:ctm_animation",
.description = "Whether to enable a fade animation for CTM changes (hyprsunset). 2 means 'auto' (Yes on everything but Nvidia).",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{2, 0, 2},
},
SConfigOptionDescription{
.value = "render:cm_fs_passthrough",
.description = "Passthrough color settings for fullscreen apps when possible",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{.value = 2, .min = 0, .max = 2},
},
SConfigOptionDescription{
.value = "render:cm_enabled",
.description = "Enable Color Management pipelines (requires restart to fully take effect)",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "render:send_content_type",
.description = "Report content type to allow monitor profile autoswitch (may result in a black screen during the switch)",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "render:cm_auto_hdr",
.description = "Auto-switch to hdr mode when fullscreen app is in hdr, 0 - off, 1 - hdr, 2 - hdredid (cm_fs_passthrough can switch to hdr even when this setting is off)",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{.value = 1, .min = 0, .max = 2},
},
SConfigOptionDescription{
.value = "render:new_render_scheduling",
.description = "enable new render scheduling, which should improve FPS on underpowered devices. This does not add latency when your PC can keep up.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
@@ -1295,23 +1494,23 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
*/
SConfigOptionDescription{
.value = "cursor:use_nearest_neighbor",
.description = "sync xcursor theme with gsettings, it applies cursor-theme and cursor-size on theme load to gsettings making most CSD gtk based clients use same xcursor "
"theme and size.",
.value = "cursor:invisible",
.description = "don't render cursors",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "cursor:no_hardware_cursors",
.description = "disables hardware cursors",
.description = "disables hardware cursors. Auto = disable when multi-gpu on nvidia",
.type = CONFIG_OPTION_CHOICE,
.data = SConfigOptionDescription::SChoiceData{0, "Disabled,Enabled,Auto"},
},
SConfigOptionDescription{
.value = "cursor:no_break_fs_vrr",
.description = "disables scheduling new frames on cursor movement for fullscreen apps with VRR enabled to avoid framerate spikes (requires no_hardware_cursors = true)",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
.description = "disables scheduling new frames on cursor movement for fullscreen apps with VRR enabled to avoid framerate spikes (may require no_hardware_cursors = true) "
"0 - off, 1 - on, 2 - auto (on with content type 'game')",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{.value = 2, .min = 0, .max = 2},
},
SConfigOptionDescription{
.value = "cursor:min_refresh_rate",
@@ -1349,6 +1548,13 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_CHOICE,
.data = SConfigOptionDescription::SChoiceData{0, "Disabled,Enabled,Force"},
},
SConfigOptionDescription{
.value = "cursor:warp_on_toggle_special",
.description = "Move the cursor to the last focused window when toggling a special workspace. Options: 0 (Disabled), 1 (Enabled), "
"2 (Force - ignores cursor:no_warps option)",
.type = CONFIG_OPTION_CHOICE,
.data = SConfigOptionDescription::SChoiceData{0, "Disabled,Enabled,Force"},
},
SConfigOptionDescription{
.value = "cursor:default_monitor",
.description = "the name of a default monitor for the cursor to be set to on startup (see hyprctl monitors for names)",
@@ -1391,6 +1597,41 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "cursor:sync_gsettings_theme",
.description = "sync xcursor theme with gsettings, it applies cursor-theme and cursor-size on theme load to gsettings making most CSD gtk based clients use same xcursor "
"theme and size.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "cursor:warp_back_after_non_mouse_input",
.description = "warp the cursor back to where it was after using a non-mouse input to move it, and then returning back to mouse.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
/*
* ecosystem:
*/
SConfigOptionDescription{
.value = "ecosystem:no_update_news",
.description = "disable the popup that shows up when you update hyprland to a new version.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "ecosystem:no_donation_nag",
.description = "disable the popup that shows up twice a year encouraging to donate.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "ecosystem:enforce_permissions",
.description = "whether to enable permission control (see https://wiki.hypr.land/Configuring/Permissions/).",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
/*
* debug:
@@ -1444,12 +1685,6 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "debug:watchdog_timeout",
.description = "sets the timeout in seconds for watchdog to abort processing of a signal of the main thread. Set to 0 to disable.",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{5, 0, 20},
},
SConfigOptionDescription{
.value = "debug:disable_scale_checks",
.description = "disables verification of the scale factors. Will result in pixel alignment and rounding errors.",
@@ -1474,6 +1709,24 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "debug:log_damage",
.description = "enables logging the damage.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "debug:pass",
.description = "enables render pass debugging.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "debug:full_cm_proto",
.description = "claims support for all cm proto features (requires restart)",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
/*
* dwindle:
@@ -1548,6 +1801,24 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_CHOICE,
.data = SConfigOptionDescription::SChoiceData{0, "positional,current,opening"},
},
SConfigOptionDescription{
.value = "dwindle:precise_mouse_move",
.description = "if enabled, bindm movewindow will drop the window more precisely depending on where your mouse is.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "dwindle:single_window_aspect_ratio",
.description = "If specified, whenever only a single window is open, it will be coerced into the specified aspect ratio. Ignored if the y-value is zero.",
.type = CONFIG_OPTION_VECTOR,
.data = SConfigOptionDescription::SVectorData{{0, 0}, {0, 0}, {1000., 1000.}},
},
SConfigOptionDescription{
.value = "dwindle:single_window_aspect_ratio_tolerance",
.description = "Minimum distance for single_window_aspect_ratio to take effect, in fractions of the monitor's size.",
.type = CONFIG_OPTION_FLOAT,
.data = SConfigOptionDescription::SFloatData{0.1f, 0.f, 1.f},
},
/*
* master:
@@ -1603,11 +1874,15 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "master:always_center_master",
.description = "when using orientation=center, keep the master window centered, even when it is the only window in the workspace.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
.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)",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{2, 0, 10}, //##TODO RANGE?
},
SConfigOptionDescription{.value = "master:center_master_fallback",
.description = "Set fallback for center master when slaves are less than slave_count_for_center_master, can be left ,right ,top ,bottom",
.type = CONFIG_OPTION_STRING_SHORT,
.data = SConfigOptionDescription::SStringData{"left"}},
SConfigOptionDescription{
.value = "master:center_ignores_reserved",
.description = "centers the master window on monitor ignoring reserved areas",
@@ -1628,4 +1903,21 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "master:always_keep_position",
.description = "whether to keep the master window in its configured position when there are no slave windows",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
/*
* Experimental
*/
SConfigOptionDescription{
.value = "experimental:xx_color_management_v4",
.description = "enable color management protocol",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
};

File diff suppressed because it is too large Load Diff

@@ -1,33 +1,34 @@
#pragma once
#include <hyprutils/animation/AnimationConfig.hpp>
#define CONFIG_MANAGER_H
#include <map>
#include "../debug/Log.hpp"
#include <unordered_map>
#include "../defines.hpp"
#include <variant>
#include <vector>
#include <algorithm>
#include <optional>
#include <functional>
#include <xf86drmMode.h>
#include "../helpers/WLClasses.hpp"
#include "../helpers/Monitor.hpp"
#include "../helpers/varlist/VarList.hpp"
#include "../desktop/Window.hpp"
#include "../desktop/LayerSurface.hpp"
#include "../desktop/LayerRule.hpp"
#include "defaultConfig.hpp"
#include "ConfigDataValues.hpp"
#include "../SharedDefs.hpp"
#include "../helpers/Color.hpp"
#include "../desktop/DesktopTypes.hpp"
#include "../helpers/memory/Memory.hpp"
#include "../desktop/WindowRule.hpp"
#include "../managers/XWaylandManager.hpp"
#include <hyprlang.hpp>
#define INITANIMCFG(name) animationConfig[name] = {}
#define CREATEANIMCFG(name, parent) animationConfig[name] = {false, "", "", 0.f, -1, &animationConfig["global"], &animationConfig[parent]}
#define HANDLE void*
class CConfigManager;
struct SWorkspaceRule {
std::string monitor = "";
std::string workspaceString = "";
@@ -37,6 +38,7 @@ struct SWorkspaceRule {
bool isPersistent = false;
std::optional<CCssGapData> gapsIn;
std::optional<CCssGapData> gapsOut;
std::optional<CCssGapData> floatGaps = gapsOut;
std::optional<int64_t> borderSize;
std::optional<bool> decorate;
std::optional<bool> noRounding;
@@ -54,18 +56,6 @@ struct SMonitorAdditionalReservedArea {
int right = 0;
};
struct SAnimationPropertyConfig {
bool overridden = true;
std::string internalBezier = "";
std::string internalStyle = "";
float internalSpeed = 0.f;
int internalEnabled = -1;
SAnimationPropertyConfig* pValues = nullptr;
SAnimationPropertyConfig* pParentAnimation = nullptr;
};
struct SPluginKeyword {
HANDLE handle = nullptr;
std::string name = "";
@@ -146,12 +136,75 @@ struct SConfigOptionDescription {
std::variant<SBoolData, SRangeData, SFloatData, SStringData, SColorData, SChoiceData, SGradientData, SVectorData> data;
};
struct SFirstExecRequest {
std::string exec = "";
bool withRules = false;
};
struct SFloatCache {
size_t hash;
SFloatCache(PHLWINDOW window, bool initial) {
// Base hash from class/title
size_t baseHash = initial ? (std::hash<std::string>{}(window->m_initialClass) ^ (std::hash<std::string>{}(window->m_initialTitle) << 1)) :
(std::hash<std::string>{}(window->m_class) ^ (std::hash<std::string>{}(window->m_title) << 1));
// Use empty string as default tag value
std::string tagValue = "";
if (auto xdgTag = window->xdgTag())
tagValue = xdgTag.value();
// Combine hashes
hash = baseHash ^ (std::hash<std::string>{}(tagValue) << 2);
}
bool operator==(const SFloatCache& other) const {
return hash == other.hash;
}
};
namespace std {
template <>
struct hash<SFloatCache> {
size_t operator()(const SFloatCache& id) const {
return id.hash;
}
};
}
class CMonitorRuleParser {
public:
CMonitorRuleParser(const std::string& name);
const std::string& name();
SMonitorRule& rule();
std::optional<std::string> getError();
bool parseMode(const std::string& value);
bool parsePosition(const std::string& value, bool isFirst = false);
bool parseScale(const std::string& value);
bool parseTransform(const std::string& value);
bool parseBitdepth(const std::string& value);
bool parseCM(const std::string& value);
bool parseSDRBrightness(const std::string& value);
bool parseSDRSaturation(const std::string& value);
bool parseVRR(const std::string& value);
void setDisabled();
void setMirror(const std::string& value);
bool setReserved(const SMonitorAdditionalReservedArea& value);
private:
SMonitorRule m_rule;
std::string m_error = "";
};
class CConfigManager {
public:
CConfigManager();
void tick();
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 = "");
@@ -163,8 +216,7 @@ class CConfigManager {
void* const* getConfigValuePtr(const std::string&);
Hyprlang::CConfigValue* getHyprlangConfigValuePtr(const std::string& name, const std::string& specialCat = "");
void onPluginLoadUnload(const std::string& name, bool load);
static std::string getMainConfigPath();
std::string getMainConfigPath();
std::string getConfigString();
SMonitorRule getMonitorRuleFor(const PHLMONITOR);
@@ -177,12 +229,13 @@ class CConfigManager {
std::vector<SP<CWindowRule>> getMatchingRules(PHLWINDOW, bool dynamic = true, bool shadowExec = false);
std::vector<SP<CLayerRule>> getMatchingRules(PHLLS);
void ensurePersistentWorkspacesPresent();
const std::vector<SConfigOptionDescription>& getAllDescriptions();
std::unordered_map<std::string, SMonitorAdditionalReservedArea> m_mAdditionalReservedAreas;
std::unordered_map<std::string, SAnimationPropertyConfig> getAnimationConfig();
const std::unordered_map<std::string, SP<Hyprutils::Animation::SAnimationPropertyConfig>>& getAnimationConfig();
void addPluginConfigVar(HANDLE handle, const std::string& name, const Hyprlang::CConfigValue& value);
void addPluginKeyword(HANDLE handle, const std::string& name, Hyprlang::PCONFIGHANDLERFUNC fun, Hyprlang::SHandlerOptions opts = {});
@@ -193,18 +246,17 @@ class CConfigManager {
void dispatchExecShutdown();
void performMonitorReload();
void appendMonitorRule(const SMonitorRule&);
bool replaceMonitorRule(const SMonitorRule&);
void ensureMonitorStatus();
void ensureVRR(PHLMONITOR pMonitor = nullptr);
bool shouldUseSoftwareCursors();
bool shouldUseSoftwareCursors(PHLMONITOR pMonitor);
void updateWatcher();
std::string parseKeyword(const std::string&, const std::string&);
void addParseError(const std::string&);
SAnimationPropertyConfig* getAnimationPropertyConfig(const std::string&);
SP<Hyprutils::Animation::SAnimationPropertyConfig> getAnimationPropertyConfig(const std::string&);
void addExecRule(const SExecRequestedRule&);
@@ -213,14 +265,15 @@ class CConfigManager {
// keywords
std::optional<std::string> handleRawExec(const std::string&, const std::string&);
std::optional<std::string> handleExec(const std::string&, const std::string&);
std::optional<std::string> handleExecOnce(const std::string&, const std::string&);
std::optional<std::string> handleExecRawOnce(const std::string&, const std::string&);
std::optional<std::string> handleExecShutdown(const std::string&, const std::string&);
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> handleWindowRuleV2(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&);
@@ -230,88 +283,75 @@ class CConfigManager {
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::string configCurrentPath;
std::optional<std::string> handleMonitorv2(const std::string& output);
Hyprlang::CParseResult handleMonitorv2();
std::unordered_map<std::string, std::function<CWindowOverridableVar<bool>*(const PHLWINDOW&)>> mbWindowProperties = {
{"allowsinput", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.allowsInput; }},
{"dimaround", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.dimAround; }},
{"decorate", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.decorate; }},
{"focusonactivate", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.focusOnActivate; }},
{"keepaspectratio", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.keepAspectRatio; }},
{"nearestneighbor", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.nearestNeighbor; }},
{"noanim", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.noAnim; }},
{"noblur", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.noBlur; }},
{"noborder", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.noBorder; }},
{"nodim", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.noDim; }},
{"nofocus", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.noFocus; }},
{"nomaxsize", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.noMaxSize; }},
{"norounding", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.noRounding; }},
{"noshadow", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.noShadow; }},
{"noshortcutsinhibit", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.noShortcutsInhibit; }},
{"opaque", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.opaque; }},
{"forcergbx", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.RGBX; }},
{"syncfullscreen", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.syncFullscreen; }},
{"immediate", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.tearing; }},
{"xray", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.xray; }},
};
std::string m_configCurrentPath;
std::unordered_map<std::string, std::function<CWindowOverridableVar<int>*(const PHLWINDOW&)>> miWindowProperties = {
{"rounding", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.rounding; }},
{"bordersize", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.borderSize; }},
};
bool m_wantsMonitorReload = false;
bool m_noMonitorReload = false;
bool m_isLaunchingExecOnce = false; // For exec-once to skip initial ws tracking
bool m_lastConfigVerificationWasSuccessful = true;
std::unordered_map<std::string, std::function<CWindowOverridableVar<float>*(PHLWINDOW)>> mfWindowProperties = {
{"scrollmouse", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.scrollMouse; }},
{"scrolltouchpad", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.scrollTouchpad; }}};
bool m_bWantsMonitorReload = false;
bool m_bForceReload = false;
bool m_bNoMonitorReload = false;
bool isLaunchingExecOnce = false; // For exec-once to skip initial ws tracking
void storeFloatingSize(PHLWINDOW window, const Vector2D& size);
std::optional<Vector2D> getStoredFloatingSize(PHLWINDOW window);
private:
std::unique_ptr<Hyprlang::CConfig> m_pConfig;
UP<Hyprlang::CConfig> m_config;
std::vector<std::string> configPaths; // stores all the config paths
std::unordered_map<std::string, time_t> configModifyTimes; // stores modify times
std::vector<std::string> m_configPaths;
std::unordered_map<std::string, SAnimationPropertyConfig> animationConfig; // stores all the animations with their set values
Hyprutils::Animation::CAnimationConfigTree m_animationTree;
std::string m_szCurrentSubmap = ""; // For storing the current keybind submap
std::string m_currentSubmap = ""; // For storing the current keybind submap
std::vector<SExecRequestedRule> execRequestedRules; // rules requested with exec, e.g. [workspace 2] kitty
std::vector<SExecRequestedRule> m_execRequestedRules; // rules requested with exec, e.g. [workspace 2] kitty
std::vector<std::string> m_vDeclaredPlugins;
std::vector<SPluginKeyword> pluginKeywords;
std::vector<SPluginVariable> pluginVariables;
std::vector<std::string> m_declaredPlugins;
std::vector<SPluginKeyword> m_pluginKeywords;
std::vector<SPluginVariable> m_pluginVariables;
bool isFirstLaunch = true; // For exec-once
bool m_isFirstLaunch = true; // For exec-once
std::vector<SMonitorRule> m_vMonitorRules;
std::vector<SWorkspaceRule> m_vWorkspaceRules;
std::vector<SP<CWindowRule>> m_vWindowRules;
std::vector<SP<CLayerRule>> m_vLayerRules;
std::vector<std::string> m_dBlurLSNamespaces;
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 firstExecDispatched = false;
bool m_bManualCrashInitiated = false;
std::vector<std::string> firstExecRequests;
std::vector<std::string> finalExecRequests;
bool m_firstExecDispatched = false;
bool m_manualCrashInitiated = false;
std::vector<std::pair<std::string, std::string>> m_vFailedPluginConfigValues; // for plugin values of unloaded plugins
std::string m_szConfigErrors = "";
std::vector<SFirstExecRequest> m_firstExecRequests; // bool is for if with rules
std::vector<std::string> m_finalExecRequests;
std::vector<std::pair<std::string, std::string>> m_failedPluginConfigValues; // for plugin values of unloaded plugins
std::string m_configErrors = "";
uint32_t m_configValueNumber = 0;
// internal methods
void setAnimForChildren(SAnimationPropertyConfig* const);
void updateBlurredLS(const std::string&, const bool);
void setDefaultAnimationVars();
std::optional<std::string> resetHLConfig();
static std::optional<std::string> generateConfig(std::string configPath);
static std::optional<std::string> verifyConfigExists();
std::optional<std::string> generateConfig(std::string configPath);
std::optional<std::string> verifyConfigExists();
void postConfigReload(const Hyprlang::CParseResult& result);
void reload();
SWorkspaceRule mergeWorkspaceRules(const SWorkspaceRule&, const SWorkspaceRule&);
void registerConfigVar(const char* name, const Hyprlang::INT& val);
void registerConfigVar(const char* name, const Hyprlang::FLOAT& val);
void registerConfigVar(const char* name, const Hyprlang::VEC2& val);
void registerConfigVar(const char* name, const Hyprlang::STRING& val);
void registerConfigVar(const char* name, Hyprlang::CUSTOMTYPE&& val);
std::unordered_map<SFloatCache, Vector2D> m_mStoredFloatingSizes;
friend struct SConfigOptionDescription;
friend class CMonitorRuleParser;
};
inline std::unique_ptr<CConfigManager> g_pConfigManager;
inline UP<CConfigManager> g_pConfigManager;

@@ -0,0 +1,15 @@
#include "ConfigValue.hpp"
#include "ConfigManager.hpp"
#include "../macros.hpp"
void local__configValuePopulate(void* const** p, const std::string& val) {
const auto PVHYPRLANG = g_pConfigManager->getHyprlangConfigValuePtr(val);
*p = PVHYPRLANG->getDataStaticPtr();
}
std::type_index local__configValueTypeIdx(const std::string& val) {
const auto PVHYPRLANG = g_pConfigManager->getHyprlangConfigValuePtr(val);
const auto ANY = PVHYPRLANG->getValue();
return std::type_index(ANY.type());
}

@@ -3,23 +3,20 @@
#include <string>
#include <typeindex>
#include <hyprlang.hpp>
#include "../debug/Log.hpp"
#include "../macros.hpp"
#include "ConfigManager.hpp"
// giga hack to avoid including configManager here
// NOLINTNEXTLINE
void local__configValuePopulate(void* const** p, const std::string& val);
std::type_index local__configValueTypeIdx(const std::string& val);
template <typename T>
class CConfigValue {
public:
CConfigValue(const std::string& val) {
const auto PVHYPRLANG = g_pConfigManager->getHyprlangConfigValuePtr(val);
// NOLINTNEXTLINE
p_ = PVHYPRLANG->getDataStaticPtr();
#ifdef HYPRLAND_DEBUG
// verify type
const auto ANY = PVHYPRLANG->getValue();
const auto TYPE = std::type_index(ANY.type());
const auto TYPE = local__configValueTypeIdx(val);
// exceptions
const bool STRINGEX = (typeid(T) == typeid(std::string) && TYPE == typeid(Hyprlang::STRING));
@@ -27,6 +24,8 @@ class CConfigValue {
RASSERT(typeid(T) == TYPE || STRINGEX || CUSTOMEX, "Mismatched type in CConfigValue<T>, got {} but has {}", typeid(T).name(), TYPE.name());
#endif
local__configValuePopulate(&p_, val);
}
T* ptr() const {

@@ -0,0 +1,81 @@
#include "ConfigWatcher.hpp"
#include <sys/inotify.h>
#include "../debug/Log.hpp"
#include <ranges>
#include <fcntl.h>
#include <unistd.h>
#include <filesystem>
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");
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");
m_inotifyFd.reset();
return;
}
}
CFileDescriptor& CConfigWatcher::getInotifyFD() {
return m_inotifyFd;
}
void CConfigWatcher::setWatchList(const std::vector<std::string>& paths) {
// we clear all watches first, because whichever fired is now invalid
// or that is at least what it seems to be.
// since we don't know which fired,
// plus it doesn't matter that much, these ops are done rarely and fast anyways.
// cleanup old paths
for (auto& watch : m_watches) {
inotify_rm_watch(m_inotifyFd.get(), watch.wd);
}
m_watches.clear();
// add new paths
for (const auto& path : paths) {
m_watches.emplace_back(SInotifyWatch{
.wd = inotify_add_watch(m_inotifyFd.get(), path.c_str(), IN_MODIFY | IN_DONT_FOLLOW),
.file = path,
});
std::error_code ec, ec2;
const auto CANONICAL = std::filesystem::canonical(path, ec);
const auto IS_SYMLINK = std::filesystem::is_symlink(path, ec2);
if (!ec && !ec2 && IS_SYMLINK) {
m_watches.emplace_back(SInotifyWatch{
.wd = inotify_add_watch(m_inotifyFd.get(), CANONICAL.c_str(), IN_MODIFY),
.file = path,
});
}
}
}
void CConfigWatcher::setOnChange(const std::function<void(const SConfigWatchEvent&)>& fn) {
m_watchCallback = fn;
}
void CConfigWatcher::onInotifyEvent() {
inotify_event ev;
while (read(m_inotifyFd.get(), &ev, sizeof(ev)) > 0) {
const auto WD = std::ranges::find_if(m_watches.begin(), m_watches.end(), [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);
return;
}
m_watchCallback(SConfigWatchEvent{
.file = WD->file,
});
}
}

@@ -0,0 +1,33 @@
#pragma once
#include "../helpers/memory/Memory.hpp"
#include <vector>
#include <string>
#include <functional>
#include <hyprutils/os/FileDescriptor.hpp>
class CConfigWatcher {
public:
CConfigWatcher();
~CConfigWatcher() = default;
struct SConfigWatchEvent {
std::string file;
};
Hyprutils::OS::CFileDescriptor& getInotifyFD();
void setWatchList(const std::vector<std::string>& paths);
void setOnChange(const std::function<void(const SConfigWatchEvent&)>& fn);
void onInotifyEvent();
private:
struct SInotifyWatch {
int wd = -1;
std::string file;
};
std::function<void(const SConfigWatchEvent&)> m_watchCallback;
std::vector<SInotifyWatch> m_watches;
Hyprutils::OS::CFileDescriptor m_inotifyFd;
};
inline UP<CConfigWatcher> g_pConfigWatcher = makeUnique<CConfigWatcher>();

@@ -2,301 +2,17 @@
#include <string>
inline const std::string AUTOCONFIG = R"#(
inline constexpr std::string_view AUTOGENERATED_PREFIX = R"#(
# #######################################################################################
# AUTOGENERATED HYPRLAND CONFIG.
# PLEASE USE THE CONFIG PROVIDED IN THE GIT REPO /examples/hyprland.conf AND EDIT IT,
# OR EDIT THIS ONE ACCORDING TO THE WIKI INSTRUCTIONS.
# EDIT THIS CONFIG ACCORDING TO THE WIKI INSTRUCTIONS.
# #######################################################################################
autogenerated = 1 # remove this line to remove the warning
# This is an example Hyprland config file.
# Refer to the wiki for more information.
# https://wiki.hyprland.org/Configuring/
# Please note not all available settings / options are set here.
# For a full list, see the wiki
# You can split this configuration into multiple files
# Create your files separately and then link them to this file like this:
# source = ~/.config/hypr/myColors.conf
################
### MONITORS ###
################
# See https://wiki.hyprland.org/Configuring/Monitors/
monitor=,preferred,auto,auto
###################
### MY PROGRAMS ###
###################
# See https://wiki.hyprland.org/Configuring/Keywords/
# Set programs that you use
$terminal = kitty
$fileManager = dolphin
$menu = wofi --show drun
#################
### AUTOSTART ###
#################
# Autostart necessary processes (like notifications daemons, status bars, etc.)
# Or execute your favorite apps at launch like this:
# exec-once = $terminal
# exec-once = nm-applet &
# exec-once = waybar & hyprpaper & firefox
#############################
### ENVIRONMENT VARIABLES ###
#############################
# See https://wiki.hyprland.org/Configuring/Environment-variables/
env = XCURSOR_SIZE,24
env = HYPRCURSOR_SIZE,24
#####################
### LOOK AND FEEL ###
#####################
# Refer to https://wiki.hyprland.org/Configuring/Variables/
# https://wiki.hyprland.org/Configuring/Variables/#general
general {
gaps_in = 5
gaps_out = 20
border_size = 2
# https://wiki.hyprland.org/Configuring/Variables/#variable-types for info about colors
col.active_border = rgba(33ccffee) rgba(00ff99ee) 45deg
col.inactive_border = rgba(595959aa)
# Set to true enable resizing windows by clicking and dragging on borders and gaps
resize_on_border = false
# Please see https://wiki.hyprland.org/Configuring/Tearing/ before you turn this on
allow_tearing = false
layout = dwindle
}
# https://wiki.hyprland.org/Configuring/Variables/#decoration
decoration {
rounding = 10
# Change transparency of focused and unfocused windows
active_opacity = 1.0
inactive_opacity = 1.0
shadow {
enabled = true
range = 4
render_power = 3
color = rgba(1a1a1aee)
}
# https://wiki.hyprland.org/Configuring/Variables/#blur
blur {
enabled = true
size = 3
passes = 1
vibrancy = 0.1696
}
}
# https://wiki.hyprland.org/Configuring/Variables/#animations
animations {
enabled = yes, please :)
# Default animations, see https://wiki.hyprland.org/Configuring/Animations/ for more
bezier = easeOutQuint,0.23,1,0.32,1
bezier = easeInOutCubic,0.65,0.05,0.36,1
bezier = linear,0,0,1,1
bezier = almostLinear,0.5,0.5,0.75,1.0
bezier = quick,0.15,0,0.1,1
animation = global, 1, 10, default
animation = border, 1, 5.39, easeOutQuint
animation = windows, 1, 4.79, easeOutQuint
animation = windowsIn, 1, 4.1, easeOutQuint, popin 87%
animation = windowsOut, 1, 1.49, linear, popin 87%
animation = fadeIn, 1, 1.73, almostLinear
animation = fadeOut, 1, 1.46, almostLinear
animation = fade, 1, 3.03, quick
animation = layers, 1, 3.81, easeOutQuint
animation = layersIn, 1, 4, easeOutQuint, fade
animation = layersOut, 1, 1.5, linear, fade
animation = fadeLayersIn, 1, 1.79, almostLinear
animation = fadeLayersOut, 1, 1.39, almostLinear
animation = workspaces, 1, 1.94, almostLinear, fade
animation = workspacesIn, 1, 1.21, almostLinear, fade
animation = workspacesOut, 1, 1.94, almostLinear, fade
}
# Ref https://wiki.hyprland.org/Configuring/Workspace-Rules/
# "Smart gaps" / "No gaps when only"
# uncomment all if you wish to use that.
# workspace = w[tv1], gapsout:0, gapsin:0
# workspace = f[1], gapsout:0, gapsin:0
# windowrulev2 = bordersize 0, floating:0, onworkspace:w[tv1]
# windowrulev2 = rounding 0, floating:0, onworkspace:w[tv1]
# windowrulev2 = bordersize 0, floating:0, onworkspace:f[1]
# windowrulev2 = rounding 0, floating:0, onworkspace:f[1]
# See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more
dwindle {
pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
preserve_split = true # You probably want this
}
# See https://wiki.hyprland.org/Configuring/Master-Layout/ for more
master {
new_status = master
}
# https://wiki.hyprland.org/Configuring/Variables/#misc
misc {
force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers
disable_hyprland_logo = false # If true disables the random hyprland logo / anime girl background. :(
}
#############
### INPUT ###
#############
# https://wiki.hyprland.org/Configuring/Variables/#input
input {
kb_layout = us
kb_variant =
kb_model =
kb_options =
kb_rules =
follow_mouse = 1
sensitivity = 0 # -1.0 - 1.0, 0 means no modification.
touchpad {
natural_scroll = false
}
}
# https://wiki.hyprland.org/Configuring/Variables/#gestures
gestures {
workspace_swipe = false
}
# Example per-device config
# See https://wiki.hyprland.org/Configuring/Keywords/#per-device-input-configs for more
device {
name = epic-mouse-v1
sensitivity = -0.5
}
###################
### KEYBINDINGS ###
###################
# See https://wiki.hyprland.org/Configuring/Keywords/
$mainMod = SUPER # Sets "Windows" key as main modifier
# Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more
bind = $mainMod, Q, exec, $terminal
bind = $mainMod, C, killactive,
bind = $mainMod, M, exit,
bind = $mainMod, E, exec, $fileManager
bind = $mainMod, V, togglefloating,
bind = $mainMod, R, exec, $menu
bind = $mainMod, P, pseudo, # dwindle
bind = $mainMod, J, togglesplit, # dwindle
# Move focus with mainMod + arrow keys
bind = $mainMod, left, movefocus, l
bind = $mainMod, right, movefocus, r
bind = $mainMod, up, movefocus, u
bind = $mainMod, down, movefocus, d
# Switch workspaces with mainMod + [0-9]
bind = $mainMod, 1, workspace, 1
bind = $mainMod, 2, workspace, 2
bind = $mainMod, 3, workspace, 3
bind = $mainMod, 4, workspace, 4
bind = $mainMod, 5, workspace, 5
bind = $mainMod, 6, workspace, 6
bind = $mainMod, 7, workspace, 7
bind = $mainMod, 8, workspace, 8
bind = $mainMod, 9, workspace, 9
bind = $mainMod, 0, workspace, 10
# Move active window to a workspace with mainMod + SHIFT + [0-9]
bind = $mainMod SHIFT, 1, movetoworkspace, 1
bind = $mainMod SHIFT, 2, movetoworkspace, 2
bind = $mainMod SHIFT, 3, movetoworkspace, 3
bind = $mainMod SHIFT, 4, movetoworkspace, 4
bind = $mainMod SHIFT, 5, movetoworkspace, 5
bind = $mainMod SHIFT, 6, movetoworkspace, 6
bind = $mainMod SHIFT, 7, movetoworkspace, 7
bind = $mainMod SHIFT, 8, movetoworkspace, 8
bind = $mainMod SHIFT, 9, movetoworkspace, 9
bind = $mainMod SHIFT, 0, movetoworkspace, 10
# Example special workspace (scratchpad)
bind = $mainMod, S, togglespecialworkspace, magic
bind = $mainMod SHIFT, S, movetoworkspace, special:magic
# Scroll through existing workspaces with mainMod + scroll
bind = $mainMod, mouse_down, workspace, e+1
bind = $mainMod, mouse_up, workspace, e-1
# Move/resize windows with mainMod + LMB/RMB and dragging
bindm = $mainMod, mouse:272, movewindow
bindm = $mainMod, mouse:273, resizewindow
# Laptop multimedia keys for volume and LCD brightness
bindel = ,XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+
bindel = ,XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
bindel = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
bindel = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
bindel = ,XF86MonBrightnessUp, exec, brightnessctl s 10%+
bindel = ,XF86MonBrightnessDown, exec, brightnessctl s 10%-
# Requires playerctl
bindl = , XF86AudioNext, exec, playerctl next
bindl = , XF86AudioPause, exec, playerctl play-pause
bindl = , XF86AudioPlay, exec, playerctl play-pause
bindl = , XF86AudioPrev, exec, playerctl previous
##############################
### WINDOWS AND WORKSPACES ###
##############################
# See https://wiki.hyprland.org/Configuring/Window-Rules/ for more
# See https://wiki.hyprland.org/Configuring/Workspace-Rules/ for workspace rules
# Example windowrule v1
# windowrule = float, ^(kitty)$
# Example windowrule v2
# windowrulev2 = float,class:^(kitty)$,title:^(kitty)$
# Ignore maximize requests from apps. You'll probably like this.
windowrulev2 = suppressevent maximize, class:.*
# Fix some dragging issues with XWayland
windowrulev2 = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0
)#";
inline constexpr char EXAMPLE_CONFIG_BYTES[] = {
#embed "../../example/hyprland.conf"
};
inline constexpr std::string_view EXAMPLE_CONFIG = {EXAMPLE_CONFIG_BYTES, sizeof(EXAMPLE_CONFIG_BYTES)};

@@ -6,6 +6,7 @@
#include <cerrno>
#include <sys/stat.h>
#include <filesystem>
#include "../helpers/MiscFunctions.hpp"
#include "../plugins/PluginSystem.hpp"
#include "../signal-safe.hpp"
@@ -108,10 +109,7 @@ void NCrashReporter::createAndSaveCrash(int sig) {
finalCrashReport += "\nDate: ";
finalCrashReport += GIT_COMMIT_DATE;
finalCrashReport += "\nFlags:\n";
#ifdef LEGACY_RENDERER
finalCrashReport += "legacyrenderer\n";
#endif
#ifndef ISDEBUG
#if ISDEBUG
finalCrashReport += "debug\n";
#endif
#ifdef NO_XWAYLAND
@@ -129,11 +127,11 @@ void NCrashReporter::createAndSaveCrash(int sig) {
for (size_t i = 0; i < count; i++) {
auto p = plugins[i];
finalCrashReport += '\t';
finalCrashReport += p->name;
finalCrashReport += p->m_name;
finalCrashReport += " (";
finalCrashReport += p->author;
finalCrashReport += p->m_author;
finalCrashReport += ") ";
finalCrashReport += p->version;
finalCrashReport += p->m_version;
finalCrashReport += '\n';
}
@@ -161,7 +159,7 @@ void NCrashReporter::createAndSaveCrash(int sig) {
#if defined(__DragonFly__) || defined(__FreeBSD__)
finalCrashReport.writeCmdOutput("pciconf -lv | grep -F -A4 vga");
#else
finalCrashReport.writeCmdOutput("lspci -vnn | grep VGA");
finalCrashReport.writeCmdOutput("lspci -vnn | grep -E '(VGA|Display|3D)'");
#endif
finalCrashReport += "\n\nos-release:\n";
@@ -191,7 +189,7 @@ void NCrashReporter::createAndSaveCrash(int sig) {
#endif
};
u_int miblen = sizeof(mib) / sizeof(mib[0]);
char exe[PATH_MAX] = "";
char exe[PATH_MAX] = "/nonexistent";
size_t sz = sizeof(exe);
sysctl(mib, miblen, &exe, &sz, NULL, 0);
const auto FPATH = std::filesystem::canonical(exe);
@@ -241,5 +239,5 @@ void NCrashReporter::createAndSaveCrash(int sig) {
finalCrashReport += "\n\nLog tail:\n";
finalCrashReport += std::string_view(Debug::rollingLog).substr(Debug::rollingLog.find('\n') + 1);
finalCrashReport += std::string_view(Debug::m_rollingLog).substr(Debug::m_rollingLog.find('\n') + 1);
}

File diff suppressed because it is too large Load Diff

@@ -1,9 +1,12 @@
#pragma once
#include "../Compositor.hpp"
#include <fstream>
#include "../helpers/MiscFunctions.hpp"
#include "../helpers/defer/Promise.hpp"
#include "../desktop/Window.hpp"
#include <functional>
#include <sys/types.h>
#include <hyprutils/os/FileDescriptor.hpp>
// exposed for main.cpp
std::string systemInfoRequest(eHyprCtlOutputFormat format, std::string request);
@@ -19,12 +22,14 @@ class CHyprCtl {
void unregisterCommand(const SP<SHyprCtlCommand>& cmd);
std::string getReply(std::string);
int m_iSocketFD = -1;
Hyprutils::OS::CFileDescriptor m_socketFD;
struct {
bool all = false;
bool sysInfoConfig = false;
} m_sCurrentRequestParams;
pid_t pid = 0;
SP<CPromise<std::string>> pendingPromise;
} m_currentRequestParams;
static std::string getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format);
static std::string getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat format);
@@ -33,9 +38,9 @@ class CHyprCtl {
private:
void startHyprCtlSocket();
std::vector<SP<SHyprCtlCommand>> m_vCommands;
std::vector<SP<SHyprCtlCommand>> m_commands;
wl_event_source* m_eventSource = nullptr;
std::string m_socketPath;
};
inline std::unique_ptr<CHyprCtl> g_pHyprCtl;
inline UP<CHyprCtl> g_pHyprCtl;

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