Compare commits

...

135 commits

Author SHA1 Message Date
Simon Ser
f9626f7951 build: bump version to 0.2.1
Some checks failed
Continuous integration build / compile (clang, alpine:edge, disabled) (push) Has been cancelled
Continuous integration build / compile (clang, alpine:edge, enabled) (push) Has been cancelled
Continuous integration build / compile (clang, archlinux:base-devel, disabled) (push) Has been cancelled
Continuous integration build / compile (clang, archlinux:base-devel, enabled) (push) Has been cancelled
Continuous integration build / compile (gcc, alpine:edge, disabled) (push) Has been cancelled
Continuous integration build / compile (gcc, alpine:edge, enabled) (push) Has been cancelled
Continuous integration build / compile (gcc, archlinux:base-devel, disabled) (push) Has been cancelled
Continuous integration build / compile (gcc, archlinux:base-devel, enabled) (push) Has been cancelled
Continuous integration build / format (push) Has been cancelled
Continuous integration build / scan-build (push) Has been cancelled
2025-10-01 17:46:45 +02:00
Simon Ser
2e593fe5a8 Fix renderer, allocator and scene memory leaks on exit
Some checks failed
Continuous integration build / compile (clang, alpine:edge, disabled) (push) Has been cancelled
Continuous integration build / compile (clang, alpine:edge, enabled) (push) Has been cancelled
Continuous integration build / compile (clang, archlinux:base-devel, disabled) (push) Has been cancelled
Continuous integration build / compile (clang, archlinux:base-devel, enabled) (push) Has been cancelled
Continuous integration build / compile (gcc, alpine:edge, disabled) (push) Has been cancelled
Continuous integration build / compile (gcc, alpine:edge, enabled) (push) Has been cancelled
Continuous integration build / compile (gcc, archlinux:base-devel, disabled) (push) Has been cancelled
Continuous integration build / compile (gcc, archlinux:base-devel, enabled) (push) Has been cancelled
Continuous integration build / format (push) Has been cancelled
Continuous integration build / scan-build (push) Has been cancelled
2025-07-02 09:09:20 +02:00
Simon Ser
3da3ec0c27 output: use backend commits
Some checks failed
Continuous integration build / compile (clang, alpine:edge, disabled) (push) Has been cancelled
Continuous integration build / compile (clang, alpine:edge, enabled) (push) Has been cancelled
Continuous integration build / compile (clang, archlinux:base-devel, disabled) (push) Has been cancelled
Continuous integration build / compile (clang, archlinux:base-devel, enabled) (push) Has been cancelled
Continuous integration build / compile (gcc, alpine:edge, disabled) (push) Has been cancelled
Continuous integration build / compile (gcc, alpine:edge, enabled) (push) Has been cancelled
Continuous integration build / compile (gcc, archlinux:base-devel, disabled) (push) Has been cancelled
Continuous integration build / compile (gcc, archlinux:base-devel, enabled) (push) Has been cancelled
Continuous integration build / format (push) Has been cancelled
Continuous integration build / scan-build (push) Has been cancelled
Pass the whole new desired state to the backend, so that the
backend can leverage KMS atomic commits.
2025-06-20 19:19:25 +02:00
Simon Ser
c311ee5cdc ci: set --wrap-mode=nodownload
Some checks failed
Continuous integration build / format (push) Has been cancelled
Continuous integration build / compile (clang, alpine:edge, disabled) (push) Has been cancelled
Continuous integration build / compile (clang, alpine:edge, enabled) (push) Has been cancelled
Continuous integration build / compile (clang, archlinux:base-devel, disabled) (push) Has been cancelled
Continuous integration build / compile (clang, archlinux:base-devel, enabled) (push) Has been cancelled
Continuous integration build / compile (gcc, alpine:edge, disabled) (push) Has been cancelled
Continuous integration build / compile (gcc, alpine:edge, enabled) (push) Has been cancelled
Continuous integration build / compile (gcc, archlinux:base-devel, disabled) (push) Has been cancelled
Continuous integration build / compile (gcc, archlinux:base-devel, enabled) (push) Has been cancelled
Continuous integration build / scan-build (push) Has been cancelled
Make sure Meson doesn't automagically download and build
dependencies, so that we use system libraries.
2025-06-12 18:03:53 +02:00
Simon Ser
e8eec54129 ci: install libdisplay-info
This dependency was missing.
2025-06-12 16:04:05 +02:00
Simon Ser
63f186632e xdg_shell: bump to v5
Some checks failed
Continuous integration build / compile (clang, alpine:edge, disabled) (push) Has been cancelled
Continuous integration build / compile (clang, alpine:edge, enabled) (push) Has been cancelled
Continuous integration build / compile (clang, archlinux:base-devel, disabled) (push) Has been cancelled
Continuous integration build / compile (clang, archlinux:base-devel, enabled) (push) Has been cancelled
Continuous integration build / compile (gcc, alpine:edge, disabled) (push) Has been cancelled
Continuous integration build / compile (gcc, alpine:edge, enabled) (push) Has been cancelled
Continuous integration build / compile (gcc, archlinux:base-devel, disabled) (push) Has been cancelled
Continuous integration build / compile (gcc, archlinux:base-devel, enabled) (push) Has been cancelled
Continuous integration build / format (push) Has been cancelled
Continuous integration build / scan-build (push) Has been cancelled
v5 adds the wm_capabilities event. cage only supports fullscreen.
2025-06-03 23:22:53 +02:00
Simon Ser
60d41fb93c xdg_shell: handle xdg_popup reposition signal
We are advertising xdg-shell v4, but forgot to handle this signal.
2025-06-03 23:22:35 +02:00
Simon Ser
119f98f2b8 xdg_shell: rename xdg_toplevel signal handlers
Some checks failed
Continuous integration build / compile (clang, archlinux:base-devel, disabled) (push) Has been cancelled
Continuous integration build / compile (clang, alpine:edge, disabled) (push) Has been cancelled
Continuous integration build / compile (clang, alpine:edge, enabled) (push) Has been cancelled
Continuous integration build / compile (clang, archlinux:base-devel, enabled) (push) Has been cancelled
Continuous integration build / compile (gcc, alpine:edge, disabled) (push) Has been cancelled
Continuous integration build / compile (gcc, alpine:edge, enabled) (push) Has been cancelled
Continuous integration build / compile (gcc, archlinux:base-devel, disabled) (push) Has been cancelled
Continuous integration build / compile (gcc, archlinux:base-devel, enabled) (push) Has been cancelled
Continuous integration build / format (push) Has been cancelled
Continuous integration build / scan-build (push) Has been cancelled
These handlers aren't invoked for all types of xdg_shell surfaces:
they are only invoked for xdg_toplevel, and are not for xdg_popup.
Rename them accordingly.
2025-06-01 15:08:50 +02:00
Simon Ser
9ad44e4f52 Upgrade wlroots to v0.19
Some checks failed
Continuous integration build / compile (clang, alpine:edge, disabled) (push) Has been cancelled
Continuous integration build / compile (clang, alpine:edge, enabled) (push) Has been cancelled
Continuous integration build / compile (clang, archlinux:base-devel, disabled) (push) Has been cancelled
Continuous integration build / compile (clang, archlinux:base-devel, enabled) (push) Has been cancelled
Continuous integration build / scan-build (push) Has been cancelled
Continuous integration build / compile (gcc, alpine:edge, disabled) (push) Has been cancelled
Continuous integration build / compile (gcc, alpine:edge, enabled) (push) Has been cancelled
Continuous integration build / compile (gcc, archlinux:base-devel, disabled) (push) Has been cancelled
Continuous integration build / compile (gcc, archlinux:base-devel, enabled) (push) Has been cancelled
Continuous integration build / format (push) Has been cancelled
2025-05-26 23:09:43 +02:00
Simon Ser
6efb3b5042 cage: remove global server listeners on shutdown 2025-04-15 15:26:03 +02:00
Simon Ser
e21c155bcd seat: destroy keyboard groups on shutdown
These are not destroyed automatically because they are entirely
managed by the compositor.
2025-04-15 15:26:03 +02:00
Simon Ser
6b1ba34a4a output: drop unused wlr_matrix.h include
This is dropped in the next wlroots release.
2025-04-10 17:49:51 +02:00
Horror Proton
360e259ca5 Fix getopt("D") 2024-12-23 14:56:15 +01:00
Simon Ser
19157d3564 Make application arguments optional
If no application is specified, run without starting a child
process. Leave it up to the user to start applications externally
and stop cage with a signal.
2024-11-22 12:09:16 +01:00
Simon Ser
852839e59f Add CLI flag to enable debug logs
For bug reports, it's useful to ask for a debug log. However there's
no way for users to enable debug logs without recompiling. Add a
CLI flag to do so.
2024-11-22 12:03:40 +01:00
Michal Vaniš
0208f565dc
readme: add link to man page 2024-11-03 16:29:19 +01:00
Simon Ser
e128a9f251 build: bump to version 0.2.0 2024-10-06 15:24:22 +02:00
Simon Ser
34de3f7bac ci: drop libwayland C23 workaround 2024-10-02 09:15:44 +02:00
Simon Ser
f0651c7671 xdg_shell: wait for initial commit to unconstrain popups 2024-09-07 23:42:24 +02:00
Simon Ser
412c11ea91 cage: fix abort on shutdown
Workaround for [1]: register a listener for wl_display destroy and
avoid calling wl_display_terminate() after.

[1]: https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/421
2024-09-07 23:42:24 +02:00
Simon Ser
1abf7e5a4b xdg_shell: wait for initial commit to set xdg-decoration mode
Sending the xdg-decoration mode when the xdg_toplevel_decoration
object is created is incorrect: we need to wait for the initial
commit.
2024-09-07 23:42:24 +02:00
Simon Ser
fa90174607 xdg_shell: configure surface on initial commit
Instead of waiting for the surface to be mapped before sending a
configure event, do it on initial commit.

Note, wlroots 0.18 no longer sends automatic configure events on
initial commit.
2024-09-07 23:42:24 +02:00
Simon Ser
17af2f7be9 ci: workaround libwayland C23 bug 2024-09-07 23:42:24 +02:00
Simon Ser
a81cc2a8e4 seat: rename WLR_INPUT_DEVICE_TABLET_TOOL to WLR_INPUT_DEVICE_TABLET 2024-09-07 23:42:24 +02:00
Simon Ser
874a4a1e79 xdg_shell: use new lifecycle events 2024-09-07 23:42:24 +02:00
Simon Ser
7cc49d2292 cage: drop wlr_scene_set_presentation()
Scene-graph will do the right thing without this call.
2024-09-07 23:42:24 +02:00
Simon Ser
b9add8b729 cage: create backend with wl_event_loop and output layout with wl_display 2024-09-07 23:42:24 +02:00
Simon Ser
47ea6a5d68 seat: pass relative direction to wlr_seat_pointer_notify_axis() 2024-09-07 23:42:24 +02:00
Simon Ser
8714a82c24 Bump wlroots requirement to v0.18 2024-09-07 23:42:24 +02:00
Simon Ser
eaeab71ffa cage: fix SIGINT/SIGTERM handler data
server.wl_display is already a pointer.

Reported-by: Jonathan GUILLOT <jonathan@joggee.fr>
2024-08-28 12:03:10 +02:00
Simon Ser
69c5eccc50 ci: turn on --fatal-meson-warnings
Catches mistakes such as non-existing build options.
2024-08-26 18:10:26 +02:00
Jonathan GUILLOT
df508d65e7 readme: update meson commands 2024-08-22 08:35:58 +02:00
Jonathan GUILLOT
018d5bc4b9 readme: update wlroots dependency version 2024-08-22 08:35:58 +02:00
Simon Ser
d3fb99d665 build: drop xwayland option
This is unnecessary because Xwayland logic doesn't bring in new
dependencies. We can just compile in our Xwayland-related code
when wlroots has been built with Xwayland support.

See this Sway patch: https://github.com/swaywm/sway/pull/8165
2024-07-30 00:42:32 +02:00
Simon Ser
e7d8780f46 Add support for primary selection 2024-02-26 16:41:17 +01:00
Simon Ser
9d43282fa0 Add .editorconfig
Allows text editors to display files with the correct tab width.
2024-02-23 12:14:39 +01:00
Simon Ser
d07afac4ae output: fix assert when re-adding output to layout
wlr_scene_output_layout_add_output() aborts when called with an
already-added output.

