* hyprtester: Minor misc fixes
- Use `std::filesystem::path` instead of `std::string` in some places;
- Use `std::ranges::sort`, as suggested by clang-tidy;
- Reset colors at the end of every log line;
- Give more specific error messages in main.
* hyprtester: Fix a bug in `hyprlandAlive()`
Successful standard library calls are permitted to (and do) modify the
value of `errno` arbitrarily. Checking the value of `errno` only makes
sense when the library function returns an error. So, the correct
condition of a successful signal delivery is that the call to `kill`
returns 0.
Btw, given the possible other error values of `kill(2)`, should we even
consider `errno` at all?
* hyprtester: Restructure main
Split main's `main()` into multiple functions, separate responsibilities
more cleanly.
Add default values for cmd-line options to the help message.
* hyprtester: Remove redundant path canonicalization
* hyprtester: Run unified clean up code before every test
* tests: Use RAII for clients in client tests
Turn clients in client tests into classes. Constructors
initialize, destructors deinitialize.
Not only is it better design when entities (clients) are
modeled as classes, keeping all the methods connected,
this also ensures the tests perform their custom cleanup.
This will be especially important when we introduce early
termination on failure into the tests.
* hyprtester: Rewrite the testing framework!
- Implement a new test definition system used via the `TEST_CASE` and
`SUBTEST` macros.
- Adds `ASSERT*` and reimplements `EXPECT*` macros. The former now
terminate the test immediately. Also add some new macros, such as
`FAIL_TEST`.
- Move existing tests to the new system. For trivial cases, break
existing test functions down to multiple test cases.
- For non-trivial cases and a few other possible improvements, leave
TODOs.
* tests: Use `EXPECT*` instead of `ASSERT*` in some places
`ASSERT*` macros terminate the test as soon as they fail. They are
appropriate to use when, if something fails, the rest of the test
does not make sense and more error messages from failing checks will
just pollute the output.
`EXPECT*` macros mark the test as failed but its execution continues.
It makes sense to use them when error messages from the following checks
in the test may be useful for someone who will be reading the log.
I changed some uses of `ASSERT*` in main tests to `EXPECT*`. But I was
not reading tests very carefully, so maybe I was wrong in some places.
My rule of thumb was:
- Checks that ensure that a kitty opens / a certain set/number of
windows exists are fatal;
- A series of checks, especially on the same string returned by
`Hyprland`, are non-fatal;
- Checks in tests with tightly coupled steps (i.e., do this, check,
do this with the result of the previous step, check, etc) are fatal.
* seat: fix dropped wl_keyboard.enter after stale keyboardFocusResource
CSeatManager::setKeyboardFocus's leave loop was gated on
m_state.keyboardFocusResource (a WP<CWLSeatResource>). That weak
pointer can expire between focus cycles — most reliably via the
explicit `.reset()` in KeybindManager::passKeys for XWayland
targets, but also in practice after certain wl_seat rebind
sequences from toolkit-level churn. When the WP is expired, the
leave loop's `if (m_state.keyboardFocusResource)` is false, the
loop is skipped, and no wl_keyboard.leave is sent for the previous
focus. The old client's CWLKeyboardResource keeps m_currentSurface
pointing at its old surface.
On the next focus back to that surface,
CWLKeyboardResource::sendEnter's per-keyboard dedup fires
(`if (m_currentSurface == surface) return;`) and the enter is
silently dropped. Net result: client believes it's still focused,
compositor routes keys via m_state.keyboardFocusResource which now
points somewhere else, typed keystrokes silently go to a different
window. A mouse drag resyncs because
InputManager::processMouseDownNormal → refocus() →
mouseMoveUnified(refocus=true) → rawWindowFocus(…, foundSurface)
takes a different path that forces the leave/enter pair through.
Reported and reproducible in discussion #14141.
Fix: iterate PROTO::seat->m_keyboards (the global owning list) in
the leave loop and let sendLeave()'s own m_currentSurface gate
decide whether each keyboard needs the event. sendLeave() and the
sendMods(0,…) that precedes it are already gated on m_currentSurface
being set, so iterating every keyboard is a no-op for any keyboard
that wasn't on the previous focus — identical semantics to the old
code in the healthy case, correct in the broken case.
Enter loop unchanged: it's driven by surf->client() which is a
cached wl_client* on the surface resource, stable across cycles.
Verified against the repro in #14141 (two wev instances cycled via
`hyprctl dispatch focuswindow`). Before the fix both windows get
stuck in a "I am focused" state after ~3 cycles. After the fix
leave/enter pair each dispatch cleanly.
* seat: apply same leave-loop fix to setPointerFocus
Identical failure mode to the keyboard case in the previous commit:
setPointerFocus's leave loop is gated on m_state.pointerFocusResource,
which can expire between focus changes. When it does, the leave is
skipped, the previously-focused client's wl_pointer m_currentSurface
stays stuck, and the next sendEnter's per-pointer dedup silently
drops the enter.
sendPointerButton also early-returns on `if (!m_state.pointerFocusResource)`,
so the visible symptom is "a click on a taskbar / dock / menu button
doesn't register after a layer-shell Overlay surface appeared or
disappeared, until the user moves the mouse." Motion re-syncs focus
via InputManager::mouseMoveUnified → setPointerFocus with a fresh
resource, at which point clicks work again.
Fix is the same: iterate PROTO::seat->m_pointers and let sendLeave's
m_currentSurface gate decide. No-op for pointers not on the old
surface.
m_monitor is a WP<CMonitor> (weak_ptr). In onUnmap(), calling
m_monitor->activeWorkspaceID() without .lock() causes SIGABRT when
the monitor has been destroyed before the XWayland unmap event
arrives, e.g. when a window on a special workspace is unmapped.
we do run m_lastScanout.reset(), but m_lastScanout might sometimes not
be set. cache format from before attempting DS, and set back to it on
activation failure.
in ds format (XBGR8888) the swizzle was pretty much being ignored. It
would cause screenshots taken within DS, or apps which use screencopy
like hyprpicker, wayfreeze, and still, to come out with the red and blue
channels flipped.
* opengl: explicitly set stencil mask
set stencil mask to 0xFF to allow writes, when done set it back to 0x00
to become readonly, avoids potential mishaps. also at the end set the
stencil op to GL_KEEP and it will certainly be read only until next time
its changed.
* opengl: use modern glClearBufferfv over glClear
use more modern glClearBufferfv instead, and also clear the entire
buffer if a clearpass has been added but no renderdamage exist.
* input: implement follow_mouse_shrink
Add a new config option `input:follow_mouse_shrink` (INT, 0-300, default 0)
that shrinks inactive window hitboxes by the specified number of pixels for
focus detection purposes. This creates a dead zone in gaps between windows
where moving the cursor will not trigger a focus change.
The shrink only applies to inactive (non-focused) windows and only during
mouse-move focus checks (follow_mouse = 1). Click and scroll interactions
are unaffected — clicking on a window in the shrunk area still works
normally once the window is focused.
Closes#9973
* tests: add integration test for follow_mouse_shrink
* Add test for movewindowgroup
* groups: Fix `movewindoworgroup` moving into group
Fixes `CKeybindManager::moveWindowIntoGroup` to
remove a window from a group before attempting
to add it to another group. Addresses #13843.
But the animation of moving a window from a group
into another group now looks weird: as if the
whole target group is being moved.