To reproduce, run wlr-randr to reconfigure one of the enabled
outputs.
2024-02-23 12:14:00 +01:00
Simon Ser
767ccf9bbd output: use wlr_output_head_v1_state_apply()
No need to hand-roll this code, there is a wlroots helper for it.
Additionally, this fixes missing adaptive sync handling.
2024-02-20 11:01:41 +01:00
Supreeeme
b6f8f92585 xwayland: fix double wl_list_remove
When destroying an xwayland surface, the dissociate and destroy handlers
are called, but both of these were removing the map and unmap signal
handlers, causing a segfault when the destroy handler went to remove
them.
Fixes #309
2024-02-12 11:57:38 +01:00
Kenny Levinsen
8a009212bc output: Transition to wlr_output_state
We previously used the wlr_output's built-in pending state and
wlr_output_rollback. The modern state API is much nicer.
2024-01-26 23:45:57 +01:00
Kenny Levinsen
f0bc13bef7 output: Add scene output layout helper manually 2024-01-26 23:45:57 +01:00
Kenny Levinsen
63c0887664 output: Implement request_state event listener
wlroots backends no longer change state on their own, and instead send a
request_state event. Monitor this event and apply any state we receive.
2024-01-26 23:45:57 +01:00
Kenny Levinsen
c9d2f3afac meson: Bump minimum wlroots to 0.17 2024-01-26 23:45:57 +01:00
Kenny Levinsen
b40be06da2 ci: Upgrade wlroots to 0.17 2024-01-26 23:45:57 +01:00
Kenny Levinsen
67def26d83 ci: Fix Alpine xwayland dependency 2024-01-26 23:45:57 +01:00
Kenny Levinsen
c801544d61 meson: Convert xwayland option to feature type
wlroots as a subproject now yields the xwayland meson option to its
parent. We need to match the type for this to work. This also adds
support for auto mode, where xwayland is used if present but no warning
is given otherwise.
2024-01-26 23:45:57 +01:00
Kenny Levinsen
1ff7945ba1 Switch from wlr_idle to wlr_idle_notify_v1
The latter implemented the KDE protocol which has been dropped.
2024-01-26 23:45:57 +01:00
Kenny Levinsen
a30f2bcec1 Use new wlr_compositor_create signature 2024-01-26 23:45:57 +01:00
Kenny Levinsen
b55e40ad9d seat: New cursor/xcursor interface 2024-01-26 23:45:57 +01:00
Kenny Levinsen
2d4b7a4e23 shell: Use new map/unmap events
For xwayland we must listen on associate/dissociate to set up and tear
down the map/unmap event handlers instead of during surface
create/destroy.
2024-01-26 23:45:57 +01:00
Kenny Levinsen
4ea6a8b1a7 shell: Use new try_from surface getters 2024-01-26 23:45:57 +01:00
Kenny Levinsen
8df120dafd output: Use state field in wlr_output_event_commit 2024-01-26 23:45:57 +01:00
Kenny Levinsen
2f7ab094d4 output: Remove output mode handler
This is replaced by monitoring for WLR_OUTPUT_STATE_MODE on commit.
2024-01-26 23:45:57 +01:00
Kenny Levinsen
b51a6e950f output: wlr_scene_output_commit NULL options 2024-01-26 23:45:57 +01:00
Kenny Levinsen
b772a00df8 output: Call wlr_scene_output_create 2024-01-26 23:45:57 +01:00
Kenny Levinsen
64e2a44124 output: Remove wlr_output_damage include 2024-01-26 23:45:57 +01:00
Kenny Levinsen
624355485a Use new wlr_backend_autocreate signature
The session is now stored explicitly, with wlr_backend_get_session being
dropped.
2024-01-26 23:45:57 +01:00
Simon Ser
34eb3ec2c8 Make Xwayland optional at runtime
Closes: https://github.com/cage-kiosk/cage/issues/293
2023-12-11 17:25:08 +01:00
Moon Sungjoon
1f3e3043dd CI: Set `fail-fast' to false
This allows other jobs to continue running even if one job fails
2023-11-22 15:21:03 +01:00
Jonathan GUILLOT
d40dd3bd99 cage: remove not really used variables in server initialization 2023-11-10 14:38:28 +01:00
Jonathan GUILLOT
2ab480910e cage: add relative-pointer-unstable-v1 support 2023-11-10 14:38:28 +01:00
Jonathan GUILLOT
efbf7c035b seat: move initial cursor center to dedicated function 2023-11-10 14:38:28 +01:00
Jonathan GUILLOT
4dc3cf80b2 output: fix crash when re-enabling DRM output
The output is disabled then re-enabled using the wlr-output-management
protocol with tool such as wlr-randr.

$ wlr-randr --output HDMI-A-1 --off
$ wlr-randr --output HDMI-A-1 --on

When re-enabled the new output configuration is committed before the
output to be added to the global output layout. However,
handle_output_commit() expects the output to be already part of this
global layout and assert failed. Now, do not treat this in
handle_output_commit() and simply ensure the resulting scene output is
not NULL in handle_output_frame().
2023-09-20 00:01:35 +09:00
Jonathan GUILLOT
a769943447 output: ensure output is marked as enabled before trying to set mode
Otherwise, testing to set preferred / "best" mode will always return an
error. Consequently, output will simply be configured with last and
probably "worst" mode.
2023-09-01 18:37:53 +09:00
Jonathan GUILLOT
96ffaa340e output: do not always terminate when last output is destroyed
Only terminate if the last output was nested under the Wayland or X11
backend. If not, using DRM backend for example, terminating Cage when
unplugging the last monitor or simply turning it off does not seem to be
the right behavior.
2023-09-01 18:37:53 +09:00
Jonathan GUILLOT
7ec7e3df2b seat: add missing touch_frame handler
wl_touch::frame() is expected to be sent to client to indicate end of
touch frame event and not sending it may cause issues.
For example, Qt applications using Qt Wayland platform plugin do not
consider touch events until this end of frame to be received.
2023-08-24 10:05:12 +02:00
Jonathan GUILLOT
121e3ac8b2 cage: return exit code of primary client
Some applications indicate different shutdown conditions by returning
specific exit codes. One of these is e.g. Kodi, which returns 64 in case
the user chose "Power off" and 66 in case the user chose "Reboot".
In order to act on these exit codes, it thus makes sense in some
situations to pass them on from the primary client to the caller of
Cage.

This exit code is only returned if the primary client is the cause of
Cage terminating.

Co-authored-by: Patrick Steinhardt <ps@pks.im>
2023-08-21 14:40:05 +02:00
Jan Beich
9a4310f8b6 Chase HTTP redirect 2023-08-14 09:18:22 +02:00
Jente Hidskes Ankarberg
d519b5b529 Establish chain of trust for signed releases 2023-07-29 10:27:05 +02:00
Simon Ser
ea95a8af72 readme: update PGP key ID 2023-07-29 01:08:56 +02:00
Simon Ser
83ffc574be build: bump version to 0.1.5 2023-07-28 16:46:37 +02:00
Simon Ser
794803adc9 build: fix default xwayland option type 2023-07-28 16:42:58 +02:00
Simon Ser
8dcc901522 Log execvp() failures
Closes: https://github.com/cage-kiosk/cage/issues/255
2023-07-28 16:41:03 +02:00
Jonathan GUILLOT
b1129ca72e output: fix segfault when using -m last option
Check output list length before accessing previous or next item to avoid
segfault.
2023-07-28 15:36:34 +02:00
Jonathan GUILLOT
6f78d9d5b2 cage: add basic wlr-output-management support
Co-authored-by: Dima Krasner <dima@dimakrasner.com>
2023-07-28 15:36:34 +02:00
Jente Hidskes
27b6971b36 cage: remove support for rotating outputs
This is now handled through wlr_output_management
2023-07-28 15:36:34 +02:00
ChemicalXandco
bd5b20e1fa add wlr_virtual_keyboard_manager_v1 and wlr_virtual_pointer_manager_v1 2023-02-01 00:26:37 +01:00
Simon Ser
46f0ec1b72 readme: drop CI badge
cage currently uses GitHub CI, not SourceHut.
2023-01-03 22:30:54 +01:00
Alexander Orzechowski
5f4c43db04 CI: bump to wlroots 0.16 2022-12-17 21:56:03 +01:00
Alexander Orzechowski
a8e4e3d131 Enable single-pixel-buffer
Why not.
2022-12-17 21:56:03 +01:00
Alexander Orzechowski
491d508d75 meson: Bump wlroots version 2022-12-17 21:56:03 +01:00
Alexander Orzechowski
36c9c52d3f Port 0.16 surface header includes 2022-12-17 21:56:03 +01:00
Alexander Orzechowski
3015e0368b Give xdg_shell a version 2022-12-17 21:56:03 +01:00
Alexander Orzechowski
32c44ddb5f Port 0.16 scene changes 2022-12-17 21:56:03 +01:00
Alexander Orzechowski
979ff58a24 Port 0.16 wlr_output_layout_get_box changes
Fixes memory leaks.
2022-12-17 21:56:03 +01:00
Alexander Orzechowski
5818a32a99 xdg_shell: Port 0.16 configure changes 2022-12-17 21:56:03 +01:00
Alexander Orzechowski
ea14819153 xdg_shell: Port to 0.16 double buffered geometry 2022-12-17 21:56:03 +01:00
Alexander Orzechowski
b43c7adcd9 xdg_shell: Port 0.16 xdg_toplevel changes 2022-12-17 21:56:03 +01:00
Alexander Orzechowski
47f5daba20 seat: Port 0.16 device changes 2022-12-17 21:56:03 +01:00
Phil Jones
a57e0067c1 Explicitly create wlr_subcompositor.
Following wlroots b6f43ab2, the subcompositor must now be created
explicitly, rather than being handled by wlr_compositor_create.
2022-12-17 21:56:03 +01:00
Jan Beich
d3d4a129e7 cage: allow running as root when not using setuid bit 2022-12-14 11:46:48 +01:00
Ethan D. Twardy
a81ab701d7 Update meson.build not to fail if git rev-parse fails 2022-08-19 11:59:41 +02:00
Ethan D. Twardy
1b9d750dc2 Fix warnings in `meson build' output for deprecated functions.
Previously, the output of `meson build` would warn about the use of
some functions that have been deprecated since meson v0.56.0, and
the use of the `check` kwarg in the `run_command()` function.
Update meson.build to fix these build warnings.
2022-08-19 11:59:41 +02:00
Leonardo Hernández Hernández
46778e6d6c output: get scene_output by wlroots function 2022-03-22 16:58:24 +01:00
Jente Hidskes
f71844ab54 xdg_shell: add note why we're setting the size for fullscreen clients 2022-01-14 14:59:13 +01:00
Jente Hidskes
38f8566155 xdg_shell: check role in is_primary 2022-01-14 14:59:13 +01:00
Jente Hidskes
ee5410d3fb util: remove unneeded module 2022-01-14 14:59:13 +01:00
Jente Hidskes
d708a73aca view: require non-NULL surface in view_from_wlr_surface 2022-01-14 14:59:13 +01:00
Jente Hidskes
ba9c245393 view: implement view_from_wlr_surface in terms of wlr_surface->data pointer 2022-01-14 14:59:13 +01:00
Jente Hidskes
1ccbacf198 output: set pointer to cg_output in wlr_output->data
This will be used later in the layer shell implementation.
2022-01-14 14:59:13 +01:00
Dima Krasner
3321daef98 fall back to a lower output mode if needed (swaywm/sway@4cdc4ac) 2021-12-22 07:36:43 +01:00
Dima Krasner
f44ba0cea2 use find_library() for libm 2021-12-22 07:19:26 +01:00
Dima Krasner
0e87c2c8d8 restore direct libm dependency (fixes #222) 2021-12-22 07:09:09 +01:00
lunacb
de03250ca0 fixed typo 2021-12-21 21:56:56 +01:00
Jente Hidskes
dfba2d8f9d Remove no longer needed dependencies 2021-12-21 21:53:28 +01:00
Simon Ser
7fe306a79a Enable presentation-time
We get this for free with wlr_scene.
2021-12-21 21:53:28 +01:00
Simon Ser
36309d0c07 Enable viewporter
We get this for free with wlr_scene.
2021-12-21 21:53:28 +01:00
Simon Ser
77a3f6bb0c Use wlr_scene_output_send_frame_done 2021-12-21 21:53:28 +01:00
Simon Ser
7a7325c0ee Drop cg_view_child
This is now unused.
2021-12-21 21:53:28 +01:00
Simon Ser
79950cd691 Use wlr_scene_xdg_surface_create for popups
See https://github.com/swaywm/wlroots/pull/3298
2021-12-21 21:53:28 +01:00
Simon Ser
af610192f4 Use wlr_scene_output_for_each_surface 2021-12-21 21:53:28 +01:00
Simon Ser
5a1933f660 Delegate direct scan-out to wlr_scene
Depends on [1].

[1]: https://github.com/swaywm/wlroots/pull/3279
2021-12-21 21:53:28 +01:00
Simon Ser
e6f89ed6f1 Remove output_for_each_surface
It's used only by send_frame_done. Instead of keeping a generic
iterator, let's inline it in the only user.
2021-12-21 21:53:28 +01:00
Simon Ser
48b31afb5a Drop unused output_surface_for_each_surface 2021-12-21 21:53:28 +01:00
Simon Ser
0736d72e88 Use wlr_scene_attach_output_layout
This provides wlr_output_layout integration.

References: https://github.com/swaywm/wlroots/pull/3160
2021-12-21 21:53:28 +01:00
Simon Ser
d46e8a82dd Use scene-graph for damage tracking
References: https://github.com/swaywm/wlroots/pull/3117
2021-12-21 21:53:28 +01:00
Simon Ser
128fa90ea1 Use scene-graph helpers for sub-surfaces
Delegate sub-surface handling to wlr_scene_surface_tree_create.

References: https://github.com/swaywm/wlroots/pull/3128
2021-12-21 21:53:28 +01:00
Simon Ser
fb3dc58237 Replace view_wlr_surface_at with scene-graph 2021-12-21 21:53:28 +01:00
Simon Ser
6d60c6c464 Drop view_for_each_surface and view_for_each_popup_surface
With the scene-graph, these are no longer used.
2021-12-21 21:53:28 +01:00
Simon Ser
f544483340 Use the wlroots scene-graph API
References: https://github.com/swaywm/wlroots/pull/1966
2021-12-21 21:53:28 +01:00
Simon Ser
395189fb05 Upgrade to wlroots 0.15
- Update wlr_box includes to util/box.h: the wlroots header has
  been moved upstream.
- Subsurface fields have been moved
- Create renderer and allocator, stop using wlr_backend_get_renderer
- Initalize output rendering
2021-12-21 21:53:28 +01:00
Jente Hidskes
388d60d6b8 Tune compiler options 2021-12-19 11:12:19 +01:00
Jente Hidskes
8385b62a9b Move to GitHub actions 2021-12-19 11:12:19 +01:00
Palanix
d1367b1c46 removed focus_changed logic 2021-09-06 16:09:14 +02:00
Palanix
0ba1c40aa6 fix drag 2021-09-06 16:09:14 +02:00
Lorenz Brun
60492959a7 Set xdg_toplevel geometry for fullscreen clients 2021-09-06 15:46:09 +02:00
Simon Ser
b21f9c6322 Fix count_surface_iterator
This increments the pointer value, not the actual count.

References: https://github.com/Hjdskes/cage/issues/176
2021-08-11 12:54:49 +02:00
Simon Ser
1a3ab3eb3a build: allow using subproject for wlroots
This is handy when testing cage with a newer wlroots copy, or
when developing cage and wlroots patches in parallel.
2021-08-09 20:54:11 +02:00
Simon Ser
5424a35aba Damage drag icon when destroyed
This fixes the drag icon not disappearing when releasing the
pointer button.

Reproduction steps:

- Open gedit
- Drag some text around
- Release the pointer button
2021-08-09 20:53:24 +02:00
Simon Ser
cdb1cdf3b4 Don't manually parse XKB_* env vars
libxkbcommon will do it for us if we provide a NULL struct
xkb_rule_names.
2021-08-09 20:52:51 +02:00
Jente Hidskes
646b3e80b4 Update version to 0.1.4 2021-06-26 09:39:40 +02:00
Jan Beich
08e58c2b45 CI: bump wlroots version to 0.14.0 2021-06-26 09:25:36 +02:00
Jan Beich
636185bf9c meson: require wlroots 0.14.0 after 9a4523d47e
view.c:238:52: error: no member named 'subsurfaces_below' in 'struct wlr_surface'
        wl_list_for_each (subsurface, &view->wlr_surface->subsurfaces_below, parent_link) {
                                       ~~~~~~~~~~~~~~~~~  ^
/usr/include/wayland-util.h:443:30: note: expanded from macro 'wl_list_for_each'
        for (pos = wl_container_of((head)->next, pos, member);  \
                                    ^~~~
/usr/include/wayland-util.h:409:32: note: expanded from macro 'wl_container_of'
        (__typeof__(sample))((char *)(ptr) -                            \
                                      ^~~
2021-06-26 09:25:36 +02:00
Jan Beich
851268bedc view: chase swaywm/wlroots@9e58301df7
view.c:238:52: error: no member named 'subsurfaces' in 'struct wlr_surface'
        wl_list_for_each (subsurface, &view->wlr_surface->subsurfaces, parent_link) {
                                       ~~~~~~~~~~~~~~~~~  ^
/usr/include/wayland-util.h:443:30: note: expanded from macro 'wl_list_for_each'
        for (pos = wl_container_of((head)->next, pos, member);  \
                                    ^~~~
/usr/include/wayland-util.h:409:32: note: expanded from macro 'wl_container_of'
        (__typeof__(sample))((char *)(ptr) -                            \
                                      ^~~

Based on 3162766eef
2021-06-26 09:25:36 +02:00
Jente Hidskes
efaf76e9ab Include release scripts 2021-04-16 14:34:57 +02:00
31 changed files with 1273 additions and 1489 deletions

View file

@ -1,35 +0,0 @@
image: alpine/edge
packages:
- eudev-dev
- mesa-dev
- meson
- libinput-dev
- libxkbcommon-dev
- pixman-dev
- scdoc
- wayland-dev
- wayland-protocols
- xcb-util-wm-dev
- xwayland
sources:
- https://github.com/swaywm/wlroots
- https://github.com/Hjdskes/cage
tasks:
# Install wlroots, which is required by Cage. Note that we compile a tagged
# version, instead of master, to avoid any breaking changes in wlroots.
- wlroots: |
cd wlroots
git checkout 0.13.0
meson --prefix=/usr build -Dexamples=false
ninja -C build
sudo ninja -C build install
- build: |
cd cage
meson build --werror -Dxwayland=true
ninja -C build
rm -rf build
- build-no-xwayland: |
cd cage
meson build --werror -Dxwayland=false
ninja -C build
rm -rf build

View file

@ -1,45 +0,0 @@
image: archlinux
packages:
- clang
- meson
- libinput
- libxkbcommon
- mesa
- scdoc
- wayland
- wayland-protocols
- xcb-util-wm
- xorg-xwayland
sources:
- https://github.com/swaywm/wlroots
- https://github.com/Hjdskes/cage
tasks:
# Install wlroots, which is required by Cage. Note that we compile a tagged
# version, instead of master, to avoid any breaking changes in wlroots.
- wlroots: |
cd wlroots
git checkout 0.13.0
meson --prefix=/usr build -Dexamples=false
ninja -C build
sudo ninja -C build install
- build: |
cd cage
meson build --werror -Dxwayland=true
ninja -C build
rm -rf build
- build-no-xwayland: |
cd cage
meson build --werror -Dxwayland=false
ninja -C build
rm -rf build
- scan-build: |
cd cage
CC=clang meson build --werror -Dxwayland=true
CC=clang ninja -C build scan-build
rm -rf build
- clang-format: |
cd cage
meson build --werror -Dxwayland=true
ninja -C build clang-format
rm -rf build
git diff --exit-code

View file

@ -1,37 +0,0 @@
image: freebsd/latest
packages:
- devel/evdev-proto
- devel/meson
- devel/libepoll-shim
- devel/pkgconf
- graphics/mesa-libs
- graphics/wayland
- graphics/wayland-protocols
- textproc/scdoc
- x11/libinput
- x11/libxkbcommon
- x11/pixman
- x11/xcb-util-wm
- x11-servers/xwayland
sources:
- https://github.com/swaywm/wlroots
- https://github.com/Hjdskes/cage
tasks:
# Install wlroots, which is required by Cage. Note that we compile a tagged
# version, instead of master, to avoid any breaking changes in wlroots.
- wlroots: |
cd wlroots
git checkout 0.13.0
meson --prefix=/usr/local build -Dexamples=false
ninja -C build
sudo ninja -C build install
- build: |
cd cage
PKG_CONFIG_PATH=/usr/local/lib/pkgconfig meson build --werror -Dxwayland=true
PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ninja -C build
rm -rf build
- build-no-xwayland: |
cd cage
PKG_CONFIG_PATH=/usr/local/lib/pkgconfig meson build --werror -Dxwayland=false
PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ninja -C build
rm -rf build

1
.clang-format-ignore Normal file
View file

@ -0,0 +1 @@
subprojects/**/*

10
.editorconfig Normal file
View file

@ -0,0 +1,10 @@
root = true
[*]
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = tab
indent_size = 8
max_line_length = 120

81
.github/workflows/main.yml vendored Normal file
View file

@ -0,0 +1,81 @@
name: Continuous integration build
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
env:
WLROOTS_VERSION: 0.19
jobs:
compile:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
CC: [ gcc, clang ]
OS: [ "alpine:edge", "archlinux:base-devel" ]
xwayland: [ enabled, disabled ]
container: ${{ matrix.OS }}
env:
CC: ${{ matrix.CC }}
steps:
- name: Checkout Cage
uses: actions/checkout@v2
- name: Install dependencies (Alpine)
if: "matrix.OS == 'alpine:edge'"
run: apk add build-base xcb-util-wm-dev libseat-dev clang git eudev-dev mesa-dev libdrm-dev libinput-dev libxkbcommon-dev pixman-dev wayland-dev meson wayland-protocols xwayland-dev scdoc-doc hwdata libdisplay-info-dev
- name: Install dependencies (Arch)
if: "matrix.OS == 'archlinux:base-devel'"
run: |
pacman-key --init
pacman -Syu --noconfirm xcb-util-wm seatd git clang meson libinput libdrm mesa libxkbcommon wayland wayland-protocols xorg-server-xwayland scdoc libdisplay-info
- name: Fetch wlroots as a subproject
run: git clone https://gitlab.freedesktop.org/wlroots/wlroots.git subprojects/wlroots -b $WLROOTS_VERSION
- name: Compile Cage (XWayland=${{ matrix.xwayland }})
run: |
meson --fatal-meson-warnings --wrap-mode=nodownload \
build-${{ matrix.CC }}-${{matrix.xwayland }} \
-Dwlroots:xwayland=${{ matrix.xwayland }}
ninja -C build-${{ matrix.CC }}-${{matrix.xwayland }}
format:
runs-on: ubuntu-latest
container: "archlinux:base-devel"
steps:
- name: Checkout Cage
uses: actions/checkout@v2
- name: Install dependencies
run: |
pacman-key --init
pacman -Syu --noconfirm xcb-util-wm seatd git clang meson libinput libdrm mesa libxkbcommon wayland wayland-protocols xorg-server-xwayland scdoc hwdata libdisplay-info
- name: Fetch wlroots as a subproject
run: git clone https://gitlab.freedesktop.org/wlroots/wlroots.git subprojects/wlroots -b $WLROOTS_VERSION
- name: Check for formatting changes
run: |
meson --wrap-mode=nodownload build-clang-format -Dwlroots:xwayland=enabled
ninja -C build-clang-format clang-format-check
scan-build:
runs-on: ubuntu-latest
container: "archlinux:base-devel"
env:
CC: clang
steps:
- name: Checkout Cage
uses: actions/checkout@v2
- name: Install dependencies
run: |
pacman-key --init
pacman -Syu --noconfirm xcb-util-wm seatd git clang meson libinput libdrm mesa libxkbcommon wayland wayland-protocols xorg-server-xwayland scdoc hwdata libdisplay-info
- name: Fetch wlroots as a subproject
run: git clone https://gitlab.freedesktop.org/wlroots/wlroots.git subprojects/wlroots -b $WLROOTS_VERSION
- name: Run scan-build
run: |
meson --wrap-mode=nodownload build-scan-build -Dwlroots:xwayland=enabled
ninja -C build-scan-build scan-build

View file

@ -1,4 +1,4 @@
# Cage: a Wayland kiosk [![builds.sr.ht status](https://builds.sr.ht/~hjdskes.svg)](https://builds.sr.ht/~hjdskes?)
# Cage: a Wayland kiosk
<img src="https://www.hjdskes.nl/img/projects/cage/cage.svg" alt="Cage's logo" width="150px" align="right">
@ -8,57 +8,54 @@ application.
This README is only relevant for development resources and instructions. For a
description of Cage and installation instructions for end-users, please see
[its project page](https://www.hjdskes.nl/projects/cage) and [the
Wiki](https://github.com/Hjdskes/cage/wiki/).
Wiki](https://github.com/cage-kiosk/cage/wiki/).
See [the man page](./cage.1.scd) for a list of possible environment variables and run options.
## Release signatures
Releases are signed with
[6EBC43B1](http://keys.gnupg.net/pks/lookup?op=vindex&fingerprint=on&search=0x37C445296EBC43B1)
and published on [GitHub](https://github.com/Hjdskes/cage/releases).
Releases up to version 0.1.4 are signed with [6EBC43B1](http://keys.gnupg.net/pks/lookup?op=vindex&fingerprint=on&search=0x37C445296EBC43B1). Releases from 0.1.5 onwards are signed with
[E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48)
All releases are published on [GitHub](https://github.com/cage-kiosk/cage/releases).
## Building and running Cage
You can build Cage with the [meson](https://mesonbuild.com/) build system. It
requires wayland, wlroots, and xkbcommon to be installed. Optionally, install
scdoc for manual pages. Note that Cage is developed against the latest tag of
wlroots, in order to not constantly chase breaking changes as soon as they
occur.
scdoc for manual pages. Cage is currently based on branch 0.18 of wlroots.
Simply execute the following steps to build Cage:
```
$ meson build
$ ninja -C build
$ meson setup build
$ meson compile -C build
```
By default, this builds a debug build. To build a release build, use `meson
build --buildtype=release`.
setup build --buildtype=release`.
Cage comes with compile-time support for XWayland. To enable this,
first make sure that your version of wlroots is compiled with this
option. Then, add `-Dxwayland=true` to the `meson` command above. Note
that you'll need to have the XWayland binary installed on your system
for this to work.
Cage comes with compile-time support for XWayland. To enable this, make sure
that your version of wlroots is compiled with this option. Note that you'll
need to have the XWayland binary installed on your system for this to work.
You can run Cage by running `./build/cage APPLICATION`. If you run it from
within an existing X11 or Wayland session, it will open in a virtual output as
a window in your existing session. If you run it at a TTY, it'll run with the
KMS+DRM backend. In debug mode (default build type with Meson), press
<kbd>Alt</kbd>+<kbd>Esc</kbd> to quit. For more configuration options, see
[Configuration](https://github.com/Hjdskes/cage/wiki/Configuration).
[Configuration](https://github.com/cage-kiosk/cage/wiki/Configuration).
Cage is based on the annotated source of tinywl and rootston.
## Bugs
For any bug, please [create an
issue](https://github.com/Hjdskes/cage/issues/new) on
[GitHub](https://github.com/Hjdskes/cage).
issue](https://github.com/cage-kiosk/cage/issues/new) on
[GitHub](https://github.com/cage-kiosk/cage).
## License
Please see
[LICENSE](https://github.com/Hjdskes/cage/blob/master/LICENSE) on
[GitHub](https://github.com/Hjdskes/cage).
[LICENSE](https://github.com/cage-kiosk/cage/blob/master/LICENSE) on
[GitHub](https://github.com/cage-kiosk/cage).
Copyright © 2018-2020 Jente Hidskes <dev@hjdskes.nl>

View file

@ -6,7 +6,7 @@ cage - a Wayland kiosk compositor
# SYNOPSIS
*cage* [-dhmrsv] [--] _application_ [application argument ...]
*cage* [options...] [--] [_application_...]
# DESCRIPTION
@ -19,6 +19,9 @@ activities outside the scope of the running application are prevented.
*-d*
Don't draw client side decorations when possible.
*-D*
Enable debug logging.
*-h*
Show the help message.
@ -27,10 +30,6 @@ activities outside the scope of the running application are prevented.
*last* Cage uses only the last connected monitor.
*extend* Cage extends the display across all connected monitors.
*-r*
Rotate the output 90 degrees clockwise. This can be specified up to three
times, each resulting in an additional 90 degrees clockwise rotation.
*-s*
Allow VT switching
@ -65,7 +64,7 @@ _XKB_DEFAULT_VARIANT_, _XKB_DEFAULT_OPTIONS_
# BUGS
Report bugs at https://github.com/Hjdskes/cage
Report bugs at https://github.com/cage-kiosk/cage
# AUTHORS

347
cage.c
View file

@ -19,16 +19,27 @@
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_export_dmabuf_v1.h>
#include <wlr/types/wlr_gamma_control_v1.h>
#include <wlr/types/wlr_idle.h>
#include <wlr/types/wlr_idle_inhibit_v1.h>
#include <wlr/types/wlr_idle_notify_v1.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_output_management_v1.h>
#include <wlr/types/wlr_presentation_time.h>
#include <wlr/types/wlr_primary_selection_v1.h>
#include <wlr/types/wlr_relative_pointer_v1.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_screencopy_v1.h>
#include <wlr/types/wlr_server_decoration.h>
#include <wlr/types/wlr_single_pixel_buffer_v1.h>
#include <wlr/types/wlr_subcompositor.h>
#include <wlr/types/wlr_viewporter.h>
#include <wlr/types/wlr_virtual_keyboard_v1.h>
#include <wlr/types/wlr_virtual_pointer_v1.h>
#if CAGE_HAS_XWAYLAND
#include <wlr/types/wlr_xcursor_manager.h>
#endif
@ -50,10 +61,28 @@
#include "xwayland.h"
#endif
void
server_terminate(struct cg_server *server)
{
// Workaround for https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/421
if (server->terminated) {
return;
}
wl_display_terminate(server->wl_display);
}
static void
handle_display_destroy(struct wl_listener *listener, void *data)
{
struct cg_server *server = wl_container_of(listener, server, display_destroy);
server->terminated = true;
}
static int
sigchld_handler(int fd, uint32_t mask, void *data)
{
struct wl_display *display = data;
struct cg_server *server = data;
/* Close Cage's read pipe. */
close(fd);
@ -64,7 +93,8 @@ sigchld_handler(int fd, uint32_t mask, void *data)
wlr_log(WLR_DEBUG, "Connection closed by server");
}
wl_display_terminate(display);
server->return_app_code = true;
server_terminate(server);
return 0;
}
@ -88,7 +118,7 @@ set_cloexec(int fd)
}
static bool
spawn_primary_client(struct wl_display *display, char *argv[], pid_t *pid_out, struct wl_event_source **sigchld_source)
spawn_primary_client(struct cg_server *server, char *argv[], pid_t *pid_out, struct wl_event_source **sigchld_source)
{
int fd[2];
if (pipe(fd) != 0) {
@ -104,6 +134,8 @@ spawn_primary_client(struct wl_display *display, char *argv[], pid_t *pid_out, s
/* Close read, we only need write in the primary client process. */
close(fd[0]);
execvp(argv[0], argv);
/* execvp() returns only on failure */
wlr_log_errno(WLR_ERROR, "Failed to spawn client");
_exit(1);
} else if (pid == -1) {
wlr_log_errno(WLR_ERROR, "Unable to fork");
@ -120,15 +152,15 @@ spawn_primary_client(struct wl_display *display, char *argv[], pid_t *pid_out, s
/* Close write, we only need read in Cage. */
close(fd[1]);
struct wl_event_loop *event_loop = wl_display_get_event_loop(display);
struct wl_event_loop *event_loop = wl_display_get_event_loop(server->wl_display);
uint32_t mask = WL_EVENT_HANGUP | WL_EVENT_ERROR;
*sigchld_source = wl_event_loop_add_fd(event_loop, fd[0], mask, sigchld_handler, display);
*sigchld_source = wl_event_loop_add_fd(event_loop, fd[0], mask, sigchld_handler, server);
wlr_log(WLR_DEBUG, "Child process created with pid %d", pid);
return true;
}
static void
static int
cleanup_primary_client(pid_t pid)
{
int status;
@ -137,15 +169,25 @@ cleanup_primary_client(pid_t pid)
if (WIFEXITED(status)) {
wlr_log(WLR_DEBUG, "Child exited normally with exit status %d", WEXITSTATUS(status));
return WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
/* Mimic Bash and other shells for the exit status */
wlr_log(WLR_DEBUG, "Child was terminated by a signal (%d)", WTERMSIG(status));
return 128 + WTERMSIG(status);
}
return 0;
}
static bool
drop_permissions(void)
{
if (getuid() == 0 || getgid() == 0) {
wlr_log(WLR_INFO, "Running as root user, this is dangerous");
return true;
}
if (getuid() != geteuid() || getgid() != getegid()) {
wlr_log(WLR_INFO, "setuid/setgid bit detected, dropping permissions");
// Set the gid and uid in the correct order.
if (setgid(getgid()) != 0 || setuid(getuid()) != 0) {
wlr_log(WLR_ERROR, "Unable to drop root, refusing to start");
@ -165,13 +207,13 @@ drop_permissions(void)
static int
handle_signal(int signal, void *data)
{
struct wl_display *display = data;
struct cg_server *server = data;
switch (signal) {
case SIGINT:
/* Fallthrough */
case SIGTERM:
wl_display_terminate(display);
server_terminate(server);
return 0;
default:
return 0;
@ -182,16 +224,13 @@ static void
usage(FILE *file, const char *cage)
{
fprintf(file,
"Usage: %s [OPTIONS] [--] APPLICATION\n"
"Usage: %s [OPTIONS] [--] [APPLICATION...]\n"
"\n"
" -d\t Don't draw client side decorations, when possible\n"
#ifdef DEBUG
" -D\t Turn on damage tracking debugging\n"
#endif
" -D\t Enable debug logging\n"
" -h\t Display this help message\n"
" -m extend Extend the display across all connected outputs (default)\n"
" -m last Use only the last connected output\n"
" -r\t Rotate the output 90 degrees clockwise, specify up to three times\n"
" -s\t Allow VT switching\n"
" -v\t Show the version number and exit\n"
"\n"
@ -203,20 +242,14 @@ static bool
parse_args(struct cg_server *server, int argc, char *argv[])
{
int c;
#ifdef DEBUG
while ((c = getopt(argc, argv, "dDhm:rsv")) != -1) {
#else
while ((c = getopt(argc, argv, "dhm:rsv")) != -1) {
#endif
while ((c = getopt(argc, argv, "dDhm:sv")) != -1) {
switch (c) {
case 'd':
server->xdg_decoration = true;
break;
#ifdef DEBUG
case 'D':
server->debug_damage_tracking = true;
server->log_level = WLR_DEBUG;
break;
#endif
case 'h':
usage(stdout, argv[0]);
return false;
@ -227,12 +260,6 @@ parse_args(struct cg_server *server, int argc, char *argv[])
server->output_mode = CAGE_MULTI_OUTPUT_MODE_EXTEND;
}
break;
case 'r':
server->output_transform++;
if (server->output_transform > WL_OUTPUT_TRANSFORM_270) {
server->output_transform = WL_OUTPUT_TRANSFORM_NORMAL;
}
break;
case 's':
server->allow_vt_switch = true;
break;
@ -245,48 +272,26 @@ parse_args(struct cg_server *server, int argc, char *argv[])
}
}
if (optind >= argc) {
usage(stderr, argv[0]);
return false;
}
return true;
}
int
main(int argc, char *argv[])
{
struct cg_server server = {0};
struct wl_event_loop *event_loop = NULL;
struct wl_event_source *sigint_source = NULL;
struct wl_event_source *sigterm_source = NULL;
struct cg_server server = {.log_level = WLR_INFO};
struct wl_event_source *sigchld_source = NULL;
struct wlr_renderer *renderer = NULL;
struct wlr_compositor *compositor = NULL;
struct wlr_data_device_manager *data_device_manager = NULL;
struct wlr_server_decoration_manager *server_decoration_manager = NULL;
struct wlr_xdg_decoration_manager_v1 *xdg_decoration_manager = NULL;
struct wlr_export_dmabuf_manager_v1 *export_dmabuf_manager = NULL;
struct wlr_screencopy_manager_v1 *screencopy_manager = NULL;
struct wlr_xdg_output_manager_v1 *output_manager = NULL;
struct wlr_gamma_control_manager_v1 *gamma_control_manager = NULL;
struct wlr_xdg_shell *xdg_shell = NULL;
#if CAGE_HAS_XWAYLAND
struct wlr_xwayland *xwayland = NULL;
struct wlr_xcursor_manager *xcursor_manager = NULL;
#endif
pid_t pid = 0;
int ret = 0;
int ret = 0, app_ret = 0;
#ifdef DEBUG
server.log_level = WLR_DEBUG;
#endif
if (!parse_args(&server, argc, argv)) {
return 1;
}
#ifdef DEBUG
wlr_log_init(WLR_DEBUG, NULL);
#else
wlr_log_init(WLR_ERROR, NULL);
#endif
wlr_log_init(server.log_level, NULL);
/* Wayland requires XDG_RUNTIME_DIR to be set. */
if (!getenv("XDG_RUNTIME_DIR")) {
@ -300,11 +305,14 @@ main(int argc, char *argv[])
return 1;
}
event_loop = wl_display_get_event_loop(server.wl_display);
sigint_source = wl_event_loop_add_signal(event_loop, SIGINT, handle_signal, &server.wl_display);
sigterm_source = wl_event_loop_add_signal(event_loop, SIGTERM, handle_signal, &server.wl_display);
server.display_destroy.notify = handle_display_destroy;
wl_display_add_destroy_listener(server.wl_display, &server.display_destroy);
server.backend = wlr_backend_autocreate(server.wl_display);
struct wl_event_loop *event_loop = wl_display_get_event_loop(server.wl_display);
struct wl_event_source *sigint_source = wl_event_loop_add_signal(event_loop, SIGINT, handle_signal, &server);
struct wl_event_source *sigterm_source = wl_event_loop_add_signal(event_loop, SIGTERM, handle_signal, &server);
server.backend = wlr_backend_autocreate(event_loop, &server.session);
if (!server.backend) {
wlr_log(WLR_ERROR, "Unable to create the wlroots backend");
ret = 1;
@ -316,33 +324,68 @@ main(int argc, char *argv[])
goto end;
}
renderer = wlr_backend_get_renderer(server.backend);
wlr_renderer_init_wl_display(renderer, server.wl_display);
server.renderer = wlr_renderer_autocreate(server.backend);
if (!server.renderer) {
wlr_log(WLR_ERROR, "Unable to create the wlroots renderer");
ret = 1;
goto end;
}
server.allocator = wlr_allocator_autocreate(server.backend, server.renderer);
if (!server.allocator) {
wlr_log(WLR_ERROR, "Unable to create the wlroots allocator");
ret = 1;
goto end;
}
wlr_renderer_init_wl_display(server.renderer, server.wl_display);
wl_list_init(&server.views);
wl_list_init(&server.outputs);
server.output_layout = wlr_output_layout_create();
server.output_layout = wlr_output_layout_create(server.wl_display);
if (!server.output_layout) {
wlr_log(WLR_ERROR, "Unable to create output layout");
ret = 1;
goto end;
}
server.output_layout_change.notify = handle_output_layout_change;
wl_signal_add(&server.output_layout->events.change, &server.output_layout_change);
compositor = wlr_compositor_create(server.wl_display, renderer);
server.scene = wlr_scene_create();
if (!server.scene) {
wlr_log(WLR_ERROR, "Unable to create scene");
ret = 1;
goto end;
}
server.scene_output_layout = wlr_scene_attach_output_layout(server.scene, server.output_layout);
struct wlr_compositor *compositor = wlr_compositor_create(server.wl_display, 6, server.renderer);
if (!compositor) {
wlr_log(WLR_ERROR, "Unable to create the wlroots compositor");
ret = 1;
goto end;
}
data_device_manager = wlr_data_device_manager_create(server.wl_display);
if (!data_device_manager) {
if (!wlr_subcompositor_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create the wlroots subcompositor");
ret = 1;
goto end;
}
if (!wlr_data_device_manager_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create the data device manager");
ret = 1;
goto end;
}
if (!wlr_primary_selection_v1_device_manager_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create primary selection device manager");
ret = 1;
goto end;
}
/* Configure a listener to be notified when new outputs are
* available on the backend. We use this only to detect the
* first output and ignore subsequent outputs. */
@ -356,7 +399,7 @@ main(int argc, char *argv[])
goto end;
}
server.idle = wlr_idle_create(server.wl_display);
server.idle = wlr_idle_notifier_v1_create(server.wl_display);
if (!server.idle) {
wlr_log(WLR_ERROR, "Unable to create the idle tracker");
ret = 1;
@ -373,16 +416,19 @@ main(int argc, char *argv[])
wl_signal_add(&server.idle_inhibit_v1->events.new_inhibitor, &server.new_idle_inhibitor_v1);
wl_list_init(&server.inhibitors);
xdg_shell = wlr_xdg_shell_create(server.wl_display);
struct wlr_xdg_shell *xdg_shell = wlr_xdg_shell_create(server.wl_display, 5);
if (!xdg_shell) {
wlr_log(WLR_ERROR, "Unable to create the XDG shell interface");
ret = 1;
goto end;
}
server.new_xdg_shell_surface.notify = handle_xdg_shell_surface_new;
wl_signal_add(&xdg_shell->events.new_surface, &server.new_xdg_shell_surface);
server.new_xdg_toplevel.notify = handle_new_xdg_toplevel;
wl_signal_add(&xdg_shell->events.new_toplevel, &server.new_xdg_toplevel);
server.new_xdg_popup.notify = handle_new_xdg_popup;
wl_signal_add(&xdg_shell->events.new_popup, &server.new_xdg_popup);
xdg_decoration_manager = wlr_xdg_decoration_manager_v1_create(server.wl_display);
struct wlr_xdg_decoration_manager_v1 *xdg_decoration_manager =
wlr_xdg_decoration_manager_v1_create(server.wl_display);
if (!xdg_decoration_manager) {
wlr_log(WLR_ERROR, "Unable to create the XDG decoration manager");
ret = 1;
@ -391,7 +437,8 @@ main(int argc, char *argv[])
wl_signal_add(&xdg_decoration_manager->events.new_toplevel_decoration, &server.xdg_toplevel_decoration);
server.xdg_toplevel_decoration.notify = handle_xdg_toplevel_decoration;
server_decoration_manager = wlr_server_decoration_manager_create(server.wl_display);
struct wlr_server_decoration_manager *server_decoration_manager =
wlr_server_decoration_manager_create(server.wl_display);
if (!server_decoration_manager) {
wlr_log(WLR_ERROR, "Unable to create the server decoration manager");
ret = 1;
@ -401,65 +448,117 @@ main(int argc, char *argv[])
server_decoration_manager, server.xdg_decoration ? WLR_SERVER_DECORATION_MANAGER_MODE_SERVER
: WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT);
export_dmabuf_manager = wlr_export_dmabuf_manager_v1_create(server.wl_display);
if (!export_dmabuf_manager) {
if (!wlr_viewporter_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create the viewporter interface");
ret = 1;
goto end;
}
struct wlr_presentation *presentation = wlr_presentation_create(server.wl_display, server.backend, 2);
if (!presentation) {
wlr_log(WLR_ERROR, "Unable to create the presentation interface");
ret = 1;
goto end;
}
if (!wlr_export_dmabuf_manager_v1_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create the export DMABUF manager");
ret = 1;
goto end;
}
screencopy_manager = wlr_screencopy_manager_v1_create(server.wl_display);
if (!screencopy_manager) {
if (!wlr_screencopy_manager_v1_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create the screencopy manager");
ret = 1;
goto end;
}
output_manager = wlr_xdg_output_manager_v1_create(server.wl_display, server.output_layout);
if (!output_manager) {
if (!wlr_single_pixel_buffer_manager_v1_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create the single pixel buffer manager");
ret = 1;
goto end;
}
if (!wlr_xdg_output_manager_v1_create(server.wl_display, server.output_layout)) {
wlr_log(WLR_ERROR, "Unable to create the output manager");
ret = 1;
goto end;
}
gamma_control_manager = wlr_gamma_control_manager_v1_create(server.wl_display);
if (!gamma_control_manager) {
server.output_manager_v1 = wlr_output_manager_v1_create(server.wl_display);
if (!server.output_manager_v1) {
wlr_log(WLR_ERROR, "Unable to create the output manager");
ret = 1;
goto end;
}
server.output_manager_apply.notify = handle_output_manager_apply;
wl_signal_add(&server.output_manager_v1->events.apply, &server.output_manager_apply);
server.output_manager_test.notify = handle_output_manager_test;
wl_signal_add(&server.output_manager_v1->events.test, &server.output_manager_test);
if (!wlr_gamma_control_manager_v1_create(server.wl_display)) {
wlr_log(WLR_ERROR, "Unable to create the gamma control manager");
ret = 1;
goto end;
}
struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard =
wlr_virtual_keyboard_manager_v1_create(server.wl_display);
if (!virtual_keyboard) {
wlr_log(WLR_ERROR, "Unable to create the virtual keyboard manager");
ret = 1;
goto end;
}
wl_signal_add(&virtual_keyboard->events.new_virtual_keyboard, &server.new_virtual_keyboard);
struct wlr_virtual_pointer_manager_v1 *virtual_pointer =
wlr_virtual_pointer_manager_v1_create(server.wl_display);
if (!virtual_pointer) {
wlr_log(WLR_ERROR, "Unable to create the virtual pointer manager");
ret = 1;
goto end;
}
wl_signal_add(&virtual_pointer->events.new_virtual_pointer, &server.new_virtual_pointer);
server.relative_pointer_manager = wlr_relative_pointer_manager_v1_create(server.wl_display);
if (!server.relative_pointer_manager) {
wlr_log(WLR_ERROR, "Unable to create the relative pointer manager");
ret = 1;
goto end;
}
#if CAGE_HAS_XWAYLAND
xwayland = wlr_xwayland_create(server.wl_display, compositor, true);
struct wlr_xcursor_manager *xcursor_manager = NULL;
struct wlr_xwayland *xwayland = wlr_xwayland_create(server.wl_display, compositor, true);
if (!xwayland) {
wlr_log(WLR_ERROR, "Cannot create XWayland server");
ret = 1;
goto end;
}
server.new_xwayland_surface.notify = handle_xwayland_surface_new;
wl_signal_add(&xwayland->events.new_surface, &server.new_xwayland_surface);
xcursor_manager = wlr_xcursor_manager_create(DEFAULT_XCURSOR, XCURSOR_SIZE);
if (!xcursor_manager) {
wlr_log(WLR_ERROR, "Cannot create XWayland XCursor manager");
ret = 1;
goto end;
}
if (setenv("DISPLAY", xwayland->display_name, true) < 0) {
wlr_log_errno(WLR_ERROR, "Unable to set DISPLAY for XWayland. Clients may not be able to connect");
} else {
wlr_log(WLR_DEBUG, "XWayland is running on display %s", xwayland->display_name);
}
server.new_xwayland_surface.notify = handle_xwayland_surface_new;
wl_signal_add(&xwayland->events.new_surface, &server.new_xwayland_surface);
if (!wlr_xcursor_manager_load(xcursor_manager, 1)) {
wlr_log(WLR_ERROR, "Cannot load XWayland XCursor theme");
}
struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor(xcursor_manager, DEFAULT_XCURSOR, 1);
if (xcursor) {
struct wlr_xcursor_image *image = xcursor->images[0];
wlr_xwayland_set_cursor(xwayland, image->buffer, image->width * 4, image->width, image->height,
image->hotspot_x, image->hotspot_y);
xcursor_manager = wlr_xcursor_manager_create(DEFAULT_XCURSOR, XCURSOR_SIZE);
if (!xcursor_manager) {
wlr_log(WLR_ERROR, "Cannot create XWayland XCursor manager");
ret = 1;
goto end;
}
if (setenv("DISPLAY", xwayland->display_name, true) < 0) {
wlr_log_errno(WLR_ERROR,
"Unable to set DISPLAY for XWayland. Clients may not be able to connect");
} else {
wlr_log(WLR_DEBUG, "XWayland is running on display %s", xwayland->display_name);
}
if (!wlr_xcursor_manager_load(xcursor_manager, 1)) {
wlr_log(WLR_ERROR, "Cannot load XWayland XCursor theme");
}
struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor(xcursor_manager, DEFAULT_XCURSOR, 1);
if (xcursor) {
struct wlr_xcursor_image *image = xcursor->images[0];
wlr_xwayland_set_cursor(xwayland, image->buffer, image->width * 4, image->width, image->height,
image->hotspot_x, image->hotspot_y);
}
}
#endif
@ -483,28 +582,44 @@ main(int argc, char *argv[])
}
#if CAGE_HAS_XWAYLAND
wlr_xwayland_set_seat(xwayland, server.seat->seat);
if (xwayland) {
wlr_xwayland_set_seat(xwayland, server.seat->seat);
}
#endif
if (!spawn_primary_client(server.wl_display, argv + optind, &pid, &sigchld_source)) {
if (optind < argc && !spawn_primary_client(&server, argv + optind, &pid, &sigchld_source)) {
ret = 1;
goto end;
}
/* Place the cursor in the center of the output layout. */
struct wlr_box *layout_box = wlr_output_layout_get_box(server.output_layout, NULL);
wlr_cursor_warp(server.seat->cursor, NULL, layout_box->width / 2, layout_box->height / 2);
seat_center_cursor(server.seat);
wl_display_run(server.wl_display);
#if CAGE_HAS_XWAYLAND
if (xwayland) {
wl_list_remove(&server.new_xwayland_surface.link);
}
wlr_xwayland_destroy(xwayland);
wlr_xcursor_manager_destroy(xcursor_manager);
#endif
wl_display_destroy_clients(server.wl_display);
wl_list_remove(&server.new_virtual_pointer.link);
wl_list_remove(&server.new_virtual_keyboard.link);
wl_list_remove(&server.output_manager_apply.link);
wl_list_remove(&server.output_manager_test.link);
wl_list_remove(&server.xdg_toplevel_decoration.link);
wl_list_remove(&server.new_xdg_toplevel.link);
wl_list_remove(&server.new_xdg_popup.link);
wl_list_remove(&server.new_idle_inhibitor_v1.link);
wl_list_remove(&server.new_output.link);
wl_list_remove(&server.output_layout_change.link);
end:
cleanup_primary_client(pid);
if (pid != 0)
app_ret = cleanup_primary_client(pid);
if (!ret && server.return_app_code)
ret = app_ret;
wl_event_source_remove(sigint_source);
wl_event_source_remove(sigterm_source);
@ -515,6 +630,8 @@ end:
/* This function is not null-safe, but we only ever get here
with a proper wl_display. */
wl_display_destroy(server.wl_display);
wlr_output_layout_destroy(server.output_layout);
wlr_scene_node_destroy(&server.scene->tree.node);
wlr_allocator_destroy(server.allocator);
wlr_renderer_destroy(server.renderer);
return ret;
}

25
contrib/increment-version Executable file
View file

@ -0,0 +1,25 @@
#!/usr/bin/env bash
if [ "$#" -ne 1 ]; then
echo "usage: $0 <new-version>" >&2
exit 1
fi
new_version="$1"
if [ "$new_version" != "${new_version#v}" ]; then
echo "Error: The new version shouldn't be prefixed with a \"v\"." >&2
exit 1
fi
set -x
sed -i meson.build -e "s/^ version: '.*'/ version: '$new_version'/"
echo -n "Minimum wlroots version? "
read -r wlr_version_min
sed -i meson.build -e "s/'wlroots', version: '.*'/'wlroots', version: '>= $wlr_version_min'/"
git add meson.build
git commit -m "Update version to $new_version"

21
contrib/release Executable file
View file

@ -0,0 +1,21 @@
#!/usr/bin/env bash
if [ "$#" -ne 1 ]; then
echo "usage: $0 <new-version>" >&2
exit 1
fi
new_version="$1"
if [ "$new_version" != "${new_version#v}" ]; then
echo "Error: The new version shouldn't be prefixed with a \"v\"." >&2
exit 1
fi
set -x
./increment_version "$new_version"
./tag-release "$new_version"
./sign-release
git push --tags

12
contrib/sign-release Executable file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -x
project="$(basename "$(pwd)")"
last=$(git describe --tags --abbrev=0)
prefix="$project-${last#v}"
archive="$prefix.tar.gz"
git archive --prefix="$prefix/" -o "$archive" "$last"
gpg --output "$archive".sig --detach-sig "$archive"

26
contrib/tag-release Executable file
View file

@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -x
if [ "$#" -ne 1 ]; then
echo "usage: $0 <new-version>" >&2
exit 1
fi
last=$(git describe --tags --abbrev=0)
echo "Last release was $last"
next="v$1"
shortlog="$(git shortlog --no-merges "$last"..)"
printf "Shortlog: \n\n%s\n\nRelease $next? [y/N] " "$shortlog"
read -r answer
if [ "$answer" != "y" ]; then
exit 0
fi
project="$(basename "$(pwd)")"
(echo "$project $next"; echo ""; echo "$shortlog") | git tag "$next" -ase -F -

View file

@ -8,8 +8,8 @@
#include <stdlib.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_idle.h>
#include <wlr/types/wlr_idle_inhibit_v1.h>
#include <wlr/types/wlr_idle_notify_v1.h>
#include "idle_inhibit_v1.h"
#include "server.h"
@ -32,7 +32,7 @@ idle_inhibit_v1_check_active(struct cg_server *server)
Hence, we simply check for any inhibitors and inhibit
accordingly. */
bool inhibited = !wl_list_empty(&server->inhibitors);
wlr_idle_set_enabled(server->idle, NULL, !inhibited);
wlr_idle_notifier_v1_set_inhibited(server->idle, inhibited);
}
static void

View file

@ -1,16 +1,17 @@
project('cage', 'c',
version: '0.1.3',
version: '0.2.1',
license: 'MIT',
meson_version: '>=0.58.1',
default_options: [
'c_std=c11',
'warning_level=3',
'warning_level=2',
'werror=true',
],
)
add_project_arguments(
[
'-DWLR_USE_UNSTABLE',
'-Wall',
'-Wundef',
'-Wno-unused-parameter',
],
@ -34,14 +35,13 @@ if is_freebsd
)
endif
wlroots = dependency('wlroots', version: '>= 0.13.0')
wlroots = dependency('wlroots-0.19', fallback: ['wlroots', 'wlroots'])
wayland_protos = dependency('wayland-protocols', version: '>=1.14')
wayland_server = dependency('wayland-server')
pixman = dependency('pixman-1')
xkbcommon = dependency('xkbcommon')
math = cc.find_library('m')
wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir')
wl_protocol_dir = wayland_protos.get_variable('pkgdatadir')
wayland_scanner = find_program('wayland-scanner')
wayland_scanner_server = generator(
wayland_scanner,
@ -64,22 +64,13 @@ server_protos = declare_dependency(
sources: server_protos_headers,
)
if get_option('xwayland')
wlroots_has_xwayland = cc.get_define('WLR_HAS_XWAYLAND', prefix: '#include <wlr/config.h>', dependencies: wlroots) == '1'
if not wlroots_has_xwayland
error('Cannot build Cage with XWayland support: wlroots has been built without it')
else
have_xwayland = true
endif
else
have_xwayland = false
endif
have_xwayland = wlroots.get_variable(pkgconfig: 'have_xwayland', internal: 'have_xwayland') == 'true'
version = '@0@'.format(meson.project_version())
git = find_program('git', native: true, required: false)
if git.found()
git_commit = run_command([git, 'rev-parse', '--short', 'HEAD'])
git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD'])
git_commit = run_command([git, 'rev-parse', '--short', 'HEAD'], check: false)
git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD'], check: false)
if git_commit.returncode() == 0 and git_branch.returncode() == 0
version = '@0@-@1@ (branch \'@2@\')'.format(
meson.project_version(),
@ -95,7 +86,7 @@ conf_data.set_quoted('CAGE_VERSION', version)
scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_option('man-pages'))
if scdoc.found()
scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true)
scdoc_prog = find_program(scdoc.get_variable('scdoc'), native: true)
sh = find_program('sh', native: true)
mandir = get_option('mandir')
man_files = [
@ -111,7 +102,7 @@ if scdoc.found()
input: filename,
output: output,
command: [
sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc_prog.path(), output)
sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc_prog.full_path(), output)
],
install: true,
install_dir: '@0@/man@1@'.format(mandir, section)
@ -123,9 +114,7 @@ cage_sources = [
'cage.c',
'idle_inhibit_v1.c',
'output.c',
'render.c',
'seat.c',
'util.c',
'view.c',
'xdg_shell.c',
]
@ -136,10 +125,8 @@ cage_headers = [
configuration: conf_data),
'idle_inhibit_v1.h',
'output.h',
'render.h',
'seat.h',
'server.h',
'util.h',
'view.h',
'xdg_shell.h',
]
@ -157,7 +144,6 @@ executable(
wayland_server,
wlroots,
xkbcommon,
pixman,
math,
],
install: true,

View file

@ -1,2 +1 @@
option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')
option('xwayland', type: 'boolean', value: 'false', description: 'Enable support for X11 applications')

574
output.c
View file

@ -1,7 +1,7 @@
/*
* Cage: A Wayland kiosk.
*
* Copyright (C) 2018-2020 Jente Hidskes
* Copyright (C) 2018-2021 Jente Hidskes
* Copyright (C) 2019 The Sway authors
*
* See the LICENSE file accompanying this file.
@ -11,6 +11,7 @@
#include "config.h"
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <wayland-server-core.h>
@ -20,220 +21,79 @@
#if WLR_HAS_X11_BACKEND
#include <wlr/backend/x11.h>
#endif
#include <wlr/render/swapchain.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_matrix.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_damage.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_surface.h>
#include <wlr/types/wlr_output_management_v1.h>
#include <wlr/types/wlr_output_swapchain_manager.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h>
#include <wlr/util/region.h>
#include "output.h"
#include "render.h"
#include "seat.h"
#include "server.h"
#include "util.h"
#include "view.h"
#if CAGE_HAS_XWAYLAND
#include "xwayland.h"
#endif
static void output_for_each_surface(struct cg_output *output, cg_surface_iterator_func_t iterator, void *user_data);
#define OUTPUT_CONFIG_UPDATED \
(WLR_OUTPUT_STATE_ENABLED | WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_SCALE | WLR_OUTPUT_STATE_TRANSFORM | \
WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED)
struct surface_iterator_data {
cg_surface_iterator_func_t user_iterator;
void *user_data;
static void
update_output_manager_config(struct cg_server *server)
{
struct wlr_output_configuration_v1 *config = wlr_output_configuration_v1_create();
struct cg_output *output;
wl_list_for_each (output, &server->outputs, link) {
struct wlr_output *wlr_output = output->wlr_output;
struct wlr_output_configuration_head_v1 *config_head =
wlr_output_configuration_head_v1_create(config, wlr_output);
struct wlr_box output_box;
/* Output-local coordinates. */
double ox, oy;
};
wlr_output_layout_get_box(server->output_layout, wlr_output, &output_box);
if (!wlr_box_empty(&output_box)) {
config_head->state.x = output_box.x;
config_head->state.y = output_box.y;
}
}
static bool
intersects_with_output(struct cg_output *output, struct wlr_output_layout *output_layout, struct wlr_box *surface_box)
{
/* Since the surface_box's x- and y-coordinates are already output local,
* the x- and y-coordinates of this box need to be 0 for this function to
* work correctly. */
struct wlr_box output_box = {0};
wlr_output_effective_resolution(output->wlr_output, &output_box.width, &output_box.height);
struct wlr_box intersection;
return wlr_box_intersection(&intersection, &output_box, surface_box);
wlr_output_manager_v1_set_configuration(server->output_manager_v1, config);
}
static void
output_for_each_surface_iterator(struct wlr_surface *surface, int sx, int sy, void *user_data)
static inline void
output_layout_add_auto(struct cg_output *output)
{
struct surface_iterator_data *data = user_data;
struct cg_output *output = data->output;
assert(output->scene_output != NULL);
struct wlr_output_layout_output *layout_output =
wlr_output_layout_add_auto(output->server->output_layout, output->wlr_output);
wlr_scene_output_layout_add_output(output->server->scene_output_layout, layout_output, output->scene_output);
}
if (!wlr_surface_has_buffer(surface)) {
static inline void
output_layout_add(struct cg_output *output, int32_t x, int32_t y)
{
assert(output->scene_output != NULL);
bool exists = wlr_output_layout_get(output->server->output_layout, output->wlr_output);
struct wlr_output_layout_output *layout_output =
wlr_output_layout_add(output->server->output_layout, output->wlr_output, x, y);
if (exists) {
return;
}
struct wlr_box surface_box = {
.x = data->ox + sx + surface->sx,
.y = data->oy + sy + surface->sy,
.width = surface->current.width,
.height = surface->current.height,
};
if (!intersects_with_output(output, output->server->output_layout, &surface_box)) {
return;
}
data->user_iterator(data->output, surface, &surface_box, data->user_data);
wlr_scene_output_layout_add_output(output->server->scene_output_layout, layout_output, output->scene_output);
}
void
output_surface_for_each_surface(struct cg_output *output, struct wlr_surface *surface, double ox, double oy,
cg_surface_iterator_func_t iterator, void *user_data)
static inline void
output_layout_remove(struct cg_output *output)
{
struct surface_iterator_data data = {
.user_iterator = iterator,
.user_data = user_data,
.output = output,
.ox = ox,
.oy = oy,
};
wlr_surface_for_each_surface(surface, output_for_each_surface_iterator, &data);
}
static void
output_view_for_each_surface(struct cg_output *output, struct cg_view *view, cg_surface_iterator_func_t iterator,
void *user_data)
{
struct surface_iterator_data data = {
.user_iterator = iterator,
.user_data = user_data,
.output = output,
.ox = view->lx,
.oy = view->ly,
};
wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &data.ox, &data.oy);
view_for_each_surface(view, output_for_each_surface_iterator, &data);
}
void
output_view_for_each_popup_surface(struct cg_output *output, struct cg_view *view, cg_surface_iterator_func_t iterator,
void *user_data)
{
struct surface_iterator_data data = {
.user_iterator = iterator,
.user_data = user_data,
.output = output,
.ox = view->lx,
.oy = view->ly,
};
wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &data.ox, &data.oy);
view_for_each_popup_surface(view, output_for_each_surface_iterator, &data);
}
void
output_drag_icons_for_each_surface(struct cg_output *output, struct wl_list *drag_icons,
cg_surface_iterator_func_t iterator, void *user_data)
{
struct cg_drag_icon *drag_icon;
wl_list_for_each (drag_icon, drag_icons, link) {
if (drag_icon->wlr_drag_icon->mapped) {
double ox = drag_icon->lx;
double oy = drag_icon->ly;
wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &ox, &oy);
output_surface_for_each_surface(output, drag_icon->wlr_drag_icon->surface, ox, oy, iterator,
user_data);
}
}
}
static void
output_for_each_surface(struct cg_output *output, cg_surface_iterator_func_t iterator, void *user_data)
{
struct cg_view *view;
wl_list_for_each_reverse (view, &output->server->views, link) {
output_view_for_each_surface(output, view, iterator, user_data);
}
output_drag_icons_for_each_surface(output, &output->server->seat->drag_icons, iterator, user_data);
}
struct send_frame_done_data {
struct timespec when;
};
static void
send_frame_done_iterator(struct cg_output *output, struct wlr_surface *surface, struct wlr_box *box, void *user_data)
{
struct send_frame_done_data *data = user_data;
wlr_surface_send_frame_done(surface, &data->when);
}
static void
send_frame_done(struct cg_output *output, struct send_frame_done_data *data)
{
output_for_each_surface(output, send_frame_done_iterator, data);
}
static void
count_surface_iterator(struct cg_output *output, struct wlr_surface *surface, struct wlr_box *_box, void *data)
{
size_t *n = data;
n++;
}
static bool
scan_out_primary_view(struct cg_output *output)
{
struct cg_server *server = output->server;
struct wlr_output *wlr_output = output->wlr_output;
struct cg_drag_icon *drag_icon;
wl_list_for_each (drag_icon, &server->seat->drag_icons, link) {
if (drag_icon->wlr_drag_icon->mapped) {
return false;
}
}
struct cg_view *view = seat_get_focus(server->seat);
if (!view || !view->wlr_surface) {
return false;
}
size_t n_surfaces = 0;
output_view_for_each_surface(output, view, count_surface_iterator, &n_surfaces);
if (n_surfaces > 1) {
return false;
}
#if CAGE_HAS_XWAYLAND
if (view->type == CAGE_XWAYLAND_VIEW) {
struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view);
if (!wl_list_empty(&xwayland_view->xwayland_surface->children)) {
return false;
}
}
#endif
struct wlr_surface *surface = view->wlr_surface;
if (!surface->buffer) {
return false;
}
if ((float) surface->current.scale != wlr_output->scale ||
surface->current.transform != wlr_output->transform) {
return false;
}
wlr_output_attach_buffer(wlr_output, &surface->buffer->base);
return wlr_output_commit(wlr_output);
wlr_output_layout_remove(output->server->output_layout, output->wlr_output);
}
static void
@ -246,115 +106,46 @@ output_enable(struct cg_output *output)
* duplicate the enabled property in cg_output. */
wlr_log(WLR_DEBUG, "Enabling output %s", wlr_output->name);
wlr_output_layout_add_auto(output->server->output_layout, wlr_output);
wlr_output_enable(wlr_output, true);
wlr_output_commit(wlr_output);
struct wlr_output_state state = {0};
wlr_output_state_set_enabled(&state, true);
if (wlr_output_commit_state(wlr_output, &state)) {
output_layout_add_auto(output);
}
update_output_manager_config(output->server);
}
static void
output_disable(struct cg_output *output)
{
struct wlr_output *wlr_output = output->wlr_output;
if (!wlr_output->enabled) {
wlr_log(WLR_DEBUG, "Not disabling already disabled output %s", wlr_output->name);
return;
}
wlr_log(WLR_DEBUG, "Disabling output %s", wlr_output->name);
wlr_output_enable(wlr_output, false);
wlr_output_layout_remove(output->server->output_layout, wlr_output);
wlr_output_commit(wlr_output);
struct wlr_output_state state = {0};
wlr_output_state_set_enabled(&state, false);
wlr_output_commit_state(wlr_output, &state);
output_layout_remove(output);
}
static void
damage_surface_iterator(struct cg_output *output, struct wlr_surface *surface, struct wlr_box *box, void *user_data)
handle_output_frame(struct wl_listener *listener, void *data)
{
struct wlr_output *wlr_output = output->wlr_output;
bool whole = *(bool *) user_data;
struct cg_output *output = wl_container_of(listener, output, frame);
scale_box(box, output->wlr_output->scale);
if (whole) {
wlr_output_damage_add_box(output->damage, box);
} else if (pixman_region32_not_empty(&surface->buffer_damage)) {
pixman_region32_t damage;
pixman_region32_init(&damage);
wlr_surface_get_effective_damage(surface, &damage);
wlr_region_scale(&damage, &damage, wlr_output->scale);
if (ceil(wlr_output->scale) > surface->current.scale) {
/* When scaling up a surface it'll become
blurry, so we need to expand the damage
region. */
wlr_region_expand(&damage, &damage, ceil(wlr_output->scale) - surface->current.scale);
}
pixman_region32_translate(&damage, box->x, box->y);
wlr_output_damage_add(output->damage, &damage);
pixman_region32_fini(&damage);
}
}
void
output_damage_surface(struct cg_output *output, struct wlr_surface *surface, double lx, double ly, bool whole)
{
if (!output->wlr_output->enabled) {
wlr_log(WLR_DEBUG, "Not adding damage for disabled output %s", output->wlr_output->name);
if (!output->wlr_output->enabled || !output->scene_output) {
return;
}
double ox = lx, oy = ly;
wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &ox, &oy);
output_surface_for_each_surface(output, surface, ox, oy, damage_surface_iterator, &whole);
}
wlr_scene_output_commit(output->scene_output, NULL);
static void
handle_output_damage_frame(struct wl_listener *listener, void *data)
{
struct cg_output *output = wl_container_of(listener, output, damage_frame);
struct send_frame_done_data frame_data = {0};
if (!output->wlr_output->enabled) {
return;
}
/* Check if we can scan-out the primary view. */
static bool last_scanned_out = false;
bool scanned_out = scan_out_primary_view(output);
if (scanned_out && !last_scanned_out) {
wlr_log(WLR_DEBUG, "Scanning out primary view");
}
if (last_scanned_out && !scanned_out) {
wlr_log(WLR_DEBUG, "Stopping primary view scan out");
}
last_scanned_out = scanned_out;
if (scanned_out) {
goto frame_done;
}
bool needs_frame;
pixman_region32_t damage;
pixman_region32_init(&damage);
if (!wlr_output_damage_attach_render(output->damage, &needs_frame, &damage)) {
wlr_log(WLR_ERROR, "Cannot make damage output current");
goto damage_finish;
}
if (!needs_frame) {
wlr_output_rollback(output->wlr_output);
goto damage_finish;
}
output_render(output, &damage);
damage_finish:
pixman_region32_fini(&damage);
frame_done:
clock_gettime(CLOCK_MONOTONIC, &frame_data.when);
send_frame_done(output, &frame_data);
struct timespec now = {0};
clock_gettime(CLOCK_MONOTONIC, &now);
wlr_scene_output_send_frame_done(output->scene_output, &now);
}
static void
@ -363,76 +154,80 @@ handle_output_commit(struct wl_listener *listener, void *data)
struct cg_output *output = wl_container_of(listener, output, commit);
struct wlr_output_event_commit *event = data;
if (!output->wlr_output->enabled) {
return;
}
/* Notes:
* - output layout change will also be called if needed to position the views
* - always update output manager configuration even if the output is now disabled */
if (event->committed & WLR_OUTPUT_STATE_TRANSFORM) {
struct cg_view *view;
wl_list_for_each (view, &output->server->views, link) {
view_position(view);
}
if (event->state->committed & OUTPUT_CONFIG_UPDATED) {
update_output_manager_config(output->server);
}
}
static void
handle_output_mode(struct wl_listener *listener, void *data)
handle_output_request_state(struct wl_listener *listener, void *data)
{
struct cg_output *output = wl_container_of(listener, output, mode);
struct cg_output *output = wl_container_of(listener, output, request_state);
struct wlr_output_event_request_state *event = data;
if (!output->wlr_output->enabled) {
return;
if (wlr_output_commit_state(output->wlr_output, event->state)) {
update_output_manager_config(output->server);
}
}
struct cg_view *view;
wl_list_for_each (view, &output->server->views, link) {
view_position(view);
void
handle_output_layout_change(struct wl_listener *listener, void *data)
{
struct cg_server *server = wl_container_of(listener, server, output_layout_change);
view_position_all(server);
update_output_manager_config(server);
}
static bool
is_nested_output(struct cg_output *output)
{
if (wlr_output_is_wl(output->wlr_output)) {
return true;
}
#if WLR_HAS_X11_BACKEND
if (wlr_output_is_x11(output->wlr_output)) {
return true;
}
#endif
return false;
}
static void
output_destroy(struct cg_output *output)
{
struct cg_server *server = output->server;
bool was_nested_output = is_nested_output(output);
output->wlr_output->data = NULL;
wl_list_remove(&output->destroy.link);
wl_list_remove(&output->commit.link);
wl_list_remove(&output->mode.link);
wl_list_remove(&output->damage_frame.link);
wl_list_remove(&output->damage_destroy.link);
wl_list_remove(&output->request_state.link);
wl_list_remove(&output->frame.link);
wl_list_remove(&output->link);
wlr_output_layout_remove(server->output_layout, output->wlr_output);
output_layout_remove(output);
free(output);
if (wl_list_empty(&server->outputs)) {
wl_display_terminate(server->wl_display);
} else if (server->output_mode == CAGE_MULTI_OUTPUT_MODE_LAST) {
if (wl_list_empty(&server->outputs) && was_nested_output) {
server_terminate(server);
} else if (server->output_mode == CAGE_MULTI_OUTPUT_MODE_LAST && !wl_list_empty(&server->outputs)) {
struct cg_output *prev = wl_container_of(server->outputs.next, prev, link);
if (prev) {
output_enable(prev);
struct cg_view *view;
wl_list_for_each (view, &server->views, link) {
view_position(view);
}
}
output_enable(prev);
view_position_all(server);
}
}
static void
handle_output_damage_destroy(struct wl_listener *listener, void *data)
{
struct cg_output *output = wl_container_of(listener, output, damage_destroy);
output_destroy(output);
}
static void
handle_output_destroy(struct wl_listener *listener, void *data)
{
struct cg_output *output = wl_container_of(listener, output, destroy);
wlr_output_damage_destroy(output->damage);
output_destroy(output);
}
@ -442,6 +237,11 @@ handle_new_output(struct wl_listener *listener, void *data)
struct cg_server *server = wl_container_of(listener, server, new_output);
struct wlr_output *wlr_output = data;
if (!wlr_output_init_render(wlr_output, server->allocator, server->renderer)) {
wlr_log(WLR_ERROR, "Failed to initialize output rendering");
return;
}
struct cg_output *output = calloc(1, sizeof(struct cg_output));
if (!output) {
wlr_log(WLR_ERROR, "Failed to allocate output");
@ -449,32 +249,51 @@ handle_new_output(struct wl_listener *listener, void *data)
}
output->wlr_output = wlr_output;
wlr_output->data = output;
output->server = server;
output->damage = wlr_output_damage_create(wlr_output);
wl_list_insert(&server->outputs, &output->link);
output->commit.notify = handle_output_commit;
wl_signal_add(&wlr_output->events.commit, &output->commit);
output->mode.notify = handle_output_mode;
wl_signal_add(&wlr_output->events.mode, &output->mode);
output->request_state.notify = handle_output_request_state;
wl_signal_add(&wlr_output->events.request_state, &output->request_state);
output->destroy.notify = handle_output_destroy;
wl_signal_add(&wlr_output->events.destroy, &output->destroy);
output->damage_frame.notify = handle_output_damage_frame;
wl_signal_add(&output->damage->events.frame, &output->damage_frame);
output->damage_destroy.notify = handle_output_damage_destroy;
wl_signal_add(&output->damage->events.destroy, &output->damage_destroy);
output->frame.notify = handle_output_frame;
wl_signal_add(&wlr_output->events.frame, &output->frame);
struct wlr_output_mode *preferred_mode = wlr_output_preferred_mode(wlr_output);
if (preferred_mode) {
wlr_output_set_mode(wlr_output, preferred_mode);
output->scene_output = wlr_scene_output_create(server->scene, wlr_output);
if (!output->scene_output) {
wlr_log(WLR_ERROR, "Failed to allocate scene output");
return;
}
wlr_output_set_transform(wlr_output, output->server->output_transform);
if (server->output_mode == CAGE_MULTI_OUTPUT_MODE_LAST) {
struct cg_output *next = wl_container_of(output->link.next, next, link);
if (next) {
output_disable(next);
struct wlr_output_state state = {0};
wlr_output_state_set_enabled(&state, true);
if (!wl_list_empty(&wlr_output->modes)) {
struct wlr_output_mode *preferred_mode = wlr_output_preferred_mode(wlr_output);
if (preferred_mode) {
wlr_output_state_set_mode(&state, preferred_mode);
}
if (!wlr_output_test_state(wlr_output, &state)) {
struct wlr_output_mode *mode;
wl_list_for_each (mode, &wlr_output->modes, link) {
if (mode == preferred_mode) {
continue;
}
wlr_output_state_set_mode(&state, mode);
if (wlr_output_test_state(wlr_output, &state)) {
break;
}
}
}
}
if (server->output_mode == CAGE_MULTI_OUTPUT_MODE_LAST && wl_list_length(&server->outputs) > 1) {
struct cg_output *next = wl_container_of(output->link.next, next, link);
output_disable(next);
}
if (!wlr_xcursor_manager_load(server->seat->xcursor_manager, wlr_output->scale)) {
@ -482,12 +301,13 @@ handle_new_output(struct wl_listener *listener, void *data)
wlr_output->scale);
}
output_enable(output);
struct cg_view *view;
wl_list_for_each (view, &output->server->views, link) {
view_position(view);
wlr_log(WLR_DEBUG, "Enabling new output %s", wlr_output->name);
if (wlr_output_commit_state(wlr_output, &state)) {
output_layout_add_auto(output);
}
view_position_all(output->server);
update_output_manager_config(output->server);
}
void
@ -508,3 +328,95 @@ output_set_window_title(struct cg_output *output, const char *title)
#endif
}
}
static bool
output_config_apply(struct cg_server *server, struct wlr_output_configuration_v1 *config, bool test_only)
{
bool ok = false;
size_t states_len;
struct wlr_backend_output_state *states = wlr_output_configuration_v1_build_state(config, &states_len);
if (states == NULL) {
return false;
}
struct wlr_output_swapchain_manager swapchain_manager;
wlr_output_swapchain_manager_init(&swapchain_manager, server->backend);
ok = wlr_output_swapchain_manager_prepare(&swapchain_manager, states, states_len);
if (!ok || test_only) {
goto out;
}
for (size_t i = 0; i < states_len; i++) {
struct wlr_backend_output_state *backend_state = &states[i];
struct cg_output *output = backend_state->output->data;
struct wlr_swapchain *swapchain =
wlr_output_swapchain_manager_get_swapchain(&swapchain_manager, backend_state->output);
struct wlr_scene_output_state_options options = {
.swapchain = swapchain,
};
struct wlr_output_state *state = &backend_state->base;
if (!wlr_scene_output_build_state(output->scene_output, state, &options)) {
ok = false;
goto out;
}
}
ok = wlr_backend_commit(server->backend, states, states_len);
if (!ok) {
goto out;
}
wlr_output_swapchain_manager_apply(&swapchain_manager);
struct wlr_output_configuration_head_v1 *head;
wl_list_for_each (head, &config->heads, link) {
struct cg_output *output = head->state.output->data;
if (head->state.enabled) {
output_layout_add(output, head->state.x, head->state.y);
} else {
output_layout_remove(output);
}
}
out:
wlr_output_swapchain_manager_finish(&swapchain_manager);
for (size_t i = 0; i < states_len; i++) {
wlr_output_state_finish(&states[i].base);
}
free(states);
return ok;
}
void
handle_output_manager_apply(struct wl_listener *listener, void *data)
{
struct cg_server *server = wl_container_of(listener, server, output_manager_apply);
struct wlr_output_configuration_v1 *config = data;
if (output_config_apply(server, config, false)) {
wlr_output_configuration_v1_send_succeeded(config);
} else {
wlr_output_configuration_v1_send_failed(config);
}
wlr_output_configuration_v1_destroy(config);
}
void
handle_output_manager_test(struct wl_listener *listener, void *data)
{
struct cg_server *server = wl_container_of(listener, server, output_manager_test);
struct wlr_output_configuration_v1 *config = data;
if (output_config_apply(server, config, true)) {
wlr_output_configuration_v1_send_succeeded(config);
} else {
wlr_output_configuration_v1_send_failed(config);
}
wlr_output_configuration_v1_destroy(config);
}

View file

@ -3,7 +3,6 @@
#include <wayland-server-core.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_damage.h>
#include "server.h"
#include "view.h"
@ -11,28 +10,20 @@
struct cg_output {
struct cg_server *server;
struct wlr_output *wlr_output;
struct wlr_output_damage *damage;
struct wlr_scene_output *scene_output;
struct wl_listener commit;
struct wl_listener mode;
struct wl_listener request_state;
struct wl_listener destroy;
struct wl_listener damage_frame;
struct wl_listener damage_destroy;
struct wl_listener frame;
struct wl_list link; // cg_server::outputs
};
typedef void (*cg_surface_iterator_func_t)(struct cg_output *output, struct wlr_surface *surface, struct wlr_box *box,
void *user_data);
void handle_output_manager_apply(struct wl_listener *listener, void *data);
void handle_output_manager_test(struct wl_listener *listener, void *data);
void handle_output_layout_change(struct wl_listener *listener, void *data);
void handle_new_output(struct wl_listener *listener, void *data);
void output_surface_for_each_surface(struct cg_output *output, struct wlr_surface *surface, double ox, double oy,
cg_surface_iterator_func_t iterator, void *user_data);
void output_view_for_each_popup_surface(struct cg_output *output, struct cg_view *view,
cg_surface_iterator_func_t iterator, void *user_data);
void output_drag_icons_for_each_surface(struct cg_output *output, struct wl_list *drag_icons,
cg_surface_iterator_func_t iterator, void *user_data);
void output_damage_surface(struct cg_output *output, struct wlr_surface *surface, double lx, double ly, bool whole);
void output_set_window_title(struct cg_output *output, const char *title);
#endif

205
render.c
View file

@ -1,205 +0,0 @@
/*
* Cage: A Wayland kiosk.
*
* Copyright (C) 2018-2020 Jente Hidskes
* Copyright (C) 2019 The Sway authors
*
* See the LICENSE file accompanying this file.
*/
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_box.h>
#include <wlr/types/wlr_matrix.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_surface.h>
#include <wlr/util/log.h>
#include <wlr/util/region.h>
#include "output.h"
#include "seat.h"
#include "server.h"
#include "util.h"
#include "view.h"
static void
scissor_output(struct wlr_output *output, pixman_box32_t *rect)
{
struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend);
struct wlr_box box = {
.x = rect->x1,
.y = rect->y1,
.width = rect->x2 - rect->x1,
.height = rect->y2 - rect->y1,
};
int output_width, output_height;
wlr_output_transformed_resolution(output, &output_width, &output_height);
enum wl_output_transform transform = wlr_output_transform_invert(output->transform);
wlr_box_transform(&box, &box, transform, output_width, output_height);
wlr_renderer_scissor(renderer, &box);
}
struct render_data {
pixman_region32_t *damage;
};
static void
render_texture(struct wlr_output *wlr_output, pixman_region32_t *output_damage, struct wlr_texture *texture,
const struct wlr_box *box, const float matrix[static 9])
{
struct wlr_renderer *renderer = wlr_backend_get_renderer(wlr_output->backend);
pixman_region32_t damage;
pixman_region32_init(&damage);
pixman_region32_union_rect(&damage, &damage, box->x, box->y, box->width, box->height);
pixman_region32_intersect(&damage, &damage, output_damage);
if (!pixman_region32_not_empty(&damage)) {
goto damage_finish;
}
int nrects;
pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects);
for (int i = 0; i < nrects; i++) {
scissor_output(wlr_output, &rects[i]);
wlr_render_texture_with_matrix(renderer, texture, matrix, 1.0f);
}
damage_finish:
pixman_region32_fini(&damage);
}
static void
render_surface_iterator(struct cg_output *output, struct wlr_surface *surface, struct wlr_box *box, void *user_data)
{
struct render_data *data = user_data;
struct wlr_output *wlr_output = output->wlr_output;
pixman_region32_t *output_damage = data->damage;
struct wlr_texture *texture = wlr_surface_get_texture(surface);
if (!texture) {
wlr_log(WLR_DEBUG, "Cannot obtain surface texture");
return;
}
scale_box(box, wlr_output->scale);
float matrix[9];
enum wl_output_transform transform = wlr_output_transform_invert(surface->current.transform);
wlr_matrix_project_box(matrix, box, transform, 0.0f, wlr_output->transform_matrix);
render_texture(wlr_output, output_damage, texture, box, matrix);
}
static void
render_drag_icons(struct cg_output *output, pixman_region32_t *damage, struct wl_list *drag_icons)
{
struct render_data data = {
.damage = damage,
};
output_drag_icons_for_each_surface(output, drag_icons, render_surface_iterator, &data);
}
/**
* Render all toplevels without descending into popups.
*/
static void
render_view_toplevels(struct cg_view *view, struct cg_output *output, pixman_region32_t *damage)
{
struct render_data data = {
.damage = damage,
};
double ox = view->lx;
double oy = view->ly;
wlr_output_layout_output_coords(output->server->output_layout, output->wlr_output, &ox, &oy);
output_surface_for_each_surface(output, view->wlr_surface, ox, oy, render_surface_iterator, &data);
}
static void
render_view_popups(struct cg_view *view, struct cg_output *output, pixman_region32_t *damage)
{
struct render_data data = {
.damage = damage,
};
output_view_for_each_popup_surface(output, view, render_surface_iterator, &data);
}
void
output_render(struct cg_output *output, pixman_region32_t *damage)
{
struct cg_server *server = output->server;
struct wlr_output *wlr_output = output->wlr_output;
struct wlr_renderer *renderer = wlr_backend_get_renderer(wlr_output->backend);
if (!renderer) {
wlr_log(WLR_DEBUG, "Expected the output backend to have a renderer");
return;
}
wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height);
if (!pixman_region32_not_empty(damage)) {
wlr_log(WLR_DEBUG, "Output isn't damaged but needs a buffer swap");
goto renderer_end;
}
#ifdef DEBUG
if (server->debug_damage_tracking) {
wlr_renderer_clear(renderer, (float[]){1.0f, 0.0f, 0.0f, 1.0f});
}
#endif
float color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
int nrects;
pixman_box32_t *rects = pixman_region32_rectangles(damage, &nrects);
for (int i = 0; i < nrects; i++) {
scissor_output(wlr_output, &rects[i]);
wlr_renderer_clear(renderer, color);
}
// TODO: render only top view, possibly use focused view for this, see #35.
struct cg_view *view;
wl_list_for_each_reverse (view, &server->views, link) {
render_view_toplevels(view, output, damage);
}
struct cg_view *focused_view = seat_get_focus(server->seat);
if (focused_view) {
render_view_popups(focused_view, output, damage);
}
render_drag_icons(output, damage, &server->seat->drag_icons);
renderer_end:
/* Draw software cursor in case hardware cursors aren't
available. This is a no-op when they are. */
wlr_output_render_software_cursors(wlr_output, damage);
wlr_renderer_scissor(renderer, NULL);
wlr_renderer_end(renderer);
int output_width, output_height;
wlr_output_transformed_resolution(wlr_output, &output_width, &output_height);
pixman_region32_t frame_damage;
pixman_region32_init(&frame_damage);
enum wl_output_transform transform = wlr_output_transform_invert(wlr_output->transform);
wlr_region_transform(&frame_damage, &output->damage->current, transform, output_width, output_height);
#ifdef DEBUG
if (server->debug_damage_tracking) {
pixman_region32_union_rect(&frame_damage, &frame_damage, 0, 0, output_width, output_height);
}
#endif
wlr_output_set_damage(wlr_output, &frame_damage);
pixman_region32_fini(&frame_damage);
if (!wlr_output_commit(wlr_output)) {
wlr_log(WLR_ERROR, "Could not commit output");
}
}

View file

@ -1,8 +0,0 @@
#ifndef CG_RENDER_H
#define CG_RENDER_H
#include "output.h"
void output_render(struct cg_output *output, pixman_region32_t *damage);
#endif

387
seat.c
View file

@ -6,21 +6,29 @@
* See the LICENSE file accompanying this file.
*/
#define _POSIX_C_SOURCE 200809L
#include "config.h"
#include <assert.h>
#include <linux/input-event-codes.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/backend/multi.h>
#include <wlr/backend/session.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_idle.h>
#include <wlr/types/wlr_idle_notify_v1.h>
#include <wlr/types/wlr_keyboard_group.h>
#include <wlr/types/wlr_primary_selection.h>
#include <wlr/types/wlr_relative_pointer_v1.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_surface.h>
#include <wlr/types/wlr_touch.h>
#include <wlr/types/wlr_virtual_keyboard_v1.h>
#include <wlr/types/wlr_virtual_pointer_v1.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/util/log.h>
#if CAGE_HAS_XWAYLAND
@ -41,42 +49,42 @@ static void drag_icon_update_position(struct cg_drag_icon *drag_icon);
* menus or tooltips. This function tests if any of those are underneath the
* coordinates lx and ly (in output Layout Coordinates). If so, it sets the
* surface pointer to that wlr_surface and the sx and sy coordinates to the
* coordinates relative to that surface's top-left corner. */
static bool
view_at(struct cg_view *view, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy)
{
double view_sx = lx - view->lx;
double view_sy = ly - view->ly;
double _sx, _sy;
struct wlr_surface *_surface = view_wlr_surface_at(view, view_sx, view_sy, &_sx, &_sy);
if (_surface != NULL) {
*sx = _sx;
*sy = _sy;
*surface = _surface;
return true;
}
return false;
}
/* This iterates over all of our surfaces and attempts to find one
* under the cursor. This relies on server->views being ordered from
* top-to-bottom. If desktop_view_at returns a view, there is also a
* surface. There cannot be a surface without a view, either. It's
* both or nothing. */
* coordinates relative to that surface's top-left corner.
*
* This function iterates over all of our surfaces and attempts to find one
* under the cursor. If desktop_view_at returns a view, there is also a
* surface. There cannot be a surface without a view, either. It's both or
* nothing.
*/
static struct cg_view *
desktop_view_at(struct cg_server *server, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy)
{
struct cg_view *view;
wl_list_for_each (view, &server->views, link) {
if (view_at(view, lx, ly, surface, sx, sy)) {
return view;
}
struct wlr_scene_node *node = wlr_scene_node_at(&server->scene->tree.node, lx, ly, sx, sy);
if (node == NULL || node->type != WLR_SCENE_NODE_BUFFER) {
return NULL;
}
return NULL;
struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer(scene_buffer);
if (!scene_surface) {
return NULL;
}
*surface = scene_surface->surface;
/* Walk up the tree until we find a node with a data pointer. When done,
* we've found the node representing the view. */
while (!node->data) {
if (!node->parent) {
node = NULL;
break;
}
node = &node->parent->node;
}
assert(node != NULL);
return node->data;
}
static void
@ -120,23 +128,23 @@ update_capabilities(struct cg_seat *seat)
/* Hide cursor if the seat doesn't have pointer capability. */
if ((caps & WL_SEAT_CAPABILITY_POINTER) == 0) {
wlr_cursor_set_image(seat->cursor, NULL, 0, 0, 0, 0, 0, 0);
wlr_cursor_unset_image(seat->cursor);
} else {
wlr_xcursor_manager_set_cursor_image(seat->xcursor_manager, DEFAULT_XCURSOR, seat->cursor);
wlr_cursor_set_xcursor(seat->cursor, seat->xcursor_manager, DEFAULT_XCURSOR);
}
}
static void
map_input_device_to_output(struct cg_seat *seat, struct wlr_input_device *device)
map_input_device_to_output(struct cg_seat *seat, struct wlr_input_device *device, const char *output_name)
{
if (!device->output_name) {
if (!output_name) {
wlr_log(WLR_INFO, "Input device %s cannot be mapped to an output device\n", device->name);
return;
}
struct cg_output *output;
wl_list_for_each (output, &seat->server->outputs, link) {
if (strcmp(device->output_name, output->wlr_output->name) == 0) {
if (strcmp(output_name, output->wlr_output->name) == 0) {
wlr_log(WLR_INFO, "Mapping input device %s to output device %s\n", device->name,
output->wlr_output->name);
wlr_cursor_map_input_to_output(seat->cursor, device, output->wlr_output);
@ -154,7 +162,7 @@ handle_touch_destroy(struct wl_listener *listener, void *data)
struct cg_seat *seat = touch->seat;
wl_list_remove(&touch->link);
wlr_cursor_detach_input_device(seat->cursor, touch->device);
wlr_cursor_detach_input_device(seat->cursor, &touch->touch->base);
wl_list_remove(&touch->destroy.link);
free(touch);
@ -162,7 +170,7 @@ handle_touch_destroy(struct wl_listener *listener, void *data)
}
static void
handle_new_touch(struct cg_seat *seat, struct wlr_input_device *device)
handle_new_touch(struct cg_seat *seat, struct wlr_touch *wlr_touch)
{
struct cg_touch *touch = calloc(1, sizeof(struct cg_touch));
if (!touch) {
@ -171,14 +179,14 @@ handle_new_touch(struct cg_seat *seat, struct wlr_input_device *device)
}
touch->seat = seat;
touch->device = device;
wlr_cursor_attach_input_device(seat->cursor, device);
touch->touch = wlr_touch;
wlr_cursor_attach_input_device(seat->cursor, &wlr_touch->base);
wl_list_insert(&seat->touch, &touch->link);
touch->destroy.notify = handle_touch_destroy;
wl_signal_add(&touch->device->events.destroy, &touch->destroy);
wl_signal_add(&wlr_touch->base.events.destroy, &touch->destroy);
map_input_device_to_output(seat, device);
map_input_device_to_output(seat, &wlr_touch->base, wlr_touch->output_name);
}
static void
@ -188,7 +196,7 @@ handle_pointer_destroy(struct wl_listener *listener, void *data)
struct cg_seat *seat = pointer->seat;
wl_list_remove(&pointer->link);
wlr_cursor_detach_input_device(seat->cursor, pointer->device);
wlr_cursor_detach_input_device(seat->cursor, &pointer->pointer->base);
wl_list_remove(&pointer->destroy.link);
free(pointer);
@ -196,7 +204,7 @@ handle_pointer_destroy(struct wl_listener *listener, void *data)
}
static void
handle_new_pointer(struct cg_seat *seat, struct wlr_input_device *device)
handle_new_pointer(struct cg_seat *seat, struct wlr_pointer *wlr_pointer)
{
struct cg_pointer *pointer = calloc(1, sizeof(struct cg_pointer));
if (!pointer) {
@ -205,23 +213,44 @@ handle_new_pointer(struct cg_seat *seat, struct wlr_input_device *device)
}
pointer->seat = seat;
pointer->device = device;
wlr_cursor_attach_input_device(seat->cursor, device);
pointer->pointer = wlr_pointer;
wlr_cursor_attach_input_device(seat->cursor, &wlr_pointer->base);
wl_list_insert(&seat->pointers, &pointer->link);
pointer->destroy.notify = handle_pointer_destroy;
wl_signal_add(&device->events.destroy, &pointer->destroy);
wl_signal_add(&wlr_pointer->base.events.destroy, &pointer->destroy);
map_input_device_to_output(seat, device);
map_input_device_to_output(seat, &wlr_pointer->base, wlr_pointer->output_name);
}
static void
handle_modifier_event(struct wlr_input_device *device, struct cg_seat *seat)
handle_virtual_pointer(struct wl_listener *listener, void *data)
{
wlr_seat_set_keyboard(seat->seat, device);
wlr_seat_keyboard_notify_modifiers(seat->seat, &device->keyboard->modifiers);
struct cg_server *server = wl_container_of(listener, server, new_virtual_pointer);
struct cg_seat *seat = server->seat;
struct wlr_virtual_pointer_v1_new_pointer_event *event = data;
struct wlr_virtual_pointer_v1 *pointer = event->new_pointer;
struct wlr_pointer *wlr_pointer = &pointer->pointer;
wlr_idle_notify_activity(seat->server->idle, seat->seat);
/* We'll want to map the device back to an output later, this is a bit
* sub-optimal (we could just keep the suggested_output), but just copy
* its name so we do like other devices
*/
if (event->suggested_output != NULL) {
wlr_pointer->output_name = strdup(event->suggested_output->name);
}
/* TODO: event->suggested_seat should be checked if we handle multiple seats */
handle_new_pointer(seat, wlr_pointer);
update_capabilities(seat);
}
static void
handle_modifier_event(struct wlr_keyboard *keyboard, struct cg_seat *seat)
{
wlr_seat_set_keyboard(seat->seat, keyboard);
wlr_seat_keyboard_notify_modifiers(seat->seat, &keyboard->modifiers);
wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}
static bool
@ -229,38 +258,37 @@ handle_keybinding(struct cg_server *server, xkb_keysym_t sym)
{
#ifdef DEBUG
if (sym == XKB_KEY_Escape) {
wl_display_terminate(server->wl_display);
server_terminate(server);
return true;
}
#endif
if (server->allow_vt_switch && sym >= XKB_KEY_XF86Switch_VT_1 && sym <= XKB_KEY_XF86Switch_VT_12) {
if (wlr_backend_is_multi(server->backend)) {
struct wlr_session *session = wlr_backend_get_session(server->backend);
if (session) {
if (server->session) {
unsigned vt = sym - XKB_KEY_XF86Switch_VT_1 + 1;
wlr_session_change_vt(session, vt);
wlr_session_change_vt(server->session, vt);
}
}
} else {
return false;
}
wlr_idle_notify_activity(server->idle, server->seat->seat);
wlr_idle_notifier_v1_notify_activity(server->idle, server->seat->seat);
return true;
}
static void
handle_key_event(struct wlr_input_device *device, struct cg_seat *seat, void *data)
handle_key_event(struct wlr_keyboard *keyboard, struct cg_seat *seat, void *data)
{
struct wlr_event_keyboard_key *event = data;
struct wlr_keyboard_key_event *event = data;
/* Translate from libinput keycode to an xkbcommon keycode. */
xkb_keycode_t keycode = event->keycode + 8;
const xkb_keysym_t *syms;
int nsyms = xkb_state_key_get_syms(device->keyboard->xkb_state, keycode, &syms);
int nsyms = xkb_state_key_get_syms(keyboard->xkb_state, keycode, &syms);
bool handled = false;
uint32_t modifiers = wlr_keyboard_get_modifiers(device->keyboard);
uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard);
if ((modifiers & WLR_MODIFIER_ALT) && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
/* If Alt is held down and this button was pressed, we
* attempt to process it as a compositor
@ -272,38 +300,43 @@ handle_key_event(struct wlr_input_device *device, struct cg_seat *seat, void *da
if (!handled) {
/* Otherwise, we pass it along to the client. */
wlr_seat_set_keyboard(seat->seat, device);
wlr_seat_set_keyboard(seat->seat, keyboard);
wlr_seat_keyboard_notify_key(seat->seat, event->time_msec, event->keycode, event->state);
}
wlr_idle_notify_activity(seat->server->idle, seat->seat);
wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}
static void
handle_keyboard_group_key(struct wl_listener *listener, void *data)
{
struct cg_keyboard_group *cg_group = wl_container_of(listener, cg_group, key);
handle_key_event(cg_group->wlr_group->input_device, cg_group->seat, data);
handle_key_event(&cg_group->wlr_group->keyboard, cg_group->seat, data);
}
static void
handle_keyboard_group_modifiers(struct wl_listener *listener, void *data)
{
struct cg_keyboard_group *group = wl_container_of(listener, group, modifiers);
handle_modifier_event(group->wlr_group->input_device, group->seat);
handle_modifier_event(&group->wlr_group->keyboard, group->seat);
}
static void
cg_keyboard_group_add(struct wlr_input_device *device, struct cg_seat *seat)
cg_keyboard_group_add(struct wlr_keyboard *keyboard, struct cg_seat *seat, bool virtual)
{
struct wlr_keyboard *wlr_keyboard = device->keyboard;
struct cg_keyboard_group *group;
wl_list_for_each (group, &seat->keyboard_groups, link) {
struct wlr_keyboard_group *wlr_group = group->wlr_group;
if (wlr_keyboard_group_add_keyboard(wlr_group, wlr_keyboard)) {
wlr_log(WLR_DEBUG, "Added new keyboard to existing group");
return;
/* We apparently should not group virtual keyboards,
* so create a new group with it
*/
if (!virtual) {
struct cg_keyboard_group *group;
wl_list_for_each (group, &seat->keyboard_groups, link) {
if (group->is_virtual)
continue;
struct wlr_keyboard_group *wlr_group = group->wlr_group;
if (wlr_keyboard_group_add_keyboard(wlr_group, keyboard)) {
wlr_log(WLR_DEBUG, "Added new keyboard to existing group");
return;
}
}
}
@ -315,6 +348,7 @@ cg_keyboard_group_add(struct wlr_input_device *device, struct cg_seat *seat)
return;
}
cg_group->seat = seat;
cg_group->is_virtual = virtual;
cg_group->wlr_group = wlr_keyboard_group_create();
if (cg_group->wlr_group == NULL) {
wlr_log(WLR_ERROR, "Failed to create wlr keyboard group.");
@ -322,14 +356,14 @@ cg_keyboard_group_add(struct wlr_input_device *device, struct cg_seat *seat)
}
cg_group->wlr_group->data = cg_group;
wlr_keyboard_set_keymap(&cg_group->wlr_group->keyboard, device->keyboard->keymap);
wlr_keyboard_set_keymap(&cg_group->wlr_group->keyboard, keyboard->keymap);
wlr_keyboard_set_repeat_info(&cg_group->wlr_group->keyboard, wlr_keyboard->repeat_info.rate,
wlr_keyboard->repeat_info.delay);
wlr_keyboard_set_repeat_info(&cg_group->wlr_group->keyboard, keyboard->repeat_info.rate,
keyboard->repeat_info.delay);
wlr_log(WLR_DEBUG, "Created keyboard group");
wlr_keyboard_group_add_keyboard(cg_group->wlr_group, wlr_keyboard);
wlr_keyboard_group_add_keyboard(cg_group->wlr_group, keyboard);
wl_list_insert(&seat->keyboard_groups, &cg_group->link);
wl_signal_add(&cg_group->wlr_group->keyboard.events.key, &cg_group->key);
@ -347,36 +381,55 @@ cleanup:
}
static void
handle_new_keyboard(struct cg_seat *seat, struct wlr_input_device *device)
keyboard_group_destroy(struct cg_keyboard_group *keyboard_group)
{
wl_list_remove(&keyboard_group->key.link);
wl_list_remove(&keyboard_group->modifiers.link);
wlr_keyboard_group_destroy(keyboard_group->wlr_group);
wl_list_remove(&keyboard_group->link);
free(keyboard_group);
}
static void
handle_new_keyboard(struct cg_seat *seat, struct wlr_keyboard *keyboard, bool virtual)
{
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!context) {
wlr_log(WLR_ERROR, "Unable to create XBK context");
wlr_log(WLR_ERROR, "Unable to create XKB context");
return;
}
struct xkb_rule_names rules = {0};
rules.rules = getenv("XKB_DEFAULT_RULES");
rules.model = getenv("XKB_DEFAULT_MODEL");
rules.layout = getenv("XKB_DEFAULT_LAYOUT");
rules.variant = getenv("XKB_DEFAULT_VARIANT");
rules.options = getenv("XKB_DEFAULT_OPTIONS");
struct xkb_keymap *keymap = xkb_map_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS);
struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL, XKB_KEYMAP_COMPILE_NO_FLAGS);
if (!keymap) {
wlr_log(WLR_ERROR, "Unable to configure keyboard: keymap does not exist");
xkb_context_unref(context);
return;
}
wlr_keyboard_set_keymap(device->keyboard, keymap);
wlr_keyboard_set_keymap(keyboard, keymap);
xkb_keymap_unref(keymap);
xkb_context_unref(context);
wlr_keyboard_set_repeat_info(device->keyboard, 25, 600);
wlr_keyboard_set_repeat_info(keyboard, 25, 600);
cg_keyboard_group_add(device, seat);
cg_keyboard_group_add(keyboard, seat, virtual);
wlr_seat_set_keyboard(seat->seat, device);
wlr_seat_set_keyboard(seat->seat, keyboard);
}
static void
handle_virtual_keyboard(struct wl_listener *listener, void *data)
{
struct cg_server *server = wl_container_of(listener, server, new_virtual_keyboard);
struct cg_seat *seat = server->seat;
struct wlr_virtual_keyboard_v1 *keyboard = data;
struct wlr_keyboard *wlr_keyboard = &keyboard->keyboard;
/* TODO: If multiple seats are supported, check keyboard->seat
* to select the appropriate one */
handle_new_keyboard(seat, wlr_keyboard, true);
update_capabilities(seat);
}
static void
@ -387,18 +440,18 @@ handle_new_input(struct wl_listener *listener, void *data)
switch (device->type) {
case WLR_INPUT_DEVICE_KEYBOARD:
handle_new_keyboard(seat, device);
handle_new_keyboard(seat, wlr_keyboard_from_input_device(device), false);
break;
case WLR_INPUT_DEVICE_POINTER:
handle_new_pointer(seat, device);
handle_new_pointer(seat, wlr_pointer_from_input_device(device));
break;
case WLR_INPUT_DEVICE_TOUCH:
handle_new_touch(seat, device);
handle_new_touch(seat, wlr_touch_from_input_device(device));
break;
case WLR_INPUT_DEVICE_SWITCH:
wlr_log(WLR_DEBUG, "Switch input is not implemented");
return;
case WLR_INPUT_DEVICE_TABLET_TOOL:
case WLR_INPUT_DEVICE_TABLET:
case WLR_INPUT_DEVICE_TABLET_PAD:
wlr_log(WLR_DEBUG, "Tablet input is not implemented");
return;
@ -448,10 +501,10 @@ static void
handle_touch_down(struct wl_listener *listener, void *data)
{
struct cg_seat *seat = wl_container_of(listener, seat, touch_down);
struct wlr_event_touch_down *event = data;
struct wlr_touch_down_event *event = data;
double lx, ly;
wlr_cursor_absolute_to_layout_coords(seat->cursor, event->device, event->x, event->y, &lx, &ly);
wlr_cursor_absolute_to_layout_coords(seat->cursor, &event->touch->base, event->x, event->y, &lx, &ly);
double sx, sy;
struct wlr_surface *surface;
@ -466,43 +519,43 @@ handle_touch_down(struct wl_listener *listener, void *data)
seat->touch_id = event->touch_id;
seat->touch_lx = lx;
seat->touch_ly = ly;
press_cursor_button(seat, event->device, event->time_msec, BTN_LEFT, WLR_BUTTON_PRESSED, lx, ly);
press_cursor_button(seat, &event->touch->base, event->time_msec, BTN_LEFT, WLR_BUTTON_PRESSED, lx, ly);
}
wlr_idle_notify_activity(seat->server->idle, seat->seat);
wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}
static void
handle_touch_up(struct wl_listener *listener, void *data)
{
struct cg_seat *seat = wl_container_of(listener, seat, touch_up);
struct wlr_event_touch_up *event = data;
struct wlr_touch_up_event *event = data;
if (!wlr_seat_touch_get_point(seat->seat, event->touch_id)) {
return;
}
if (wlr_seat_touch_num_points(seat->seat) == 1) {
press_cursor_button(seat, event->device, event->time_msec, BTN_LEFT, WLR_BUTTON_RELEASED,
press_cursor_button(seat, &event->touch->base, event->time_msec, BTN_LEFT, WLR_BUTTON_RELEASED,
seat->touch_lx, seat->touch_ly);
}
wlr_seat_touch_notify_up(seat->seat, event->time_msec, event->touch_id);
wlr_idle_notify_activity(seat->server->idle, seat->seat);
wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}
static void
handle_touch_motion(struct wl_listener *listener, void *data)
{
struct cg_seat *seat = wl_container_of(listener, seat, touch_motion);
struct wlr_event_touch_motion *event = data;
struct wlr_touch_motion_event *event = data;
if (!wlr_seat_touch_get_point(seat->seat, event->touch_id)) {
return;
}
double lx, ly;
wlr_cursor_absolute_to_layout_coords(seat->cursor, event->device, event->x, event->y, &lx, &ly);
wlr_cursor_absolute_to_layout_coords(seat->cursor, &event->touch->base, event->x, event->y, &lx, &ly);
double sx, sy;
struct wlr_surface *surface;
@ -520,7 +573,16 @@ handle_touch_motion(struct wl_listener *listener, void *data)
seat->touch_ly = ly;
}
wlr_idle_notify_activity(seat->server->idle, seat->seat);
wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}
static void
handle_touch_frame(struct wl_listener *listener, void *data)
{
struct cg_seat *seat = wl_container_of(listener, seat, touch_frame);
wlr_seat_touch_notify_frame(seat->seat);
wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}
static void
@ -529,50 +591,52 @@ handle_cursor_frame(struct wl_listener *listener, void *data)
struct cg_seat *seat = wl_container_of(listener, seat, cursor_frame);
wlr_seat_pointer_notify_frame(seat->seat);
wlr_idle_notify_activity(seat->server->idle, seat->seat);
wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}
static void
handle_cursor_axis(struct wl_listener *listener, void *data)
{
struct cg_seat *seat = wl_container_of(listener, seat, cursor_axis);
struct wlr_event_pointer_axis *event = data;
struct wlr_pointer_axis_event *event = data;
wlr_seat_pointer_notify_axis(seat->seat, event->time_msec, event->orientation, event->delta,
event->delta_discrete, event->source);
wlr_idle_notify_activity(seat->server->idle, seat->seat);
event->delta_discrete, event->source, event->relative_direction);
wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}
static void
handle_cursor_button(struct wl_listener *listener, void *data)
{
struct cg_seat *seat = wl_container_of(listener, seat, cursor_button);
struct wlr_event_pointer_button *event = data;
struct wlr_pointer_button_event *event = data;
wlr_seat_pointer_notify_button(seat->seat, event->time_msec, event->button, event->state);
press_cursor_button(seat, event->device, event->time_msec, event->button, event->state, seat->cursor->x,
press_cursor_button(seat, &event->pointer->base, event->time_msec, event->button, event->state, seat->cursor->x,
seat->cursor->y);
wlr_idle_notify_activity(seat->server->idle, seat->seat);
wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}
static void
process_cursor_motion(struct cg_seat *seat, uint32_t time)
process_cursor_motion(struct cg_seat *seat, uint32_t time_msec, double dx, double dy, double dx_unaccel,
double dy_unaccel)
{
double sx, sy;
struct wlr_seat *wlr_seat = seat->seat;
struct wlr_surface *surface = NULL;
struct cg_view *view = desktop_view_at(seat->server, seat->cursor->x, seat->cursor->y, &surface, &sx, &sy);
if (!view) {
wlr_seat_pointer_clear_focus(wlr_seat);
} else {
wlr_seat_pointer_notify_enter(wlr_seat, surface, sx, sy);
wlr_seat_pointer_notify_motion(wlr_seat, time_msec, sx, sy);
}
bool focus_changed = wlr_seat->pointer_state.focused_surface != surface;
if (!focus_changed && time > 0) {
wlr_seat_pointer_notify_motion(wlr_seat, time, sx, sy);
}
if (dx != 0 || dy != 0) {
wlr_relative_pointer_manager_v1_send_relative_motion(seat->server->relative_pointer_manager, wlr_seat,
(uint64_t) time_msec * 1000, dx, dy, dx_unaccel,
dy_unaccel);
}
struct cg_drag_icon *drag_icon;
@ -580,38 +644,36 @@ process_cursor_motion(struct cg_seat *seat, uint32_t time)
drag_icon_update_position(drag_icon);
}
wlr_idle_notify_activity(seat->server->idle, seat->seat);
wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}
static void
handle_cursor_motion_absolute(struct wl_listener *listener, void *data)
{
struct cg_seat *seat = wl_container_of(listener, seat, cursor_motion_absolute);
struct wlr_event_pointer_motion_absolute *event = data;
struct wlr_pointer_motion_absolute_event *event = data;
wlr_cursor_warp_absolute(seat->cursor, event->device, event->x, event->y);
process_cursor_motion(seat, event->time_msec);
wlr_idle_notify_activity(seat->server->idle, seat->seat);
double lx, ly;
wlr_cursor_absolute_to_layout_coords(seat->cursor, &event->pointer->base, event->x, event->y, &lx, &ly);
double dx = lx - seat->cursor->x;
double dy = ly - seat->cursor->y;
wlr_cursor_warp_absolute(seat->cursor, &event->pointer->base, event->x, event->y);
process_cursor_motion(seat, event->time_msec, dx, dy, dx, dy);
wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}
static void
handle_cursor_motion(struct wl_listener *listener, void *data)
handle_cursor_motion_relative(struct wl_listener *listener, void *data)
{
struct cg_seat *seat = wl_container_of(listener, seat, cursor_motion);
struct wlr_event_pointer_motion *event = data;
struct cg_seat *seat = wl_container_of(listener, seat, cursor_motion_relative);
struct wlr_pointer_motion_event *event = data;
wlr_cursor_move(seat->cursor, event->device, event->delta_x, event->delta_y);
process_cursor_motion(seat, event->time_msec);
wlr_idle_notify_activity(seat->server->idle, seat->seat);
}
static void
drag_icon_damage(struct cg_drag_icon *drag_icon)
{
struct cg_output *output;
wl_list_for_each (output, &drag_icon->seat->server->outputs, link) {
output_damage_surface(output, drag_icon->wlr_drag_icon->surface, drag_icon->lx, drag_icon->ly, true);
}
wlr_cursor_move(seat->cursor, &event->pointer->base, event->delta_x, event->delta_y);
process_cursor_motion(seat, event->time_msec, event->delta_x, event->delta_y, event->unaccel_dx,
event->unaccel_dy);
wlr_idle_notifier_v1_notify_activity(seat->server->idle, seat->seat);
}
static void
@ -621,8 +683,6 @@ drag_icon_update_position(struct cg_drag_icon *drag_icon)
struct cg_seat *seat = drag_icon->seat;
struct wlr_touch_point *point;
drag_icon_damage(drag_icon);
switch (wlr_icon->drag->grab_type) {
case WLR_DRAG_GRAB_KEYBOARD:
return;
@ -640,7 +700,7 @@ drag_icon_update_position(struct cg_drag_icon *drag_icon)
break;
}
drag_icon_damage(drag_icon);
wlr_scene_node_set_position(&drag_icon->scene_tree->node, drag_icon->lx, drag_icon->ly);
}
static void
@ -650,6 +710,7 @@ handle_drag_icon_destroy(struct wl_listener *listener, void *data)
wl_list_remove(&drag_icon->link);
wl_list_remove(&drag_icon->destroy.link);
wlr_scene_node_destroy(&drag_icon->scene_tree->node);
free(drag_icon);
}
@ -692,6 +753,11 @@ handle_start_drag(struct wl_listener *listener, void *data)
}
drag_icon->seat = seat;
drag_icon->wlr_drag_icon = wlr_drag_icon;
drag_icon->scene_tree = wlr_scene_subsurface_tree_create(&seat->server->scene->tree, wlr_drag_icon->surface);
if (!drag_icon->scene_tree) {
free(drag_icon);
return;
}
drag_icon->destroy.notify = handle_drag_icon_destroy;
wl_signal_add(&wlr_drag_icon->events.destroy, &drag_icon->destroy);
@ -706,7 +772,7 @@ handle_destroy(struct wl_listener *listener, void *data)
{
struct cg_seat *seat = wl_container_of(listener, seat, destroy);
wl_list_remove(&seat->destroy.link);
wl_list_remove(&seat->cursor_motion.link);
wl_list_remove(&seat->cursor_motion_relative.link);
wl_list_remove(&seat->cursor_motion_absolute.link);
wl_list_remove(&seat->cursor_button.link);
wl_list_remove(&seat->cursor_axis.link);
@ -714,6 +780,7 @@ handle_destroy(struct wl_listener *listener, void *data)
wl_list_remove(&seat->touch_down.link);
wl_list_remove(&seat->touch_up.link);
wl_list_remove(&seat->touch_motion.link);
wl_list_remove(&seat->touch_frame.link);
wl_list_remove(&seat->request_set_cursor.link);
wl_list_remove(&seat->request_set_selection.link);
wl_list_remove(&seat->request_set_primary_selection.link);
@ -779,8 +846,8 @@ seat_create(struct cg_server *server, struct wlr_backend *backend)
}
}
seat->cursor_motion.notify = handle_cursor_motion;
wl_signal_add(&seat->cursor->events.motion, &seat->cursor_motion);
seat->cursor_motion_relative.notify = handle_cursor_motion_relative;
wl_signal_add(&seat->cursor->events.motion, &seat->cursor_motion_relative);
seat->cursor_motion_absolute.notify = handle_cursor_motion_absolute;
wl_signal_add(&seat->cursor->events.motion_absolute, &seat->cursor_motion_absolute);
seat->cursor_button.notify = handle_cursor_button;
@ -796,6 +863,8 @@ seat_create(struct cg_server *server, struct wlr_backend *backend)
wl_signal_add(&seat->cursor->events.touch_up, &seat->touch_up);
seat->touch_motion.notify = handle_touch_motion;
wl_signal_add(&seat->cursor->events.touch_motion, &seat->touch_motion);
seat->touch_frame.notify = handle_touch_frame;
wl_signal_add(&seat->cursor->events.touch_frame, &seat->touch_frame);
seat->request_set_cursor.notify = handle_request_set_cursor;
wl_signal_add(&seat->seat->events.request_set_cursor, &seat->request_set_cursor);
@ -812,6 +881,9 @@ seat_create(struct cg_server *server, struct wlr_backend *backend)
seat->new_input.notify = handle_new_input;
wl_signal_add(&backend->events.new_input, &seat->new_input);
server->new_virtual_keyboard.notify = handle_virtual_keyboard;
server->new_virtual_pointer.notify = handle_virtual_pointer;
wl_list_init(&seat->drag_icons);
seat->request_start_drag.notify = handle_request_start_drag;
wl_signal_add(&seat->seat->events.request_start_drag, &seat->request_start_drag);
@ -831,6 +903,11 @@ seat_destroy(struct cg_seat *seat)
wl_list_remove(&seat->request_start_drag.link);
wl_list_remove(&seat->start_drag.link);
struct cg_keyboard_group *keyboard_group, *keyboard_group_tmp;
wl_list_for_each_safe (keyboard_group, keyboard_group_tmp, &seat->keyboard_groups, link) {
keyboard_group_destroy(keyboard_group);
}
// Destroying the wlr seat will trigger the destroy handler on our seat,
// which will in turn free it.
wlr_seat_destroy(seat->seat);
@ -840,7 +917,10 @@ struct cg_view *
seat_get_focus(struct cg_seat *seat)
{
struct wlr_surface *prev_surface = seat->seat->keyboard_state.focused_surface;
return view_from_wlr_surface(seat->server, prev_surface);
if (!prev_surface) {
return NULL;
}
return view_from_wlr_surface(prev_surface);
}
void
@ -857,7 +937,7 @@ seat_set_focus(struct cg_seat *seat, struct cg_view *view)
#if CAGE_HAS_XWAYLAND
if (view->type == CAGE_XWAYLAND_VIEW) {
struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view);
if (!wlr_xwayland_or_surface_wants_focus(xwayland_view->xwayland_surface)) {
if (!wlr_xwayland_surface_override_redirect_wants_focus(xwayland_view->xwayland_surface)) {
return;
}
}
@ -890,5 +970,14 @@ seat_set_focus(struct cg_seat *seat, struct cg_view *view)
wlr_seat_keyboard_notify_enter(wlr_seat, view->wlr_surface, NULL, 0, NULL);
}
process_cursor_motion(seat, -1);
process_cursor_motion(seat, -1, 0, 0, 0, 0);
}
void
seat_center_cursor(struct cg_seat *seat)
{
/* Place the cursor in the center of the output layout. */
struct wlr_box layout_box;
wlr_output_layout_get_box(seat->server->output_layout, NULL, &layout_box);
wlr_cursor_warp(seat->cursor, NULL, layout_box.width / 2, layout_box.height / 2);
}

10
seat.h
View file

@ -27,7 +27,7 @@ struct cg_seat {
struct wlr_cursor *cursor;
struct wlr_xcursor_manager *xcursor_manager;
struct wl_listener cursor_motion;
struct wl_listener cursor_motion_relative;
struct wl_listener cursor_motion_absolute;
struct wl_listener cursor_button;
struct wl_listener cursor_axis;
@ -39,6 +39,7 @@ struct cg_seat {
struct wl_listener touch_down;
struct wl_listener touch_up;
struct wl_listener touch_motion;
struct wl_listener touch_frame;
struct wl_list drag_icons;
struct wl_listener request_start_drag;
@ -55,12 +56,13 @@ struct cg_keyboard_group {
struct wl_listener key;
struct wl_listener modifiers;
struct wl_list link; // cg_seat::keyboard_groups
bool is_virtual;
};
struct cg_pointer {
struct wl_list link; // seat::pointers
struct cg_seat *seat;
struct wlr_input_device *device;
struct wlr_pointer *pointer;
struct wl_listener destroy;
};
@ -68,7 +70,7 @@ struct cg_pointer {
struct cg_touch {
struct wl_list link; // seat::touch
struct cg_seat *seat;
struct wlr_input_device *device;
struct wlr_touch *touch;
struct wl_listener destroy;
};
@ -77,6 +79,7 @@ struct cg_drag_icon {
struct wl_list link; // seat::drag_icons
struct cg_seat *seat;
struct wlr_drag_icon *wlr_drag_icon;
struct wlr_scene_tree *scene_tree;
/* The drag icon has a position in layout coordinates. */
double lx, ly;
@ -88,5 +91,6 @@ struct cg_seat *seat_create(struct cg_server *server, struct wlr_backend *backen
void seat_destroy(struct cg_seat *seat);
struct cg_view *seat_get_focus(struct cg_seat *seat);
void seat_set_focus(struct cg_seat *seat, struct cg_view *view);
void seat_center_cursor(struct cg_seat *seat);
#endif

View file

@ -4,18 +4,17 @@
#include "config.h"
#include <wayland-server-core.h>
#include <wlr/types/wlr_idle.h>
#include <wlr/types/wlr_idle_inhibit_v1.h>
#include <wlr/types/wlr_idle_notify_v1.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_relative_pointer_v1.h>
#include <wlr/types/wlr_xdg_decoration_v1.h>
#include <wlr/util/log.h>
#if CAGE_HAS_XWAYLAND
#include <wlr/xwayland.h>
#endif
#include "output.h"
#include "seat.h"
#include "view.h"
enum cg_multi_output_mode {
CAGE_MULTI_OUTPUT_MODE_EXTEND,
CAGE_MULTI_OUTPUT_MODE_LAST,
@ -25,32 +24,50 @@ struct cg_server {
struct wl_display *wl_display;
struct wl_list views;
struct wlr_backend *backend;
struct wlr_renderer *renderer;
struct wlr_allocator *allocator;
struct wlr_session *session;
struct wl_listener display_destroy;
struct cg_seat *seat;
struct wlr_idle *idle;
struct wlr_idle_notifier_v1 *idle;
struct wlr_idle_inhibit_manager_v1 *idle_inhibit_v1;
struct wl_listener new_idle_inhibitor_v1;
struct wl_list inhibitors;
enum cg_multi_output_mode output_mode;
struct wlr_output_layout *output_layout;
struct wlr_scene_output_layout *scene_output_layout;
struct wlr_scene *scene;
/* Includes disabled outputs; depending on the output_mode
* some outputs may be disabled. */
struct wl_list outputs; // cg_output::link
struct wl_listener new_output;
struct wl_listener output_layout_change;
struct wl_listener xdg_toplevel_decoration;
struct wl_listener new_xdg_shell_surface;
struct wl_listener new_xdg_toplevel;
struct wl_listener new_xdg_popup;
struct wl_listener new_virtual_keyboard;
struct wl_listener new_virtual_pointer;
#if CAGE_HAS_XWAYLAND
struct wl_listener new_xwayland_surface;
#endif
struct wlr_output_manager_v1 *output_manager_v1;
struct wl_listener output_manager_apply;
struct wl_listener output_manager_test;
struct wlr_relative_pointer_manager_v1 *relative_pointer_manager;
bool xdg_decoration;
bool allow_vt_switch;
enum wl_output_transform output_transform;
#ifdef DEBUG
bool debug_damage_tracking;
#endif
bool return_app_code;
bool terminated;
enum wlr_log_importance log_level;
};
void server_terminate(struct cg_server *server);
#endif

36
util.c
View file

@ -1,36 +0,0 @@
/*
* Cage: A Wayland kiosk.
*
* Copyright (C) 2019 The Sway authors
*
* See the LICENSE file accompanying this file.
*/
#include <wlr/types/wlr_box.h>
#include "util.h"
int
scale_length(int length, int offset, float scale)
{
/**
* One does not simply multiply the width by the scale. We allow fractional
* scaling, which means the resulting scaled width might be a decimal.
* So we round it.
*
* But even this can produce undesirable results depending on the X or Y
* offset of the box. For example, with a scale of 1.5, a box with
* width=1 should not scale to 2px if its X coordinate is 1, because the
* X coordinate would have scaled to 2px.
*/
return round((offset + length) * scale) - round(offset * scale);
}
void
scale_box(struct wlr_box *box, float scale)
{
box->width = scale_length(box->width, box->x, scale);
box->height = scale_length(box->height, box->y, scale);
box->x = round(box->x * scale);
box->y = round(box->y * scale);
}

11
util.h
View file

@ -1,11 +0,0 @@
#ifndef CG_UTIL_H
#define CG_UTIL_H
#include <wlr/types/wlr_box.h>
/** Apply scale to a width or height. */
int scale_length(int length, int offset, float scale);
void scale_box(struct wlr_box *box, float scale);
#endif

187
view.c
View file

@ -1,20 +1,20 @@
/*
* Cage: A Wayland kiosk.
*
* Copyright (C) 2018-2020 Jente Hidskes
* Copyright (C) 2018-2021 Jente Hidskes
*
* See the LICENSE file accompanying this file.
*/
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_box.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_surface.h>
#include <wlr/types/wlr_scene.h>
#include "output.h"
#include "seat.h"
@ -24,96 +24,6 @@
#include "xwayland.h"
#endif
static void
view_child_handle_commit(struct wl_listener *listener, void *data)
{
struct cg_view_child *child = wl_container_of(listener, child, commit);
view_damage_part(child->view);
}
static void subsurface_create(struct cg_view *view, struct wlr_subsurface *wlr_subsurface);
static void
view_child_handle_new_subsurface(struct wl_listener *listener, void *data)
{
struct cg_view_child *child = wl_container_of(listener, child, new_subsurface);
struct wlr_subsurface *wlr_subsurface = data;
subsurface_create(child->view, wlr_subsurface);
}
void
view_child_finish(struct cg_view_child *child)
{
if (!child) {
return;
}
view_damage_whole(child->view);
wl_list_remove(&child->link);
wl_list_remove(&child->commit.link);
wl_list_remove(&child->new_subsurface.link);
}
void
view_child_init(struct cg_view_child *child, struct cg_view *view, struct wlr_surface *wlr_surface)
{
child->view = view;
child->wlr_surface = wlr_surface;
child->commit.notify = view_child_handle_commit;
wl_signal_add(&wlr_surface->events.commit, &child->commit);
child->new_subsurface.notify = view_child_handle_new_subsurface;
wl_signal_add(&wlr_surface->events.new_subsurface, &child->new_subsurface);
wl_list_insert(&view->children, &child->link);
}
static void
subsurface_destroy(struct cg_view_child *child)
{
if (!child) {
return;
}
struct cg_subsurface *subsurface = (struct cg_subsurface *) child;
wl_list_remove(&subsurface->destroy.link);
view_child_finish(&subsurface->view_child);
free(subsurface);
}
static void
subsurface_handle_destroy(struct wl_listener *listener, void *data)
{
struct cg_subsurface *subsurface = wl_container_of(listener, subsurface, destroy);
struct cg_view_child *view_child = (struct cg_view_child *) subsurface;
subsurface_destroy(view_child);
}
static void
subsurface_create(struct cg_view *view, struct wlr_subsurface *wlr_subsurface)
{
struct cg_subsurface *subsurface = calloc(1, sizeof(struct cg_subsurface));
if (!subsurface) {
return;
}
view_child_init(&subsurface->view_child, view, wlr_subsurface->surface);
subsurface->view_child.destroy = subsurface_destroy;
subsurface->wlr_subsurface = wlr_subsurface;
subsurface->destroy.notify = subsurface_handle_destroy;
wl_signal_add(&wlr_subsurface->events.destroy, &subsurface->destroy);
}
static void
handle_new_subsurface(struct wl_listener *listener, void *data)
{
struct cg_view *view = wl_container_of(listener, view, new_subsurface);
struct wlr_subsurface *wlr_subsurface = data;
subsurface_create(view, wlr_subsurface);
}
char *
view_get_title(struct cg_view *view)
{
@ -136,24 +46,6 @@ view_is_transient_for(struct cg_view *child, struct cg_view *parent)
return child->impl->is_transient_for(child, parent);
}
void
view_damage_part(struct cg_view *view)
{
struct cg_output *output;
wl_list_for_each (output, &view->server->outputs, link) {
output_damage_surface(output, view->wlr_surface, view->lx, view->ly, false);
}
}
void
view_damage_whole(struct cg_view *view)
{
struct cg_output *output;
wl_list_for_each (output, &view->server->outputs, link) {
output_damage_surface(output, view->wlr_surface, view->lx, view->ly, true);
}
}
void
view_activate(struct cg_view *view, bool activate)
{
@ -174,6 +66,11 @@ view_maximize(struct cg_view *view, struct wlr_box *layout_box)
{
view->lx = layout_box->x;
view->ly = layout_box->y;
if (view->scene_tree) {
wlr_scene_node_set_position(&view->scene_tree->node, view->lx, view->ly);
}
view->impl->maximize(view, layout_box->width, layout_box->height);
}
@ -185,33 +82,32 @@ view_center(struct cg_view *view, struct wlr_box *layout_box)
view->lx = (layout_box->width - width) / 2;
view->ly = (layout_box->height - height) / 2;
if (view->scene_tree) {
wlr_scene_node_set_position(&view->scene_tree->node, view->lx, view->ly);
}
}
void
view_position(struct cg_view *view)
{
struct wlr_box *layout_box = wlr_output_layout_get_box(view->server->output_layout, NULL);
struct wlr_box layout_box;
wlr_output_layout_get_box(view->server->output_layout, NULL, &layout_box);
if (view_is_primary(view) || view_extends_output_layout(view, layout_box)) {
view_maximize(view, layout_box);
if (view_is_primary(view) || view_extends_output_layout(view, &layout_box)) {
view_maximize(view, &layout_box);
} else {
view_center(view, layout_box);
view_center(view, &layout_box);
}
}
void
view_for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data)
view_position_all(struct cg_server *server)
{
view->impl->for_each_surface(view, iterator, data);
}
void
view_for_each_popup_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data)
{
if (!view->impl->for_each_popup_surface) {
return;
struct cg_view *view;
wl_list_for_each (view, &server->views, link) {
view_position(view);
}
view->impl->for_each_popup_surface(view, iterator, data);
}
void
@ -219,28 +115,24 @@ view_unmap(struct cg_view *view)
{
wl_list_remove(&view->link);
wl_list_remove(&view->new_subsurface.link);
struct cg_view_child *child, *tmp;
wl_list_for_each_safe (child, tmp, &view->children, link) {
child->destroy(child);
}
wlr_scene_node_destroy(&view->scene_tree->node);
view->wlr_surface->data = NULL;
view->wlr_surface = NULL;
}
void
view_map(struct cg_view *view, struct wlr_surface *surface)
{
view->wlr_surface = surface;
struct wlr_subsurface *subsurface;
wl_list_for_each (subsurface, &view->wlr_surface->subsurfaces, parent_link) {
subsurface_create(view, subsurface);
view->scene_tree = wlr_scene_subsurface_tree_create(&view->server->scene->tree, surface);
if (!view->scene_tree) {
wl_resource_post_no_memory(surface->resource);
return;
}
view->scene_tree->node.data = view;
view->new_subsurface.notify = handle_new_subsurface;
wl_signal_add(&view->wlr_surface->events.new_subsurface, &view->new_subsurface);
view->wlr_surface = surface;
surface->data = view;
#if CAGE_HAS_XWAYLAND
/* We shouldn't position override-redirect windows. They set
@ -280,24 +172,11 @@ view_init(struct cg_view *view, struct cg_server *server, enum cg_view_type type
view->server = server;
view->type = type;
view->impl = impl;
wl_list_init(&view->children);
}
struct cg_view *
view_from_wlr_surface(struct cg_server *server, struct wlr_surface *surface)
view_from_wlr_surface(struct wlr_surface *surface)
{
struct cg_view *view;
wl_list_for_each (view, &server->views, link) {
if (view->wlr_surface == surface) {
return view;
}
}
return NULL;
}
struct wlr_surface *
view_wlr_surface_at(struct cg_view *view, double sx, double sy, double *sub_x, double *sub_y)
{
return view->impl->wlr_surface_at(view, sx, sy, sub_x, sub_y);
assert(surface);
return surface->data;
}

40
view.h
View file

@ -5,9 +5,9 @@
#include <stdbool.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_box.h>
#include <wlr/types/wlr_surface.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/box.h>
#if CAGE_HAS_XWAYLAND
#include <wlr/xwayland.h>
#endif
@ -24,16 +24,14 @@ enum cg_view_type {
struct cg_view {
struct cg_server *server;
struct wl_list link; // server::views
struct wl_list children; // cg_view_child::link
struct wlr_surface *wlr_surface;
struct wlr_scene_tree *scene_tree;
/* The view has a position in layout coordinates. */
int lx, ly;
enum cg_view_type type;
const struct cg_view_impl *impl;
struct wl_listener new_subsurface;
};
struct cg_view_impl {
@ -44,47 +42,19 @@ struct cg_view_impl {
void (*activate)(struct cg_view *view, bool activate);
void (*maximize)(struct cg_view *view, int output_width, int output_height);
void (*destroy)(struct cg_view *view);
void (*for_each_surface)(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data);
void (*for_each_popup_surface)(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data);
struct wlr_surface *(*wlr_surface_at)(struct cg_view *view, double sx, double sy, double *sub_x, double *sub_y);
};
struct cg_view_child {
struct cg_view *view;
struct wlr_surface *wlr_surface;
struct wl_list link;
struct wl_listener commit;
struct wl_listener new_subsurface;
void (*destroy)(struct cg_view_child *child);
};
struct cg_subsurface {
struct cg_view_child view_child;
struct wlr_subsurface *wlr_subsurface;
struct wl_listener destroy;
};
char *view_get_title(struct cg_view *view);
bool view_is_primary(struct cg_view *view);
bool view_is_transient_for(struct cg_view *child, struct cg_view *parent);
void view_damage_part(struct cg_view *view);
void view_damage_whole(struct cg_view *view);
void view_activate(struct cg_view *view, bool activate);
void view_position(struct cg_view *view);
void view_for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data);
void view_for_each_popup_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data);
void view_position_all(struct cg_server *server);
void view_unmap(struct cg_view *view);
void view_map(struct cg_view *view, struct wlr_surface *surface);
void view_destroy(struct cg_view *view);
void view_init(struct cg_view *view, struct cg_server *server, enum cg_view_type type, const struct cg_view_impl *impl);
struct cg_view *view_from_wlr_surface(struct cg_server *server, struct wlr_surface *surface);
struct wlr_surface *view_wlr_surface_at(struct cg_view *view, double sx, double sy, double *sub_x, double *sub_y);
void view_child_finish(struct cg_view_child *child);
void view_child_init(struct cg_view_child *child, struct cg_view *view, struct wlr_surface *wlr_surface);
struct cg_view *view_from_wlr_surface(struct wlr_surface *surface);
#endif

View file

@ -6,10 +6,11 @@
* See the LICENSE file accompanying this file.
*/
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_box.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h>
@ -18,21 +19,9 @@
#include "xdg_shell.h"
static void
xdg_decoration_handle_destroy(struct wl_listener *listener, void *data)
xdg_decoration_set_mode(struct cg_xdg_decoration *xdg_decoration)
{
struct cg_xdg_decoration *xdg_decoration = wl_container_of(listener, xdg_decoration, destroy);
wl_list_remove(&xdg_decoration->destroy.link);
wl_list_remove(&xdg_decoration->request_mode.link);
free(xdg_decoration);
}
static void
xdg_decoration_handle_request_mode(struct wl_listener *listener, void *data)
{
struct cg_xdg_decoration *xdg_decoration = wl_container_of(listener, xdg_decoration, request_mode);
enum wlr_xdg_toplevel_decoration_v1_mode mode;
if (xdg_decoration->server->xdg_decoration) {
mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;
} else {
@ -42,104 +31,84 @@ xdg_decoration_handle_request_mode(struct wl_listener *listener, void *data)
}
static void
xdg_popup_destroy(struct cg_view_child *child)
xdg_decoration_handle_destroy(struct wl_listener *listener, void *data)
{
if (!child) {
struct cg_xdg_decoration *xdg_decoration = wl_container_of(listener, xdg_decoration, destroy);
wl_list_remove(&xdg_decoration->destroy.link);
wl_list_remove(&xdg_decoration->commit.link);
wl_list_remove(&xdg_decoration->request_mode.link);
free(xdg_decoration);
}
static void
xdg_decoration_handle_commit(struct wl_listener *listener, void *data)
{
struct cg_xdg_decoration *xdg_decoration = wl_container_of(listener, xdg_decoration, commit);
if (xdg_decoration->wlr_decoration->toplevel->base->initial_commit) {
xdg_decoration_set_mode(xdg_decoration);
}
}
static void
xdg_decoration_handle_request_mode(struct wl_listener *listener, void *data)
{
struct cg_xdg_decoration *xdg_decoration = wl_container_of(listener, xdg_decoration, request_mode);
if (xdg_decoration->wlr_decoration->toplevel->base->initialized) {
xdg_decoration_set_mode(xdg_decoration);
}
}
static struct cg_view *
popup_get_view(struct wlr_xdg_popup *popup)
{
while (true) {
if (popup->parent == NULL) {
return NULL;
}
struct wlr_xdg_surface *xdg_surface = wlr_xdg_surface_try_from_wlr_surface(popup->parent);
if (xdg_surface == NULL) {
return NULL;
}
switch (xdg_surface->role) {
case WLR_XDG_SURFACE_ROLE_TOPLEVEL:
return xdg_surface->data;
case WLR_XDG_SURFACE_ROLE_POPUP:
popup = xdg_surface->popup;
break;
case WLR_XDG_SURFACE_ROLE_NONE:
return NULL;
}
}
}
static void
popup_unconstrain(struct wlr_xdg_popup *popup)
{
struct cg_view *view = popup_get_view(popup);
if (view == NULL) {
return;
}
struct cg_xdg_popup *popup = (struct cg_xdg_popup *) child;
wl_list_remove(&popup->destroy.link);
wl_list_remove(&popup->map.link);
wl_list_remove(&popup->unmap.link);
wl_list_remove(&popup->new_popup.link);
view_child_finish(&popup->view_child);
free(popup);
}
static void
handle_xdg_popup_map(struct wl_listener *listener, void *data)
{
struct cg_xdg_popup *popup = wl_container_of(listener, popup, map);
view_damage_whole(popup->view_child.view);
}
static void
handle_xdg_popup_unmap(struct wl_listener *listener, void *data)
{
struct cg_xdg_popup *popup = wl_container_of(listener, popup, unmap);
view_damage_whole(popup->view_child.view);
}
static void
handle_xdg_popup_destroy(struct wl_listener *listener, void *data)
{
struct cg_xdg_popup *popup = wl_container_of(listener, popup, destroy);
struct cg_view_child *view_child = (struct cg_view_child *) popup;
xdg_popup_destroy(view_child);
}
static void xdg_popup_create(struct cg_view *view, struct wlr_xdg_popup *wlr_popup);
static void
popup_handle_new_xdg_popup(struct wl_listener *listener, void *data)
{
struct cg_xdg_popup *popup = wl_container_of(listener, popup, new_popup);
struct wlr_xdg_popup *wlr_popup = data;
xdg_popup_create(popup->view_child.view, wlr_popup);
}
static void
popup_unconstrain(struct cg_xdg_popup *popup)
{
struct cg_view *view = popup->view_child.view;
struct cg_server *server = view->server;
struct wlr_box *popup_box = &popup->wlr_popup->geometry;
struct wlr_box *popup_box = &popup->current.geometry;
struct wlr_output_layout *output_layout = server->output_layout;
struct wlr_output *wlr_output =
wlr_output_layout_output_at(output_layout, view->lx + popup_box->x, view->ly + popup_box->y);
struct wlr_box *output_box = wlr_output_layout_get_box(output_layout, wlr_output);
struct wlr_box output_box;
wlr_output_layout_get_box(output_layout, wlr_output, &output_box);
struct wlr_box output_toplevel_box = {
.x = output_box->x - view->lx,
.y = output_box->y - view->ly,
.width = output_box->width,
.height = output_box->height,
.x = output_box.x - view->lx,
.y = output_box.y - view->ly,
.width = output_box.width,
.height = output_box.height,
};
wlr_xdg_popup_unconstrain_from_box(popup->wlr_popup, &output_toplevel_box);
}
static void
xdg_popup_create(struct cg_view *view, struct wlr_xdg_popup *wlr_popup)
{
struct cg_xdg_popup *popup = calloc(1, sizeof(struct cg_xdg_popup));
if (!popup) {
return;
}
popup->wlr_popup = wlr_popup;
view_child_init(&popup->view_child, view, wlr_popup->base->surface);
popup->view_child.destroy = xdg_popup_destroy;
popup->destroy.notify = handle_xdg_popup_destroy;
wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy);
popup->map.notify = handle_xdg_popup_map;
wl_signal_add(&wlr_popup->base->events.map, &popup->map);
popup->unmap.notify = handle_xdg_popup_unmap;
wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap);
popup->new_popup.notify = popup_handle_new_xdg_popup;
wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup);
popup_unconstrain(popup);
}
static void
handle_new_xdg_popup(struct wl_listener *listener, void *data)
{
struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, new_popup);
struct wlr_xdg_popup *wlr_popup = data;
xdg_popup_create(&xdg_shell_view->view, wlr_popup);
wlr_xdg_popup_unconstrain_from_box(popup, &output_toplevel_box);
}
static struct cg_xdg_shell_view *
@ -152,27 +121,26 @@ static char *
get_title(struct cg_view *view)
{
struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view);
return xdg_shell_view->xdg_surface->toplevel->title;
return xdg_shell_view->xdg_toplevel->title;
}
static void
get_geometry(struct cg_view *view, int *width_out, int *height_out)
{
struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view);
struct wlr_box geom;
struct wlr_xdg_surface *xdg_surface = xdg_shell_view->xdg_toplevel->base;
wlr_xdg_surface_get_geometry(xdg_shell_view->xdg_surface, &geom);
*width_out = geom.width;
*height_out = geom.height;
*width_out = xdg_surface->geometry.width;
*height_out = xdg_surface->geometry.height;
}
static bool
is_primary(struct cg_view *view)
{
struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view);
struct wlr_xdg_surface *parent = xdg_shell_view->xdg_surface->toplevel->parent;
/* FIXME: role is 0? */
return parent == NULL; /*&& role == WLR_XDG_SURFACE_ROLE_TOPLEVEL */
struct wlr_xdg_toplevel *parent = xdg_shell_view->xdg_toplevel->parent;
return parent == NULL;
}
static bool
@ -182,14 +150,13 @@ is_transient_for(struct cg_view *child, struct cg_view *parent)
return false;
}
struct cg_xdg_shell_view *_child = xdg_shell_view_from_view(child);
struct wlr_xdg_surface *xdg_surface = _child->xdg_surface;
struct wlr_xdg_toplevel *xdg_toplevel = _child->xdg_toplevel;
struct cg_xdg_shell_view *_parent = xdg_shell_view_from_view(parent);
struct wlr_xdg_surface *parent_xdg_surface = _parent->xdg_surface;
while (xdg_surface) {
if (xdg_surface->toplevel->parent == parent_xdg_surface) {
while (xdg_toplevel) {
if (xdg_toplevel->parent == _parent->xdg_toplevel) {
return true;
}
xdg_surface = xdg_surface->toplevel->parent;
xdg_toplevel = xdg_toplevel->parent;
}
return false;
}
@ -198,15 +165,15 @@ static void
activate(struct cg_view *view, bool activate)
{
struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view);
wlr_xdg_toplevel_set_activated(xdg_shell_view->xdg_surface, activate);
wlr_xdg_toplevel_set_activated(xdg_shell_view->xdg_toplevel, activate);
}
static void
maximize(struct cg_view *view, int output_width, int output_height)
{
struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view);
wlr_xdg_toplevel_set_size(xdg_shell_view->xdg_surface, output_width, output_height);
wlr_xdg_toplevel_set_maximized(xdg_shell_view->xdg_surface, true);
wlr_xdg_toplevel_set_size(xdg_shell_view->xdg_toplevel, output_width, output_height);
wlr_xdg_toplevel_set_maximized(xdg_shell_view->xdg_toplevel, true);
}
static void
@ -217,81 +184,68 @@ destroy(struct cg_view *view)
}
static void
for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data)
{
struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view);
wlr_xdg_surface_for_each_surface(xdg_shell_view->xdg_surface, iterator, data);
}
static void
for_each_popup_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data)
{
struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view);
wlr_xdg_surface_for_each_popup_surface(xdg_shell_view->xdg_surface, iterator, data);
}
static struct wlr_surface *
wlr_surface_at(struct cg_view *view, double sx, double sy, double *sub_x, double *sub_y)
{
struct cg_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view);
return wlr_xdg_surface_surface_at(xdg_shell_view->xdg_surface, sx, sy, sub_x, sub_y);
}
static void
handle_xdg_shell_surface_request_fullscreen(struct wl_listener *listener, void *data)
handle_xdg_toplevel_request_fullscreen(struct wl_listener *listener, void *data)
{
struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, request_fullscreen);
struct wlr_xdg_toplevel_set_fullscreen_event *event = data;
wlr_xdg_toplevel_set_fullscreen(xdg_shell_view->xdg_surface, event->fullscreen);
/**
* Certain clients do not like figuring out their own window geometry if they
* display in fullscreen mode, so we set it here.
*/
struct wlr_box layout_box;
wlr_output_layout_get_box(xdg_shell_view->view.server->output_layout, NULL, &layout_box);
wlr_xdg_toplevel_set_size(xdg_shell_view->xdg_toplevel, layout_box.width, layout_box.height);
wlr_xdg_toplevel_set_fullscreen(xdg_shell_view->xdg_toplevel,
xdg_shell_view->xdg_toplevel->requested.fullscreen);
}
static void
handle_xdg_shell_surface_commit(struct wl_listener *listener, void *data)
{
struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, commit);
struct cg_view *view = &xdg_shell_view->view;
view_damage_part(view);
}
static void
handle_xdg_shell_surface_unmap(struct wl_listener *listener, void *data)
handle_xdg_toplevel_unmap(struct wl_listener *listener, void *data)
{
struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, unmap);
struct cg_view *view = &xdg_shell_view->view;
view_damage_whole(view);
wl_list_remove(&xdg_shell_view->commit.link);
view_unmap(view);
}
static void
handle_xdg_shell_surface_map(struct wl_listener *listener, void *data)
handle_xdg_toplevel_map(struct wl_listener *listener, void *data)
{
struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, map);
struct cg_view *view = &xdg_shell_view->view;
xdg_shell_view->commit.notify = handle_xdg_shell_surface_commit;
wl_signal_add(&xdg_shell_view->xdg_surface->surface->events.commit, &xdg_shell_view->commit);
view_map(view, xdg_shell_view->xdg_surface->surface);
view_damage_whole(view);
view_map(view, xdg_shell_view->xdg_toplevel->base->surface);
}
static void
handle_xdg_shell_surface_destroy(struct wl_listener *listener, void *data)
handle_xdg_toplevel_commit(struct wl_listener *listener, void *data)
{
struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, commit);
if (!xdg_shell_view->xdg_toplevel->base->initial_commit) {
return;
}
wlr_xdg_toplevel_set_wm_capabilities(xdg_shell_view->xdg_toplevel, XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN);
/* When an xdg_surface performs an initial commit, the compositor must
* reply with a configure so the client can map the surface. */
view_position(&xdg_shell_view->view);
}
static void
handle_xdg_toplevel_destroy(struct wl_listener *listener, void *data)
{
struct cg_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, destroy);
struct cg_view *view = &xdg_shell_view->view;
wl_list_remove(&xdg_shell_view->commit.link);
wl_list_remove(&xdg_shell_view->map.link);
wl_list_remove(&xdg_shell_view->unmap.link);
wl_list_remove(&xdg_shell_view->destroy.link);
wl_list_remove(&xdg_shell_view->request_fullscreen.link);
wl_list_remove(&xdg_shell_view->new_popup.link);
xdg_shell_view->xdg_surface = NULL;
xdg_shell_view->xdg_toplevel = NULL;
view_destroy(view);
}
@ -304,20 +258,13 @@ static const struct cg_view_impl xdg_shell_view_impl = {
.activate = activate,
.maximize = maximize,
.destroy = destroy,
.for_each_surface = for_each_surface,
.for_each_popup_surface = for_each_popup_surface,
.wlr_surface_at = wlr_surface_at,
};
void
handle_xdg_shell_surface_new(struct wl_listener *listener, void *data)
handle_new_xdg_toplevel(struct wl_listener *listener, void *data)
{
struct cg_server *server = wl_container_of(listener, server, new_xdg_shell_surface);
struct wlr_xdg_surface *xdg_surface = data;
if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
return;
}
struct cg_server *server = wl_container_of(listener, server, new_xdg_toplevel);
struct wlr_xdg_toplevel *toplevel = data;
struct cg_xdg_shell_view *xdg_shell_view = calloc(1, sizeof(struct cg_xdg_shell_view));
if (!xdg_shell_view) {
@ -326,18 +273,105 @@ handle_xdg_shell_surface_new(struct wl_listener *listener, void *data)
}
view_init(&xdg_shell_view->view, server, CAGE_XDG_SHELL_VIEW, &xdg_shell_view_impl);
xdg_shell_view->xdg_surface = xdg_surface;
xdg_shell_view->xdg_toplevel = toplevel;
xdg_shell_view->map.notify = handle_xdg_shell_surface_map;
wl_signal_add(&xdg_surface->events.map, &xdg_shell_view->map);
xdg_shell_view->unmap.notify = handle_xdg_shell_surface_unmap;
wl_signal_add(&xdg_surface->events.unmap, &xdg_shell_view->unmap);
xdg_shell_view->destroy.notify = handle_xdg_shell_surface_destroy;
wl_signal_add(&xdg_surface->events.destroy, &xdg_shell_view->destroy);
xdg_shell_view->request_fullscreen.notify = handle_xdg_shell_surface_request_fullscreen;
wl_signal_add(&xdg_surface->toplevel->events.request_fullscreen, &xdg_shell_view->request_fullscreen);
xdg_shell_view->new_popup.notify = handle_new_xdg_popup;
wl_signal_add(&xdg_surface->events.new_popup, &xdg_shell_view->new_popup);
xdg_shell_view->commit.notify = handle_xdg_toplevel_commit;
wl_signal_add(&toplevel->base->surface->events.commit, &xdg_shell_view->commit);
xdg_shell_view->map.notify = handle_xdg_toplevel_map;
wl_signal_add(&toplevel->base->surface->events.map, &xdg_shell_view->map);
xdg_shell_view->unmap.notify = handle_xdg_toplevel_unmap;
wl_signal_add(&toplevel->base->surface->events.unmap, &xdg_shell_view->unmap);
xdg_shell_view->destroy.notify = handle_xdg_toplevel_destroy;
wl_signal_add(&toplevel->events.destroy, &xdg_shell_view->destroy);
xdg_shell_view->request_fullscreen.notify = handle_xdg_toplevel_request_fullscreen;
wl_signal_add(&toplevel->events.request_fullscreen, &xdg_shell_view->request_fullscreen);
toplevel->base->data = xdg_shell_view;
}
static void
popup_handle_destroy(struct wl_listener *listener, void *data)
{
struct cg_xdg_popup *popup = wl_container_of(listener, popup, destroy);
wl_list_remove(&popup->destroy.link);
wl_list_remove(&popup->commit.link);
wl_list_remove(&popup->reposition.link);
free(popup);
}
static void
popup_handle_commit(struct wl_listener *listener, void *data)
{
struct cg_xdg_popup *popup = wl_container_of(listener, popup, commit);
if (popup->xdg_popup->base->initial_commit) {
popup_unconstrain(popup->xdg_popup);
}
}
static void
popup_handle_reposition(struct wl_listener *listener, void *data)
{
struct cg_xdg_popup *popup = wl_container_of(listener, popup, reposition);
popup_unconstrain(popup->xdg_popup);
}
void
handle_new_xdg_popup(struct wl_listener *listener, void *data)
{
struct cg_server *server = wl_container_of(listener, server, new_xdg_popup);
struct wlr_xdg_popup *wlr_popup = data;
struct cg_view *view = popup_get_view(wlr_popup);
if (view == NULL) {
return;
}
struct wlr_scene_tree *parent_scene_tree = NULL;
struct wlr_xdg_surface *parent = wlr_xdg_surface_try_from_wlr_surface(wlr_popup->parent);
if (parent == NULL) {
return;
}
switch (parent->role) {
case WLR_XDG_SURFACE_ROLE_TOPLEVEL:;
parent_scene_tree = view->scene_tree;
break;
case WLR_XDG_SURFACE_ROLE_POPUP:
parent_scene_tree = parent->data;
break;
case WLR_XDG_SURFACE_ROLE_NONE:
break;
}
if (parent_scene_tree == NULL) {
return;
}
struct cg_xdg_popup *popup = calloc(1, sizeof(*popup));
if (popup == NULL) {
wlr_log(WLR_ERROR, "Failed to allocate popup");
return;
}
popup->xdg_popup = wlr_popup;
popup->destroy.notify = popup_handle_destroy;
wl_signal_add(&wlr_popup->events.destroy, &popup->destroy);
popup->commit.notify = popup_handle_commit;
wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit);
popup->reposition.notify = popup_handle_reposition;
wl_signal_add(&wlr_popup->events.reposition, &popup->reposition);
struct wlr_scene_tree *popup_scene_tree = wlr_scene_xdg_surface_create(parent_scene_tree, wlr_popup->base);
if (popup_scene_tree == NULL) {
wlr_log(WLR_ERROR, "Failed to allocate scene-graph node for XDG popup");
free(popup);
return;
}
wlr_popup->base->data = popup_scene_tree;
}
void
@ -356,8 +390,8 @@ handle_xdg_toplevel_decoration(struct wl_listener *listener, void *data)
xdg_decoration->destroy.notify = xdg_decoration_handle_destroy;
wl_signal_add(&wlr_decoration->events.destroy, &xdg_decoration->destroy);
xdg_decoration->commit.notify = xdg_decoration_handle_commit;
wl_signal_add(&wlr_decoration->toplevel->base->surface->events.commit, &xdg_decoration->commit);
xdg_decoration->request_mode.notify = xdg_decoration_handle_request_mode;
wl_signal_add(&wlr_decoration->events.request_mode, &xdg_decoration->request_mode);
xdg_decoration_handle_request_mode(&xdg_decoration->request_mode, wlr_decoration);
}

View file

@ -9,34 +9,33 @@
struct cg_xdg_shell_view {
struct cg_view view;
struct wlr_xdg_surface *xdg_surface;
struct wlr_xdg_toplevel *xdg_toplevel;
struct wl_listener destroy;
struct wl_listener unmap;
struct wl_listener map;
struct wl_listener commit;
struct wl_listener request_fullscreen;
struct wl_listener new_popup;
};
struct cg_xdg_popup {
struct cg_view_child view_child;
struct wlr_xdg_popup *wlr_popup;
struct wl_listener destroy;
struct wl_listener map;
struct wl_listener unmap;
struct wl_listener new_popup;
struct wl_listener map;
struct wl_listener request_fullscreen;
};
struct cg_xdg_decoration {
struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration;
struct cg_server *server;
struct wl_listener destroy;
struct wl_listener commit;
struct wl_listener request_mode;
};
void handle_xdg_shell_surface_new(struct wl_listener *listener, void *data);
struct cg_xdg_popup {
struct wlr_xdg_popup *xdg_popup;
struct wl_listener destroy;
struct wl_listener commit;
struct wl_listener reposition;
};
void handle_new_xdg_toplevel(struct wl_listener *listener, void *data);
void handle_new_xdg_popup(struct wl_listener *listener, void *data);
void handle_xdg_toplevel_decoration(struct wl_listener *listener, void *data);

View file

@ -9,7 +9,6 @@
#include <stdbool.h>
#include <stdlib.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_box.h>
#include <wlr/util/log.h>
#include <wlr/xwayland.h>
@ -42,8 +41,15 @@ static void
get_geometry(struct cg_view *view, int *width_out, int *height_out)
{
struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view);
*width_out = xwayland_view->xwayland_surface->surface->current.width;
*height_out = xwayland_view->xwayland_surface->surface->current.height;
struct wlr_xwayland_surface *xsurface = xwayland_view->xwayland_surface;
if (xsurface->surface == NULL) {
*width_out = 0;
*height_out = 0;
return;
}
*width_out = xsurface->surface->current.width;
*height_out = xsurface->surface->current.height;
}
static bool
@ -86,7 +92,7 @@ maximize(struct cg_view *view, int output_width, int output_height)
struct cg_xwayland_view *xwayland_view = xwayland_view_from_view(view);
wlr_xwayland_surface_configure(xwayland_view->xwayland_surface, view->lx, view->ly, output_width,
output_height);
wlr_xwayland_surface_set_maximized(xwayland_view->xwayland_surface, true);
wlr_xwayland_surface_set_maximized(xwayland_view->xwayland_surface, true, true);
}
static void
@ -96,18 +102,6 @@ destroy(struct cg_view *view)
free(xwayland_view);
}
static void
for_each_surface(struct cg_view *view, wlr_surface_iterator_func_t iterator, void *data)
{
wlr_surface_for_each_surface(view->wlr_surface, iterator, data);
}
static struct wlr_surface *
wlr_surface_at(struct cg_view *view, double sx, double sy, double *sub_x, double *sub_y)
{
return wlr_surface_surface_at(view->wlr_surface, sx, sy, sub_x, sub_y);
}
static void
handle_xwayland_surface_request_fullscreen(struct wl_listener *listener, void *data)
{
@ -116,24 +110,12 @@ handle_xwayland_surface_request_fullscreen(struct wl_listener *listener, void *d
wlr_xwayland_surface_set_fullscreen(xwayland_view->xwayland_surface, xwayland_surface->fullscreen);
}
static void
handle_xwayland_surface_commit(struct wl_listener *listener, void *data)
{
struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, commit);
struct cg_view *view = &xwayland_view->view;
view_damage_part(view);
}
static void
handle_xwayland_surface_unmap(struct wl_listener *listener, void *data)
{
struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, unmap);
struct cg_view *view = &xwayland_view->view;
view_damage_whole(view);
wl_list_remove(&xwayland_view->commit.link);
view_unmap(view);
}
@ -148,12 +130,7 @@ handle_xwayland_surface_map(struct wl_listener *listener, void *data)
view->ly = xwayland_view->xwayland_surface->y;
}
xwayland_view->commit.notify = handle_xwayland_surface_commit;
wl_signal_add(&xwayland_view->xwayland_surface->surface->events.commit, &xwayland_view->commit);
view_map(view, xwayland_view->xwayland_surface->surface);
view_damage_whole(view);
}
static void
@ -162,8 +139,6 @@ handle_xwayland_surface_destroy(struct wl_listener *listener, void *data)
struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, destroy);
struct cg_view *view = &xwayland_view->view;
wl_list_remove(&xwayland_view->map.link);
wl_list_remove(&xwayland_view->unmap.link);
wl_list_remove(&xwayland_view->destroy.link);
wl_list_remove(&xwayland_view->request_fullscreen.link);
xwayland_view->xwayland_surface = NULL;
@ -179,12 +154,28 @@ static const struct cg_view_impl xwayland_view_impl = {
.activate = activate,
.maximize = maximize,
.destroy = destroy,
.for_each_surface = for_each_surface,
/* XWayland doesn't have a separate popup iterator. */
.for_each_popup_surface = NULL,
.wlr_surface_at = wlr_surface_at,
};
void
handle_xwayland_associate(struct wl_listener *listener, void *data)
{
struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, associate);
struct wlr_xwayland_surface *xsurface = xwayland_view->xwayland_surface;
xwayland_view->map.notify = handle_xwayland_surface_map;
wl_signal_add(&xsurface->surface->events.map, &xwayland_view->map);
xwayland_view->unmap.notify = handle_xwayland_surface_unmap;
wl_signal_add(&xsurface->surface->events.unmap, &xwayland_view->unmap);
}
void
handle_xwayland_dissociate(struct wl_listener *listener, void *data)
{
struct cg_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, dissociate);
wl_list_remove(&xwayland_view->map.link);
wl_list_remove(&xwayland_view->unmap.link);
}
void
handle_xwayland_surface_new(struct wl_listener *listener, void *data)
{
@ -200,10 +191,10 @@ handle_xwayland_surface_new(struct wl_listener *listener, void *data)
view_init(&xwayland_view->view, server, CAGE_XWAYLAND_VIEW, &xwayland_view_impl);
xwayland_view->xwayland_surface = xwayland_surface;
xwayland_view->map.notify = handle_xwayland_surface_map;
wl_signal_add(&xwayland_surface->events.map, &xwayland_view->map);
xwayland_view->unmap.notify = handle_xwayland_surface_unmap;
wl_signal_add(&xwayland_surface->events.unmap, &xwayland_view->unmap);
xwayland_view->associate.notify = handle_xwayland_associate;
wl_signal_add(&xwayland_surface->events.associate, &xwayland_view->associate);
xwayland_view->dissociate.notify = handle_xwayland_dissociate;
wl_signal_add(&xwayland_surface->events.dissociate, &xwayland_view->dissociate);
xwayland_view->destroy.notify = handle_xwayland_surface_destroy;
wl_signal_add(&xwayland_surface->events.destroy, &xwayland_view->destroy);
xwayland_view->request_fullscreen.notify = handle_xwayland_surface_request_fullscreen;

View file

@ -10,9 +10,10 @@ struct cg_xwayland_view {
struct cg_view view;
struct wlr_xwayland_surface *xwayland_surface;
struct wl_listener destroy;
struct wl_listener associate;
struct wl_listener dissociate;
struct wl_listener unmap;
struct wl_listener map;
struct wl_listener commit;
struct wl_listener request_fullscreen;
};