pax_global_header00006660000000000000000000000064145560070600014515gustar00rootroot0000000000000052 comment=74428f2a8fa7f252e2a46fdf5b697536c66c8a1c xdg-desktop-portal-wlr-0.7.1/000077500000000000000000000000001455600706000160545ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.7.1/.builds/000077500000000000000000000000001455600706000174145ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.7.1/.builds/alpine.yml000066400000000000000000000006671455600706000214200ustar00rootroot00000000000000image: alpine/edge packages: - elogind-dev - gcc - meson - pipewire-dev - wayland-dev - wayland-protocols - inih-dev - scdoc - libdrm - mesa-dev sources: - https://github.com/emersion/xdg-desktop-portal-wlr tasks: - setup: | cd xdg-desktop-portal-wlr meson -Dauto_features=enabled -Dsystemd=disabled -Dsd-bus-provider=libelogind build/ - build: | cd xdg-desktop-portal-wlr ninja -C build/ xdg-desktop-portal-wlr-0.7.1/.builds/archlinux.yml000066400000000000000000000010751455600706000221370ustar00rootroot00000000000000image: archlinux packages: - gcc - clang - meson - wayland - wayland-protocols - pipewire - libinih - scdoc - mesa sources: - https://github.com/emersion/xdg-desktop-portal-wlr tasks: - setup: | cd xdg-desktop-portal-wlr CC=gcc meson -Dauto_features=enabled -Dsd-bus-provider=libsystemd build-gcc/ CC=clang meson -Dauto_features=enabled -Dsd-bus-provider=libsystemd build-clang/ - build-gcc: | cd xdg-desktop-portal-wlr ninja -C build-gcc/ - build-clang: | cd xdg-desktop-portal-wlr ninja -C build-clang/ xdg-desktop-portal-wlr-0.7.1/.builds/freebsd.yml000066400000000000000000000007121455600706000215510ustar00rootroot00000000000000image: freebsd/latest packages: - basu - libepoll-shim - meson - pipewire - pkgconf - wayland - wayland-protocols - inih - scdoc - graphics/libdrm - graphics/mesa-libs sources: - https://github.com/emersion/xdg-desktop-portal-wlr tasks: - setup: | cd xdg-desktop-portal-wlr meson -Dauto_features=enabled -Dsystemd=disabled -Dsd-bus-provider=basu build/ - build: | cd xdg-desktop-portal-wlr ninja -C build/ xdg-desktop-portal-wlr-0.7.1/.editorconfig000066400000000000000000000003051455600706000205270ustar00rootroot00000000000000root = true [*] end_of_line = lf insert_final_newline = true charset = utf-8 trim_trailing_whitespace = true indent_style = tab indent_size = 4 [*.{xml,yml}] indent_style = space indent_size = 2 xdg-desktop-portal-wlr-0.7.1/.gitignore000066400000000000000000000007161455600706000200500ustar00rootroot00000000000000# Prerequisites *.d # Object files *.o *.ko *.obj *.elf # Linker output *.ilk *.map *.exp # Precompiled Headers *.gch *.pch # Libraries *.lib *.a *.la *.lo # Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib # Executables *.exe *.out *.app *.i*86 *.x86_64 *.hex # Debug files *.dSYM/ *.su *.idb *.pdb # Kernel Module Compile Results *.mod* *.cmd .tmp_versions/ modules.order Module.symvers Mkfile.old dkms.conf # build folder build/ build-*/ xdg-desktop-portal-wlr-0.7.1/CONTRIBUTING.md000066400000000000000000000022611455600706000203060ustar00rootroot00000000000000# Contributing We closely follow the wlroots [contributing] guidelines where possible. Please see that document for more information. ## Tooling Useful tools include `dbus-monitor` to watch requests being made, and `dbus-send` and the similar `busctl call` for manual dbus calls. You can test the integration with the [portal-test] Flatpak app. Alternatively you can trigger it with [trigger-screen-shot.py] and [xdp-screen-cast.py]. [contributing]: https://github.com/swaywm/wlroots/blob/master/CONTRIBUTING.md [portal-test]: https://github.com/matthiasclasen/portal-test [trigger-screen-shot.py]: https://gist.github.com/danshick/3446dac24c64ce6172eced4ac255ac3d [xdp-screen-cast.py]: https://gitlab.gnome.org/snippets/19 ## Alternate *.portal Location xdg-desktop-portal will read the XDG_DESKTOP_PORTAL_DIR environment variable for an alternate path for *.portal files. This can be useful when testing changes to that portal file, or for testing xdpw without installing it. This feature is undocumented and shouldn't be relied on, but may be helpful in some circumstances. https://github.com/flatpak/xdg-desktop-portal/blob/e7f78640e35debb68fef891fc233c449006d9724/src/portal-impl.c#L124 xdg-desktop-portal-wlr-0.7.1/LICENSE000066400000000000000000000020511455600706000170570ustar00rootroot00000000000000MIT License Copyright (c) 2018 emersion Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. xdg-desktop-portal-wlr-0.7.1/README.md000066400000000000000000000040331455600706000173330ustar00rootroot00000000000000# xdg-desktop-portal-wlr [![builds.sr.ht status](https://builds.sr.ht/~emersion/xdg-desktop-portal-wlr/commits/master.svg)](https://builds.sr.ht/~emersion/xdg-desktop-portal-wlr/commits/master?) [xdg-desktop-portal] backend for wlroots ## Building ```sh meson build ninja -C build ``` ## Installing ### From Source ```sh ninja -C build install ``` ### Distro Packages [![Packaging status](https://repology.org/badge/vertical-allrepos/xdg-desktop-portal-wlr.svg)](https://repology.org/project/xdg-desktop-portal-wlr/versions) ## Running Make sure `XDG_CURRENT_DESKTOP` is set. Make sure `WAYLAND_DISPLAY` and `XDG_CURRENT_DESKTOP` are imported into D-Bus. If you're running Sway, this can be added to your config file: exec dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=sway When correctly installed, xdg-desktop-portal should automatically invoke xdg-desktop-portal-wlr when needed. ### Configuration See `man 5 xdg-desktop-portal-wlr`. ### Manual startup At the moment, some command line flags are available for development and testing. If you need to use one of these flags, you can start an instance of xdpw using the following command: ```sh xdg-desktop-portal-wlr -r [OPTION...] ``` To list the available options, you can run `xdg-desktop-portal-wlr --help`. ## FAQ Check out or [FAQ] for answers to commonly asked questions. Please see the [screencast compatibility] guide for more information on compatible applications and how to get them working. If you have a question or problem that is not mentioned in those documents, please open an issue or come chat with us in #sway on Libera Chat. ## Contributing If you're interested in testing or development, check out [CONTRIBUTING.md] for more information. ## License MIT [xdg-desktop-portal]: https://github.com/flatpak/xdg-desktop-portal [FAQ]: https://github.com/emersion/xdg-desktop-portal-wlr/wiki/FAQ [screencast compatibility]: https://github.com/emersion/xdg-desktop-portal-wlr/wiki/Screencast-Compatibility [CONTRIBUTING.md]: CONTRIBUTING.md xdg-desktop-portal-wlr-0.7.1/contrib/000077500000000000000000000000001455600706000175145ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.7.1/contrib/config.sample000066400000000000000000000001251455600706000221620ustar00rootroot00000000000000[screencast] output_name= max_fps=30 chooser_cmd=slurp -f %o -or chooser_type=simple xdg-desktop-portal-wlr-0.7.1/contrib/systemd/000077500000000000000000000000001455600706000212045ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.7.1/contrib/systemd/xdg-desktop-portal-wlr.service.in000066400000000000000000000004451455600706000275300ustar00rootroot00000000000000[Unit] Description=Portal service (wlroots implementation) PartOf=graphical-session.target After=graphical-session.target ConditionEnvironment=WAYLAND_DISPLAY [Service] Type=dbus BusName=org.freedesktop.impl.portal.desktop.wlr ExecStart=@libexecdir@/xdg-desktop-portal-wlr Restart=on-failure xdg-desktop-portal-wlr-0.7.1/contrib/wlroots-portals.conf000066400000000000000000000004531455600706000235600ustar00rootroot00000000000000[preferred] # Use xdg-desktop-portal-gtk for every portal interface... default=gtk # ... except for the Screencast, Screenshot and Settings (dark/light mode) interface org.freedesktop.impl.portal.Screencast=wlr org.freedesktop.impl.portal.Screenshot=wlr org.freedesktop.impl.portal.Settings=darkman xdg-desktop-portal-wlr-0.7.1/include/000077500000000000000000000000001455600706000174775ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.7.1/include/config.h000066400000000000000000000010401455600706000211100ustar00rootroot00000000000000#ifndef CONFIG_H #define CONFIG_H #include "logger.h" #include "screencast_common.h" struct config_screencast { char *output_name; double max_fps; char *exec_before; char *exec_after; char *chooser_cmd; enum xdpw_chooser_types chooser_type; bool force_mod_linear; }; struct xdpw_config { struct config_screencast screencast_conf; }; void print_config(enum LOGLEVEL loglevel, struct xdpw_config *config); void finish_config(struct xdpw_config *config); void init_config(char ** const configfile, struct xdpw_config *config); #endif xdg-desktop-portal-wlr-0.7.1/include/fps_limit.h000066400000000000000000000005611455600706000216400ustar00rootroot00000000000000#ifndef FPS_LIMIT_H #define FPS_LIMIT_H #include #include struct fps_limit_state { struct timespec frame_last_time; struct timespec fps_last_time; uint64_t fps_frame_count; }; void fps_limit_measure_start(struct fps_limit_state *state, double max_fps); uint64_t fps_limit_measure_end(struct fps_limit_state *state, double max_fps); #endif xdg-desktop-portal-wlr-0.7.1/include/logger.h000066400000000000000000000005601455600706000211300ustar00rootroot00000000000000#ifndef LOGGER_H #define LOGGER_H #include #define DEFAULT_LOGLEVEL ERROR enum LOGLEVEL { QUIET, ERROR, WARN, INFO, DEBUG, TRACE }; struct logger_properties { enum LOGLEVEL level; FILE *dst; }; void init_logger(FILE *dst, enum LOGLEVEL level); enum LOGLEVEL get_loglevel(const char *level); void logprint(enum LOGLEVEL level, char *msg, ...); #endif xdg-desktop-portal-wlr-0.7.1/include/pipewire_screencast.h000066400000000000000000000012021455600706000237010ustar00rootroot00000000000000#ifndef PIPEWIRE_SCREENCAST_H #define PIPEWIRE_SCREENCAST_H #include "screencast_common.h" #define XDPW_PWR_BUFFERS 2 #define XDPW_PWR_BUFFERS_MIN 2 #define XDPW_PWR_ALIGN 16 void xdpw_pwr_dequeue_buffer(struct xdpw_screencast_instance *cast); void xdpw_pwr_enqueue_buffer(struct xdpw_screencast_instance *cast); void pwr_update_stream_param(struct xdpw_screencast_instance *cast); void xdpw_pwr_stream_create(struct xdpw_screencast_instance *cast); void xdpw_pwr_stream_destroy(struct xdpw_screencast_instance *cast); int xdpw_pwr_context_create(struct xdpw_state *state); void xdpw_pwr_context_destroy(struct xdpw_state *state); #endif xdg-desktop-portal-wlr-0.7.1/include/screencast.h000066400000000000000000000003601455600706000220010ustar00rootroot00000000000000#ifndef SCREENCAST_H #define SCREENCAST_H #include "screencast_common.h" void xdpw_screencast_instance_destroy(struct xdpw_screencast_instance *cast); void xdpw_screencast_instance_teardown(struct xdpw_screencast_instance *cast); #endif xdg-desktop-portal-wlr-0.7.1/include/screencast_common.h000066400000000000000000000116401455600706000233540ustar00rootroot00000000000000#ifndef SCREENCAST_COMMON_H #define SCREENCAST_COMMON_H #include #include #include #include #include #include "fps_limit.h" // this seems to be right based on // https://github.com/flatpak/xdg-desktop-portal/blob/309a1fc0cf2fb32cceb91dbc666d20cf0a3202c2/src/screen-cast.c#L955 #define XDP_CAST_PROTO_VER 4 #define XDP_CAST_DATA_VER 1 enum cursor_modes { HIDDEN = 1, EMBEDDED = 2, METADATA = 4, }; enum source_types { MONITOR = 1, WINDOW = 2, }; enum persist_modes { PERSIST_NONE = 0, PERSIST_TRANSIENT = 1, PERSIST_PERMANENT = 2, }; enum buffer_type { WL_SHM = 0, DMABUF = 1, }; enum xdpw_chooser_types { XDPW_CHOOSER_DEFAULT, XDPW_CHOOSER_NONE, XDPW_CHOOSER_SIMPLE, XDPW_CHOOSER_DMENU, }; enum xdpw_frame_state { XDPW_FRAME_STATE_NONE, XDPW_FRAME_STATE_STARTED, XDPW_FRAME_STATE_RENEG, XDPW_FRAME_STATE_FAILED, XDPW_FRAME_STATE_SUCCESS, }; struct xdpw_output_chooser { enum xdpw_chooser_types type; char *cmd; }; struct xdpw_frame_damage { uint32_t x; uint32_t y; uint32_t width; uint32_t height; }; struct xdpw_frame { bool y_invert; uint64_t tv_sec; uint32_t tv_nsec; struct xdpw_frame_damage damage[4]; uint32_t damage_count; struct xdpw_buffer *xdpw_buffer; struct pw_buffer *pw_buffer; }; struct xdpw_screencopy_frame_info { uint32_t width; uint32_t height; uint32_t size; uint32_t stride; uint32_t format; }; struct xdpw_buffer { struct wl_list link; enum buffer_type buffer_type; uint32_t width; uint32_t height; uint32_t format; int plane_count; int fd[4]; uint32_t size[4]; uint32_t stride[4]; uint32_t offset[4]; struct gbm_bo *bo; struct wl_buffer *buffer; }; struct xdpw_format_modifier_pair { uint32_t fourcc; uint64_t modifier; }; struct xdpw_dmabuf_feedback_data { void *format_table_data; uint32_t format_table_size; bool device_used; }; struct xdpw_screencast_context { // xdpw struct xdpw_state *state; // pipewire struct pw_context *pwr_context; struct pw_core *core; // wlroots struct wl_list output_list; struct wl_registry *registry; struct zwlr_screencopy_manager_v1 *screencopy_manager; struct wl_shm *shm; struct zwp_linux_dmabuf_v1 *linux_dmabuf; struct zwp_linux_dmabuf_feedback_v1 *linux_dmabuf_feedback; struct zxdg_output_manager_v1 *xdg_output_manager; struct xdpw_dmabuf_feedback_data feedback_data; struct wl_array format_modifier_pairs; // gbm struct gbm_device *gbm; // sessions struct wl_list screencast_instances; }; struct xdpw_screencast_target { struct xdpw_wlr_output *output; bool with_cursor; }; struct xdpw_screencast_restore_data { uint32_t version; const char *output_name; }; struct xdpw_screencast_instance { // list struct wl_list link; // xdpw uint32_t refcount; struct xdpw_screencast_context *ctx; bool initialized; struct xdpw_frame current_frame; enum xdpw_frame_state frame_state; struct wl_list buffer_list; bool avoid_dmabufs; // pipewire struct pw_stream *stream; struct spa_hook stream_listener; struct spa_video_info_raw pwr_format; uint32_t seq; uint32_t node_id; bool pwr_stream_state; uint32_t framerate; // wlroots struct zwlr_screencopy_frame_v1 *frame_callback; struct xdpw_screencast_target *target; uint32_t max_framerate; struct zwlr_screencopy_frame_v1 *wlr_frame; struct xdpw_screencopy_frame_info screencopy_frame_info[2]; int err; bool quit; bool teardown; enum buffer_type buffer_type; // fps limit struct fps_limit_state fps_limit; }; struct xdpw_screencast_session_data { struct xdpw_screencast_instance *screencast_instance; uint32_t cursor_mode; uint32_t persist_mode; }; struct xdpw_wlr_output { struct wl_list link; uint32_t id; struct wl_output *output; struct zxdg_output_v1 *xdg_output; char *make; char *model; char *name; int x; int y; int width; int height; float framerate; enum wl_output_transform transformation; }; void randname(char *buf); struct gbm_device *xdpw_gbm_device_create(drmDevice *device); struct xdpw_buffer *xdpw_buffer_create(struct xdpw_screencast_instance *cast, enum buffer_type buffer_type, struct xdpw_screencopy_frame_info *frame_info); void xdpw_buffer_destroy(struct xdpw_buffer *buffer); bool wlr_query_dmabuf_modifiers(struct xdpw_screencast_context *ctx, uint32_t drm_format, uint32_t num_modifiers, uint64_t *modifiers, uint32_t *max_modifiers); enum wl_shm_format xdpw_format_wl_shm_from_drm_fourcc(uint32_t format); uint32_t xdpw_format_drm_fourcc_from_wl_shm(enum wl_shm_format format); enum spa_video_format xdpw_format_pw_from_drm_fourcc(uint32_t format); enum spa_video_format xdpw_format_pw_strip_alpha(enum spa_video_format format); enum xdpw_chooser_types get_chooser_type(const char *chooser_type); const char *chooser_type_str(enum xdpw_chooser_types chooser_type); struct xdpw_frame_damage merge_damage(struct xdpw_frame_damage *damage1, struct xdpw_frame_damage *damage2); #endif /* SCREENCAST_COMMON_H */ xdg-desktop-portal-wlr-0.7.1/include/screenshot.h000066400000000000000000000002051455600706000220220ustar00rootroot00000000000000#ifndef SCREENSHOT_H #define SCREENSHOT_H struct xdpw_ppm_pixel { int max_color_value; unsigned char red, green, blue; }; #endifxdg-desktop-portal-wlr-0.7.1/include/screenshot_common.h000066400000000000000000000001361455600706000233750ustar00rootroot00000000000000#ifndef SCREENSHOT_COMMON_H #define SCREENSHOT_COMMON_H #define XDP_SHOT_PROTO_VER 1 #endif xdg-desktop-portal-wlr-0.7.1/include/timespec_util.h000066400000000000000000000006111455600706000225140ustar00rootroot00000000000000#ifndef TIMESPEC_UTIL_H #define TIMESPEC_UTIL_H #include #include #include #define TIMESPEC_NSEC_PER_SEC 1000000000L void timespec_add(struct timespec *t, int64_t delta_ns); bool timespec_less(struct timespec *t1, struct timespec *t2); bool timespec_is_zero(struct timespec *t); int64_t timespec_diff_ns(struct timespec *t1, struct timespec *t2); #endif xdg-desktop-portal-wlr-0.7.1/include/wlr_screencast.h000066400000000000000000000016611455600706000226720ustar00rootroot00000000000000#ifndef WLR_SCREENCAST_H #define WLR_SCREENCAST_H #include "screencast_common.h" #define WL_OUTPUT_VERSION 4 #define SC_MANAGER_VERSION 3 #define SC_MANAGER_VERSION_MIN 2 #define WL_SHM_VERSION 1 #define LINUX_DMABUF_VERSION 4 #define LINUX_DMABUF_VERSION_MIN 3 #define XDG_OUTPUT_VERSION 3 #define XDG_OUTPUT_VERSION_MIN 1 struct xdpw_state; int xdpw_wlr_screencopy_init(struct xdpw_state *state); void xdpw_wlr_screencopy_finish(struct xdpw_screencast_context *ctx); bool xdpw_wlr_target_chooser(struct xdpw_screencast_context *ctx, struct xdpw_screencast_target *target); bool xdpw_wlr_target_from_data(struct xdpw_screencast_context *ctx, struct xdpw_screencast_target *target, struct xdpw_screencast_restore_data *data); void xdpw_wlr_frame_finish(struct xdpw_screencast_instance *cast); void xdpw_wlr_frame_start(struct xdpw_screencast_instance *cast); void xdpw_wlr_register_cb(struct xdpw_screencast_instance *cast); #endif xdg-desktop-portal-wlr-0.7.1/include/xdpw.h000066400000000000000000000035111455600706000206320ustar00rootroot00000000000000#ifndef XDPW_H #define XDPW_H #include #ifdef HAVE_LIBSYSTEMD #include #elif HAVE_LIBELOGIND #include #elif HAVE_BASU #include #endif #include "screencast_common.h" #include "screenshot_common.h" #include "config.h" struct xdpw_state { struct wl_list xdpw_sessions; sd_bus *bus; struct wl_display *wl_display; struct pw_loop *pw_loop; struct xdpw_screencast_context screencast; uint32_t screencast_source_types; // bitfield of enum source_types uint32_t screencast_cursor_modes; // bitfield of enum cursor_modes uint32_t screencast_version; uint32_t screenshot_version; struct xdpw_config *config; int timer_poll_fd; struct wl_list timers; struct xdpw_timer *next_timer; }; struct xdpw_request { sd_bus_slot *slot; }; struct xdpw_session { struct wl_list link; sd_bus_slot *slot; char *session_handle; struct xdpw_screencast_session_data screencast_data; }; typedef void (*xdpw_event_loop_timer_func_t)(void *data); struct xdpw_timer { struct xdpw_state *state; xdpw_event_loop_timer_func_t func; void *user_data; struct timespec at; struct wl_list link; // xdpw_state::timers }; enum { PORTAL_RESPONSE_SUCCESS = 0, PORTAL_RESPONSE_CANCELLED = 1, PORTAL_RESPONSE_ENDED = 2 }; int xdpw_screenshot_init(struct xdpw_state *state); int xdpw_screencast_init(struct xdpw_state *state); struct xdpw_request *xdpw_request_create(sd_bus *bus, const char *object_path); void xdpw_request_destroy(struct xdpw_request *req); struct xdpw_session *xdpw_session_create(struct xdpw_state *state, sd_bus *bus, char *object_path); void xdpw_session_destroy(struct xdpw_session *req); struct xdpw_timer *xdpw_add_timer(struct xdpw_state *state, uint64_t delay_ns, xdpw_event_loop_timer_func_t func, void *data); void xdpw_destroy_timer(struct xdpw_timer *timer); #endif xdg-desktop-portal-wlr-0.7.1/meson.build000066400000000000000000000076001455600706000202210ustar00rootroot00000000000000project( 'xdg-desktop-portal-wlr', 'c', version: '0.7.1', license: 'MIT', meson_version: '>=0.58.0', default_options: ['c_std=c11', 'warning_level=2', 'werror=true'], ) cc = meson.get_compiler('c') add_project_arguments(cc.get_supported_arguments([ '-Wno-missing-braces', '-Wno-missing-field-initializers', '-Wno-unused-parameter', '-D_POSIX_C_SOURCE=200809L', ]), language: 'c') prefix = get_option('prefix') sysconfdir = get_option('sysconfdir') add_project_arguments('-DSYSCONFDIR="@0@"'.format(prefix / sysconfdir), language : 'c') inc = include_directories('include') rt = cc.find_library('rt') pipewire = dependency('libpipewire-0.3', version: '>= 0.3.62') wayland_client = dependency('wayland-client') wayland_protos = dependency('wayland-protocols', version: '>=1.24') iniparser = dependency('inih') gbm = dependency('gbm') drm = dependency('libdrm') epoll = dependency('', required: false) if not cc.has_function('timerfd_create', prefix: '#include ') epoll = dependency('epoll-shim') endif if get_option('sd-bus-provider') == 'auto' assert(get_option('auto_features').auto(), 'sd-bus-provider must not be set to auto since auto_features != auto') sdbus = dependency('libsystemd', required: false, not_found_message: 'libsystemd not found, trying libelogind', ) if not sdbus.found() sdbus = dependency('libelogind', required: false, not_found_message: 'libelogind not found, trying basu', ) endif if not sdbus.found() sdbus = dependency('basu', required: false, ) endif if not sdbus.found() error('Neither libsystemd, nor libelogind, nor basu was found') endif else sdbus = dependency(get_option('sd-bus-provider')) endif add_project_arguments('-DHAVE_' + sdbus.name().to_upper() + '=1', language: 'c') subdir('protocols') xdpw_files = files( 'src/core/main.c', 'src/core/logger.c', 'src/core/config.c', 'src/core/request.c', 'src/core/session.c', 'src/core/timer.c', 'src/core/timespec_util.c', 'src/screenshot/screenshot.c', 'src/screencast/screencast.c', 'src/screencast/screencast_common.c', 'src/screencast/wlr_screencast.c', 'src/screencast/pipewire_screencast.c', 'src/screencast/fps_limit.c', ) executable( 'xdg-desktop-portal-wlr', [xdpw_files, wl_proto_files], dependencies: [ wayland_client, sdbus, pipewire, rt, iniparser, gbm, drm, epoll, ], include_directories: [inc], install: true, install_dir: get_option('libexecdir'), ) conf_data = configuration_data() conf_data.set('libexecdir', get_option('prefix') / get_option('libexecdir')) conf_data.set('systemd_service', '') systemd = dependency('systemd', required: get_option('systemd')) if systemd.found() systemd_service_file = 'xdg-desktop-portal-wlr.service' user_unit_dir = systemd.get_variable(pkgconfig: 'systemduserunitdir', pkgconfig_define: ['prefix', get_option('prefix')]) conf_data.set('systemd_service', 'SystemdService=' + systemd_service_file) configure_file( configuration: conf_data, input: 'contrib/systemd/' + systemd_service_file + '.in', output: '@BASENAME@', install_dir: user_unit_dir, ) endif configure_file( configuration: conf_data, input: 'org.freedesktop.impl.portal.desktop.wlr.service.in', output: '@BASENAME@', install_dir: get_option('datadir') / 'dbus-1' / 'services', ) install_data( 'wlr.portal', install_dir: get_option('datadir') / 'xdg-desktop-portal' / 'portals', ) scdoc = dependency('scdoc', required: get_option('man-pages'), version: '>= 1.9.7', native: true) if scdoc.found() man_pages = ['xdg-desktop-portal-wlr.5.scd'] foreach src : man_pages topic = src.split('.')[0] section = src.split('.')[1] output = topic + '.' + section custom_target( output, input: files(src), output: output, command: [ 'sh', '-c', '@0@ < @INPUT@ > @1@'.format(scdoc.get_variable(pkgconfig: 'scdoc'), output) ], install: true, install_dir: get_option('mandir') / ('man' + section), ) endforeach endif xdg-desktop-portal-wlr-0.7.1/meson_options.txt000066400000000000000000000005451455600706000215150ustar00rootroot00000000000000option('sd-bus-provider', type: 'combo', choices: ['auto', 'libsystemd', 'libelogind', 'basu'], value: 'auto', description: 'Provider of the sd-bus library') option('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit') option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') xdg-desktop-portal-wlr-0.7.1/org.freedesktop.impl.portal.desktop.wlr.service.in000066400000000000000000000001701455600706000276550ustar00rootroot00000000000000[D-BUS Service] Name=org.freedesktop.impl.portal.desktop.wlr Exec=@libexecdir@/xdg-desktop-portal-wlr @systemd_service@ xdg-desktop-portal-wlr-0.7.1/protocols/000077500000000000000000000000001455600706000201005ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.7.1/protocols/meson.build000066400000000000000000000016261455600706000222470ustar00rootroot00000000000000wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') wayland_scanner_dep = dependency('wayland-scanner', native: true) wayland_scanner = find_program( wayland_scanner_dep.get_variable(pkgconfig: 'wayland_scanner'), native: true, ) client_protocols = [ wl_protocol_dir / 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml', wl_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml', 'wlr-screencopy-unstable-v1.xml', ] wl_proto_files = [] foreach xml: client_protocols code = custom_target( xml.underscorify() + '_c', input: xml, output: '@BASENAME@-protocol.c', command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'], ) client_header = custom_target( xml.underscorify() + '_client_h', input: xml, output: '@BASENAME@-client-protocol.h', command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'], ) wl_proto_files += [code, client_header] endforeach xdg-desktop-portal-wlr-0.7.1/protocols/wlr-screencopy-unstable-v1.xml000066400000000000000000000226051455600706000257420ustar00rootroot00000000000000 Copyright © 2018 Simon Ser Copyright © 2019 Andri Yngvason Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This protocol allows clients to ask the compositor to copy part of the screen content to a client buffer. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. This object is a manager which offers requests to start capturing from a source. Capture the next frame of an entire output. Capture the next frame of an output's region. The region is given in output logical coordinates, see xdg_output.logical_size. The region will be clipped to the output's extents. All objects created by the manager will still remain valid, until their appropriate destroy request has been called. This object represents a single frame. When created, a series of buffer events will be sent, each representing a supported buffer type. The "buffer_done" event is sent afterwards to indicate that all supported buffer types have been enumerated. The client will then be able to send a "copy" request. If the capture is successful, the compositor will send a "flags" followed by a "ready" event. For objects version 2 or lower, wl_shm buffers are always supported, ie. the "buffer" event is guaranteed to be sent. If the capture failed, the "failed" event is sent. This can happen anytime before the "ready" event. Once either a "ready" or a "failed" event is received, the client should destroy the frame. Provides information about wl_shm buffer parameters that need to be used for this frame. This event is sent once after the frame is created if wl_shm buffers are supported. Copy the frame to the supplied buffer. The buffer must have a the correct size, see zwlr_screencopy_frame_v1.buffer and zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a supported format. If the frame is successfully copied, a "flags" and a "ready" events are sent. Otherwise, a "failed" event is sent. Provides flags about the frame. This event is sent once before the "ready" event. Called as soon as the frame is copied, indicating it is available for reading. This event includes the time at which presentation happened at. The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, each component being an unsigned 32-bit value. Whole seconds are in tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, and the additional fractional part in tv_nsec as nanoseconds. Hence, for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part may have an arbitrary offset at start. After receiving this event, the client should destroy the object. This event indicates that the attempted frame copy has failed. After receiving this event, the client should destroy the object. Destroys the frame. This request can be sent at any time by the client. Same as copy, except it waits until there is damage to copy. This event is sent right before the ready event when copy_with_damage is requested. It may be generated multiple times for each copy_with_damage request. The arguments describe a box around an area that has changed since the last copy request that was derived from the current screencopy manager instance. The union of all regions received between the call to copy_with_damage and a ready event is the total damage since the prior ready event. Provides information about linux-dmabuf buffer parameters that need to be used for this frame. This event is sent once after the frame is created if linux-dmabuf buffers are supported. This event is sent once after all buffer events have been sent. The client should proceed to create a buffer of one of the supported types, and send a "copy" request. xdg-desktop-portal-wlr-0.7.1/src/000077500000000000000000000000001455600706000166435ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.7.1/src/core/000077500000000000000000000000001455600706000175735ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.7.1/src/core/config.c000066400000000000000000000137471455600706000212200ustar00rootroot00000000000000#include "config.h" #include "xdpw.h" #include "logger.h" #include "screencast_common.h" #include #include #include #include #include void print_config(enum LOGLEVEL loglevel, struct xdpw_config *config) { logprint(loglevel, "config: outputname: %s", config->screencast_conf.output_name); logprint(loglevel, "config: max_fps: %f", config->screencast_conf.max_fps); logprint(loglevel, "config: exec_before: %s", config->screencast_conf.exec_before); logprint(loglevel, "config: exec_after: %s", config->screencast_conf.exec_after); logprint(loglevel, "config: chooser_cmd: %s", config->screencast_conf.chooser_cmd); logprint(loglevel, "config: chooser_type: %s", chooser_type_str(config->screencast_conf.chooser_type)); logprint(loglevel, "config: force_mod_linear: %d", config->screencast_conf.force_mod_linear); } // NOTE: calling finish_config won't prepare the config to be read again from config file // with init_config since to pointers and other values won't be reset to NULL, or 0 void finish_config(struct xdpw_config *config) { logprint(DEBUG, "config: destroying config"); // screencast free(config->screencast_conf.output_name); free(config->screencast_conf.exec_before); free(config->screencast_conf.exec_after); free(config->screencast_conf.chooser_cmd); } static void parse_string(char **dest, const char* value) { if (value == NULL || *value == '\0') { logprint(TRACE, "config: skipping empty value in config file"); return; } free(*dest); *dest = strdup(value); } static void parse_double(double *dest, const char* value) { if (value == NULL || *value == '\0') { logprint(TRACE, "config: skipping empty value in config file"); return; } *dest = strtod(value, (char**)NULL); } static void parse_bool(bool *dest, const char* value) { if (value == NULL || *value == '\0') { logprint(TRACE, "config: skipping empty value in config file"); return; } if (strcmp(value, "1") == 0) { *dest = true; } else { *dest = false; } } static int handle_ini_screencast(struct config_screencast *screencast_conf, const char *key, const char *value) { if (strcmp(key, "output_name") == 0) { parse_string(&screencast_conf->output_name, value); } else if (strcmp(key, "max_fps") == 0) { parse_double(&screencast_conf->max_fps, value); } else if (strcmp(key, "exec_before") == 0) { parse_string(&screencast_conf->exec_before, value); } else if (strcmp(key, "exec_after") == 0) { parse_string(&screencast_conf->exec_after, value); } else if (strcmp(key, "chooser_cmd") == 0) { parse_string(&screencast_conf->chooser_cmd, value); } else if (strcmp(key, "chooser_type") == 0) { char *chooser_type = NULL; parse_string(&chooser_type, value); screencast_conf->chooser_type = get_chooser_type(chooser_type); free(chooser_type); } else if (strcmp(key, "force_mod_linear") == 0) { parse_bool(&screencast_conf->force_mod_linear, value); } else { logprint(TRACE, "config: skipping invalid key in config file"); return 0; } return 1; } static int handle_ini_config(void *data, const char* section, const char *key, const char *value) { struct xdpw_config *config = (struct xdpw_config*)data; logprint(TRACE, "config: parsing setction %s, key %s, value %s", section, key, value); if (strcmp(section, "screencast") == 0) { return handle_ini_screencast(&config->screencast_conf, key, value); } logprint(TRACE, "config: skipping invalid key in config file"); return 0; } static void default_config(struct xdpw_config *config) { config->screencast_conf.max_fps = 0; config->screencast_conf.chooser_type = XDPW_CHOOSER_DEFAULT; } static bool file_exists(const char *path) { return path && access(path, R_OK) != -1; } static char *config_path(const char *prefix, const char *filename) { if (!prefix || !prefix[0] || !filename || !filename[0]) { return NULL; } char *config_folder = "xdg-desktop-portal-wlr"; size_t size = 3 + strlen(prefix) + strlen(config_folder) + strlen(filename); char *path = calloc(size, sizeof(char)); snprintf(path, size, "%s/%s/%s", prefix, config_folder, filename); return path; } static char *get_config_path(void) { const char *home = getenv("HOME"); char *config_home_fallback = NULL; if (home != NULL && home[0] != '\0') { size_t size_fallback = 1 + strlen(home) + strlen("/.config"); config_home_fallback = calloc(size_fallback, sizeof(char)); snprintf(config_home_fallback, size_fallback, "%s/.config", home); } const char *config_home = getenv("XDG_CONFIG_HOME"); if (config_home == NULL || config_home[0] == '\0') { config_home = config_home_fallback; } const char *prefix[2]; prefix[0] = config_home; prefix[1] = SYSCONFDIR "/xdg"; const char *xdg_current_desktop = getenv("XDG_CURRENT_DESKTOP"); const char *config_fallback = "config"; char *config_list = NULL; for (size_t i = 0; i < 2; i++) { if (xdg_current_desktop) { config_list = strdup(xdg_current_desktop); char *config = strtok(config_list, ":"); while (config) { char *path = config_path(prefix[i], config); if (!path) { config = strtok(NULL, ":"); continue; } logprint(TRACE, "config: trying config file %s", path); if (file_exists(path)) { free(config_list); free(config_home_fallback); return path; } free(path); config = strtok(NULL, ":"); } free(config_list); } char *path = config_path(prefix[i], config_fallback); if (!path) { continue; } logprint(TRACE, "config: trying config file %s", path); if (file_exists(path)) { free(config_home_fallback); return path; } free(path); } free(config_home_fallback); return NULL; } void init_config(char ** const configfile, struct xdpw_config *config) { if (*configfile == NULL) { *configfile = get_config_path(); } default_config(config); if (*configfile == NULL) { logprint(INFO, "config: no config file found, using the default config"); return; } if (ini_parse(*configfile, handle_ini_config, config) < 0) { logprint(ERROR, "config: unable to load config file %s", *configfile); } } xdg-desktop-portal-wlr-0.7.1/src/core/logger.c000066400000000000000000000033751455600706000212260ustar00rootroot00000000000000#include "logger.h" #include #include #include #include static struct logger_properties logprops; void init_logger(FILE *dst, enum LOGLEVEL level) { logprops.dst = dst; logprops.level = level; } enum LOGLEVEL get_loglevel(const char *level) { if (strcmp(level, "QUIET") == 0) { return QUIET; } else if (strcmp(level, "ERROR") == 0) { return ERROR; } else if (strcmp(level, "WARN") == 0) { return WARN; } else if (strcmp(level, "INFO") == 0) { return INFO; } else if (strcmp(level, "DEBUG") == 0) { return DEBUG; } else if (strcmp(level, "TRACE") == 0) { return TRACE; } fprintf(stderr, "Could not understand log level %s\n", level); exit(1); } static const char *print_loglevel(enum LOGLEVEL loglevel) { switch (loglevel) { case QUIET: return "QUIET"; case ERROR: return "ERROR"; case WARN: return "WARN"; case INFO: return "INFO"; case DEBUG: return "DEBUG"; case TRACE: return "TRACE"; } fprintf(stderr, "Could not find log level %d\n", loglevel); abort(); } void logprint(enum LOGLEVEL level, char *msg, ...) { if (!logprops.dst) { fprintf(stderr, "Logger has been called, but was not initialized\n"); abort(); } if (level > logprops.level || level == QUIET) { return; } va_list args; char timestr[200]; time_t t = time(NULL); struct tm *tmp = localtime(&t); if (strftime(timestr, sizeof(timestr), "%Y/%m/%d %H:%M:%S", tmp) == 0) { fprintf(stderr, "strftime returned 0"); abort(); } fprintf(logprops.dst, "%s", timestr); fprintf(logprops.dst, " "); fprintf(logprops.dst, "[%s]", print_loglevel(level)); fprintf(logprops.dst, " - "); va_start(args, msg); vfprintf(logprops.dst, msg, args); va_end(args); fprintf(logprops.dst, "\n"); fflush(logprops.dst); } xdg-desktop-portal-wlr-0.7.1/src/core/main.c000066400000000000000000000201621455600706000206640ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "xdpw.h" #include "logger.h" enum event_loop_fd { EVENT_LOOP_DBUS, EVENT_LOOP_WAYLAND, EVENT_LOOP_PIPEWIRE, EVENT_LOOP_TIMER, }; static const char service_name[] = "org.freedesktop.impl.portal.desktop.wlr"; static int xdpw_usage(FILE *stream, int rc) { static const char *usage = "Usage: xdg-desktop-portal-wlr [options]\n" "\n" " -l, --loglevel= Select log level (default is ERROR).\n" " QUIET, ERROR, WARN, INFO, DEBUG, TRACE\n" " -c, --config= Select config file.\n" " (default is $XDG_CONFIG_HOME/xdg-desktop-portal-wlr/config)\n" " -r, --replace Replace a running instance.\n" " -h, --help Get help (this text).\n" "\n"; fprintf(stream, "%s", usage); return rc; } static int handle_name_lost(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { logprint(INFO, "dbus: lost name, closing connection"); sd_bus_close(sd_bus_message_get_bus(m)); return 1; } int main(int argc, char *argv[]) { struct xdpw_config config = {0}; char *configfile = NULL; enum LOGLEVEL loglevel = DEFAULT_LOGLEVEL; bool replace = false; static const char *shortopts = "l:o:c:f:rh"; static const struct option longopts[] = { { "loglevel", required_argument, NULL, 'l' }, { "config", required_argument, NULL, 'c' }, { "replace", no_argument, NULL, 'r' }, { "help", no_argument, NULL, 'h' }, { NULL, 0, NULL, 0 } }; while (1) { int c = getopt_long(argc, argv, shortopts, longopts, NULL); if (c < 0) { break; } switch (c) { case 'l': loglevel = get_loglevel(optarg); break; case 'c': configfile = strdup(optarg); break; case 'r': replace = true; break; case 'h': return xdpw_usage(stdout, EXIT_SUCCESS); default: return xdpw_usage(stderr, EXIT_FAILURE); } } init_logger(stderr, loglevel); init_config(&configfile, &config); print_config(DEBUG, &config); int ret = 0; sd_bus *bus = NULL; ret = sd_bus_open_user(&bus); if (ret < 0) { logprint(ERROR, "dbus: failed to connect to user bus: %s", strerror(-ret)); return EXIT_FAILURE; } logprint(DEBUG, "dbus: connected"); struct wl_display *wl_display = wl_display_connect(NULL); if (!wl_display) { logprint(ERROR, "wayland: failed to connect to display"); sd_bus_unref(bus); return EXIT_FAILURE; } logprint(DEBUG, "wlroots: wl_display connected"); pw_init(NULL, NULL); struct pw_loop *pw_loop = pw_loop_new(NULL); if (!pw_loop) { logprint(ERROR, "pipewire: failed to create loop"); wl_display_disconnect(wl_display); sd_bus_unref(bus); return EXIT_FAILURE; } logprint(DEBUG, "pipewire: pw_loop created"); struct xdpw_state state = { .bus = bus, .wl_display = wl_display, .pw_loop = pw_loop, .screencast_source_types = MONITOR, .screencast_cursor_modes = HIDDEN | EMBEDDED, .screencast_version = XDP_CAST_PROTO_VER, .screenshot_version = XDP_SHOT_PROTO_VER, .config = &config, }; wl_list_init(&state.xdpw_sessions); ret = xdpw_screenshot_init(&state); if (ret < 0) { logprint(ERROR, "xdpw: failed to initialize screenshot"); goto error; } ret = xdpw_screencast_init(&state); if (ret < 0) { logprint(ERROR, "xdpw: failed to initialize screencast"); goto error; } uint64_t flags = SD_BUS_NAME_ALLOW_REPLACEMENT; if (replace) { flags |= SD_BUS_NAME_REPLACE_EXISTING; } ret = sd_bus_request_name(bus, service_name, flags); if (ret < 0) { logprint(ERROR, "dbus: failed to acquire service name: %s", strerror(-ret)); goto error; } const char *unique_name; ret = sd_bus_get_unique_name(bus, &unique_name); if (ret < 0) { logprint(ERROR, "dbus: failed to get unique bus name: %s", strerror(-ret)); goto error; } static char match[1024]; snprintf(match, sizeof(match), "sender='org.freedesktop.DBus'," "type='signal'," "interface='org.freedesktop.DBus'," "member='NameOwnerChanged'," "path='/org/freedesktop/DBus'," "arg0='%s'," "arg1='%s'", service_name, unique_name); sd_bus_slot *slot; ret = sd_bus_add_match(bus, &slot, match, handle_name_lost, NULL); if (ret < 0) { logprint(ERROR, "dbus: failed to add NameOwnerChanged signal match: %s", strerror(-ret)); goto error; } wl_list_init(&state.timers); struct pollfd pollfds[] = { [EVENT_LOOP_DBUS] = {0}, // Filled in later [EVENT_LOOP_WAYLAND] = { .fd = wl_display_get_fd(state.wl_display), .events = POLLIN, }, [EVENT_LOOP_PIPEWIRE] = { .fd = pw_loop_get_fd(state.pw_loop), .events = POLLIN, }, [EVENT_LOOP_TIMER] = { .fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC), .events = POLLIN, } }; state.timer_poll_fd = pollfds[EVENT_LOOP_TIMER].fd; while (1) { // sd-bus requires that we update FD/events/timeout every time we poll pollfds[EVENT_LOOP_DBUS].fd = sd_bus_get_fd(state.bus); if (pollfds[EVENT_LOOP_DBUS].fd < 0) { logprint(ERROR, "sd_bus_get_fd failed: %s", strerror(-pollfds[EVENT_LOOP_DBUS].fd)); goto error; } pollfds[EVENT_LOOP_DBUS].events = sd_bus_get_events(state.bus); if (pollfds[EVENT_LOOP_DBUS].events < 0) { logprint(ERROR, "sd_bus_get_events failed: %s", strerror(-pollfds[EVENT_LOOP_DBUS].events)); goto error; } uint64_t usec_timeout = 0; ret = sd_bus_get_timeout(state.bus, &usec_timeout); if (ret < 0) { logprint(ERROR, "sd_bus_get_timeout failed: %s", strerror(-ret)); goto error; } // Convert timestamp from usec to msec. Value of -1 indicates no // timeout, i.e. poll forever. int msec_timeout = usec_timeout == UINT64_MAX ? -1 : (int)((usec_timeout + 999) / 1000); ret = poll(pollfds, sizeof(pollfds) / sizeof(pollfds[0]), msec_timeout); if (ret < 0) { logprint(ERROR, "poll failed: %s", strerror(errno)); goto error; } if (pollfds[EVENT_LOOP_DBUS].revents & POLLHUP) { logprint(INFO, "event-loop: disconnected from dbus"); break; } if (pollfds[EVENT_LOOP_WAYLAND].revents & POLLHUP) { logprint(INFO, "event-loop: disconnected from wayland"); break; } if (pollfds[EVENT_LOOP_PIPEWIRE].revents & POLLHUP) { logprint(INFO, "event-loop: disconnected from pipewire"); break; } // sd-bus sets events=0 if it already has messages to process if (pollfds[EVENT_LOOP_DBUS].revents || pollfds[EVENT_LOOP_DBUS].events == 0) { logprint(TRACE, "event-loop: got dbus event"); do { ret = sd_bus_process(state.bus, NULL); } while (ret > 0); if (ret < 0) { logprint(ERROR, "sd_bus_process failed: %s", strerror(-ret)); goto error; } } if (pollfds[EVENT_LOOP_WAYLAND].revents & POLLIN) { logprint(TRACE, "event-loop: got wayland event"); ret = wl_display_dispatch(state.wl_display); if (ret < 0) { logprint(ERROR, "wl_display_dispatch failed: %s", strerror(errno)); goto error; } } if (pollfds[EVENT_LOOP_PIPEWIRE].revents & POLLIN) { logprint(TRACE, "event-loop: got pipewire event"); ret = pw_loop_iterate(state.pw_loop, 0); if (ret < 0) { logprint(ERROR, "pw_loop_iterate failed: %s", spa_strerror(ret)); goto error; } } if (pollfds[EVENT_LOOP_TIMER].revents & POLLIN) { logprint(TRACE, "event-loop: got a timer event"); int timer_fd = pollfds[EVENT_LOOP_TIMER].fd; uint64_t expirations; ssize_t n = read(timer_fd, &expirations, sizeof(expirations)); if (n < 0) { logprint(ERROR, "failed to read from timer FD\n"); goto error; } struct xdpw_timer *timer = state.next_timer; if (timer != NULL) { xdpw_event_loop_timer_func_t func = timer->func; void *user_data = timer->user_data; xdpw_destroy_timer(timer); func(user_data); } } do { ret = wl_display_dispatch_pending(state.wl_display); wl_display_flush(state.wl_display); } while (ret > 0); sd_bus_flush(state.bus); } // TODO: cleanup finish_config(&config); free(configfile); return EXIT_SUCCESS; error: sd_bus_unref(bus); pw_loop_leave(state.pw_loop); pw_loop_destroy(state.pw_loop); wl_display_disconnect(state.wl_display); return EXIT_FAILURE; } xdg-desktop-portal-wlr-0.7.1/src/core/request.c000066400000000000000000000024621455600706000214330ustar00rootroot00000000000000#include #include #include #include #include "xdpw.h" #include "logger.h" static const char interface_name[] = "org.freedesktop.impl.portal.Request"; static int method_close(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { struct xdpw_request *req = data; int ret = 0; logprint(INFO, "dbus: request closed"); sd_bus_message *reply = NULL; ret = sd_bus_message_new_method_return(msg, &reply); if (ret < 0) { return ret; } ret = sd_bus_send(NULL, reply, NULL); if (ret < 0) { return ret; } sd_bus_message_unref(reply); xdpw_request_destroy(req); return 0; } static const sd_bus_vtable request_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("Close", "", "", method_close, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END }; struct xdpw_request *xdpw_request_create(sd_bus *bus, const char *object_path) { struct xdpw_request *req = calloc(1, sizeof(struct xdpw_request)); if (sd_bus_add_object_vtable(bus, &req->slot, object_path, interface_name, request_vtable, NULL) < 0) { free(req); logprint(ERROR, "dbus: sd_bus_add_object_vtable failed: %s", strerror(-errno)); return NULL; } return req; } void xdpw_request_destroy(struct xdpw_request *req) { if (req == NULL) { return; } sd_bus_slot_unref(req->slot); free(req); } xdg-desktop-portal-wlr-0.7.1/src/core/session.c000066400000000000000000000044371455600706000214320ustar00rootroot00000000000000#include #include #include #include #include #include "xdpw.h" #include "screencast.h" #include "logger.h" static const char interface_name[] = "org.freedesktop.impl.portal.Session"; static int method_close(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { int ret = 0; struct xdpw_session *sess = data; logprint(INFO, "dbus: session closed"); sd_bus_message *reply = NULL; ret = sd_bus_message_new_method_return(msg, &reply); if (ret < 0) { return ret; } ret = sd_bus_send(NULL, reply, NULL); if (ret < 0) { return ret; } sd_bus_message_unref(reply); xdpw_session_destroy(sess); return 0; } static const sd_bus_vtable session_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("Close", "", "", method_close, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END }; struct xdpw_session *xdpw_session_create(struct xdpw_state *state, sd_bus *bus, char *object_path) { struct xdpw_session *sess = calloc(1, sizeof(struct xdpw_session)); sess->session_handle = object_path; if (sd_bus_add_object_vtable(bus, &sess->slot, object_path, interface_name, session_vtable, sess) < 0) { free(sess); logprint(ERROR, "dbus: sd_bus_add_object_vtable failed: %s", strerror(-errno)); return NULL; } wl_list_insert(&state->xdpw_sessions, &sess->link); return sess; } void xdpw_session_destroy(struct xdpw_session *sess) { logprint(DEBUG, "dbus: destroying session %p", sess); if (!sess) { return; } struct xdpw_screencast_instance *cast = sess->screencast_data.screencast_instance; if (cast) { assert(cast->refcount > 0); --cast->refcount; logprint(DEBUG, "xdpw: screencast instance %p now has %d references", cast, cast->refcount); if (cast->refcount < 1) { if (cast->frame_state == XDPW_FRAME_STATE_NONE) { logprint(TRACE, "xdpw: screencast instance not streaming, destroy it"); xdpw_screencast_instance_destroy(cast); } else if (cast->teardown) { logprint(TRACE, "xdpw: screencast instance marked for teardown, destroy it"); xdpw_screencast_instance_destroy(cast); } else { logprint(TRACE, "xdpw: screencast instance still streaming, set quit flag"); cast->quit = true; } } } sd_bus_slot_unref(sess->slot); wl_list_remove(&sess->link); free(sess->session_handle); free(sess); } xdg-desktop-portal-wlr-0.7.1/src/core/timer.c000066400000000000000000000030451455600706000210610ustar00rootroot00000000000000#include #include #include #include "xdpw.h" #include "logger.h" #include "timespec_util.h" static void update_timer(struct xdpw_state *state) { int timer_fd = state->timer_poll_fd; if (timer_fd < 0) { return; } bool updated = false; struct xdpw_timer *timer; wl_list_for_each(timer, &state->timers, link) { if (state->next_timer == NULL || timespec_less(&timer->at, &state->next_timer->at)) { state->next_timer = timer; updated = true; } } if (updated) { struct itimerspec delay = { .it_value = state->next_timer->at }; errno = 0; int ret = timerfd_settime(timer_fd, TFD_TIMER_ABSTIME, &delay, NULL); if (ret < 0) { fprintf(stderr, "failed to timerfd_settime(): %s\n", strerror(errno)); } } } struct xdpw_timer *xdpw_add_timer(struct xdpw_state *state, uint64_t delay_ns, xdpw_event_loop_timer_func_t func, void *data) { struct xdpw_timer *timer = calloc(1, sizeof(struct xdpw_timer)); if (timer == NULL) { logprint(ERROR, "Timer allocation failed"); return NULL; } timer->state = state; timer->func = func; timer->user_data = data; wl_list_insert(&state->timers, &timer->link); clock_gettime(CLOCK_MONOTONIC, &timer->at); timespec_add(&timer->at, delay_ns); update_timer(state); return timer; } void xdpw_destroy_timer(struct xdpw_timer *timer) { if (timer == NULL) { return; } struct xdpw_state *state = timer->state; if (state->next_timer == timer) { state->next_timer = NULL; } wl_list_remove(&timer->link); free(timer); update_timer(state); } xdg-desktop-portal-wlr-0.7.1/src/core/timespec_util.c000066400000000000000000000015011455600706000226020ustar00rootroot00000000000000#include "timespec_util.h" #include void timespec_add(struct timespec *t, int64_t delta_ns) { int delta_ns_low = delta_ns % TIMESPEC_NSEC_PER_SEC; int delta_s_high = delta_ns / TIMESPEC_NSEC_PER_SEC; t->tv_sec += delta_s_high; t->tv_nsec += (long)delta_ns_low; if (t->tv_nsec >= TIMESPEC_NSEC_PER_SEC) { t->tv_nsec -= TIMESPEC_NSEC_PER_SEC; ++t->tv_sec; } } bool timespec_less(struct timespec *t1, struct timespec *t2) { if (t1->tv_sec != t2->tv_sec) { return t1->tv_sec < t2->tv_sec; } return t1->tv_nsec < t2->tv_nsec; } bool timespec_is_zero(struct timespec *t) { return t->tv_sec == 0 && t->tv_nsec == 0; } int64_t timespec_diff_ns(struct timespec *t1, struct timespec *t2) { int64_t s = t1->tv_sec - t2->tv_sec; int64_t ns = t1->tv_nsec - t2->tv_nsec; return s * TIMESPEC_NSEC_PER_SEC + ns; } xdg-desktop-portal-wlr-0.7.1/src/screencast/000077500000000000000000000000001455600706000207755ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.7.1/src/screencast/fps_limit.c000066400000000000000000000035751455600706000231410ustar00rootroot00000000000000#include "fps_limit.h" #include "logger.h" #include "timespec_util.h" #include #include #include #include #define FPS_MEASURE_PERIOD_SEC 5.0 void measure_fps(struct fps_limit_state *state, struct timespec *now); void fps_limit_measure_start(struct fps_limit_state *state, double max_fps) { if (max_fps <= 0.0) { return; } clock_gettime(CLOCK_MONOTONIC, &state->frame_last_time); } uint64_t fps_limit_measure_end(struct fps_limit_state *state, double max_fps) { if (max_fps <= 0.0) { return 0; } // `fps_limit_measure_start` was not called? assert(!timespec_is_zero(&state->frame_last_time)); struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); int64_t elapsed_ns = timespec_diff_ns(&now, &state->frame_last_time); measure_fps(state, &now); int64_t target_ns = (1.0 / max_fps) * TIMESPEC_NSEC_PER_SEC; int64_t delay_ns = target_ns - elapsed_ns; if (delay_ns > 0) { logprint(TRACE, "fps_limit: elapsed time since the last measurement: %u, " "target %u, should delay for %u (ns)", elapsed_ns, target_ns, delay_ns); return delay_ns; } else { logprint(TRACE, "fps_limit: elapsed time since the last measurement: %u, " "target %u, target not met (ns)", elapsed_ns, target_ns); return 0; } } void measure_fps(struct fps_limit_state *state, struct timespec *now) { if (timespec_is_zero(&state->fps_last_time)) { state->fps_last_time = *now; return; } state->fps_frame_count++; int64_t elapsed_ns = timespec_diff_ns(now, &state->fps_last_time); double elapsed_sec = (double) elapsed_ns / (double) TIMESPEC_NSEC_PER_SEC; if (elapsed_sec < FPS_MEASURE_PERIOD_SEC) { return; } double avg_frames_per_sec = state->fps_frame_count / elapsed_sec; logprint(DEBUG, "fps_limit: average FPS in the last %0.2f seconds: %0.2f", elapsed_sec, avg_frames_per_sec); state->fps_last_time = *now; state->fps_frame_count = 0; } xdg-desktop-portal-wlr-0.7.1/src/screencast/pipewire_screencast.c000066400000000000000000000566321455600706000252130ustar00rootroot00000000000000#include "pipewire_screencast.h" #include #include #include #include #include #include #include #include #include #include #include #include "wlr_screencast.h" #include "xdpw.h" #include "logger.h" static struct spa_pod *build_buffer(struct spa_pod_builder *b, uint32_t blocks, uint32_t size, uint32_t stride, uint32_t datatype) { assert(blocks > 0); assert(datatype > 0); struct spa_pod_frame f[1]; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers); spa_pod_builder_add(b, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(XDPW_PWR_BUFFERS, XDPW_PWR_BUFFERS_MIN, 32), 0); spa_pod_builder_add(b, SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks), 0); if (size > 0) { spa_pod_builder_add(b, SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), 0); } if (stride > 0) { spa_pod_builder_add(b, SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride), 0); } spa_pod_builder_add(b, SPA_PARAM_BUFFERS_align, SPA_POD_Int(XDPW_PWR_ALIGN), 0); spa_pod_builder_add(b, SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(datatype), 0); return spa_pod_builder_pop(b, &f[0]); } static struct spa_pod *fixate_format(struct spa_pod_builder *b, enum spa_video_format format, uint32_t width, uint32_t height, uint32_t framerate, uint64_t *modifier) { struct spa_pod_frame f[1]; enum spa_video_format format_without_alpha = xdpw_format_pw_strip_alpha(format); spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); /* format */ if (modifier || format_without_alpha == SPA_VIDEO_FORMAT_UNKNOWN) { spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); } else { spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(3, format, format, format_without_alpha), 0); } /* modifiers */ if (modifier) { // implicit modifier spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); spa_pod_builder_long(b, *modifier); } spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(width, height)), 0); // variable framerate spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(0, 1)), 0); spa_pod_builder_add(b, SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction( &SPA_FRACTION(framerate, 1), &SPA_FRACTION(1, 1), &SPA_FRACTION(framerate, 1)), 0); return spa_pod_builder_pop(b, &f[0]); } static struct spa_pod *build_format(struct spa_pod_builder *b, enum spa_video_format format, uint32_t width, uint32_t height, uint32_t framerate, uint64_t *modifiers, int modifier_count) { struct spa_pod_frame f[2]; int i, c; enum spa_video_format format_without_alpha = xdpw_format_pw_strip_alpha(format); spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); /* format */ if (modifier_count > 0 || format_without_alpha == SPA_VIDEO_FORMAT_UNKNOWN) { // modifiers are defined only in combinations with their format // we should not announce the format without alpha spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); } else { spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(3, format, format, format_without_alpha), 0); } /* modifiers */ if (modifier_count > 0) { // build an enumeration of modifiers spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE); spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); // modifiers from the array for (i = 0, c = 0; i < modifier_count; i++) { spa_pod_builder_long(b, modifiers[i]); if (c++ == 0) spa_pod_builder_long(b, modifiers[i]); } spa_pod_builder_pop(b, &f[1]); } spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(width, height)), 0); // variable framerate spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(0, 1)), 0); spa_pod_builder_add(b, SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction( &SPA_FRACTION(framerate, 1), &SPA_FRACTION(1, 1), &SPA_FRACTION(framerate, 1)), 0); return spa_pod_builder_pop(b, &f[0]); } static bool build_modifierlist(struct xdpw_screencast_instance *cast, uint32_t drm_format, uint64_t **modifiers, uint32_t *modifier_count) { if (!wlr_query_dmabuf_modifiers(cast->ctx, drm_format, 0, NULL, modifier_count)) { *modifiers = NULL; *modifier_count = 0; return false; } if (*modifier_count == 0) { logprint(INFO, "wlroots: no modifiers available for format %u", drm_format); *modifiers = NULL; return true; } *modifiers = calloc(*modifier_count, sizeof(uint64_t)); bool ret = wlr_query_dmabuf_modifiers(cast->ctx, drm_format, *modifier_count, *modifiers, modifier_count); logprint(INFO, "wlroots: num_modififiers %d", *modifier_count); return ret; } static uint32_t build_formats(struct spa_pod_builder *b[static 2], struct xdpw_screencast_instance *cast, const struct spa_pod *params[static 2]) { uint32_t param_count; uint32_t modifier_count; uint64_t *modifiers = NULL; if (!cast->avoid_dmabufs && build_modifierlist(cast, cast->screencopy_frame_info[DMABUF].format, &modifiers, &modifier_count) && modifier_count > 0) { param_count = 2; params[0] = build_format(b[0], xdpw_format_pw_from_drm_fourcc(cast->screencopy_frame_info[DMABUF].format), cast->screencopy_frame_info[DMABUF].width, cast->screencopy_frame_info[DMABUF].height, cast->framerate, modifiers, modifier_count); assert(params[0] != NULL); params[1] = build_format(b[1], xdpw_format_pw_from_drm_fourcc(cast->screencopy_frame_info[WL_SHM].format), cast->screencopy_frame_info[WL_SHM].width, cast->screencopy_frame_info[WL_SHM].height, cast->framerate, NULL, 0); assert(params[1] != NULL); } else { param_count = 1; params[0] = build_format(b[0], xdpw_format_pw_from_drm_fourcc(cast->screencopy_frame_info[WL_SHM].format), cast->screencopy_frame_info[WL_SHM].width, cast->screencopy_frame_info[WL_SHM].height, cast->framerate, NULL, 0); assert(params[0] != NULL); } free(modifiers); return param_count; } static void pwr_handle_stream_state_changed(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct xdpw_screencast_instance *cast = data; cast->node_id = pw_stream_get_node_id(cast->stream); logprint(INFO, "pipewire: stream state changed to \"%s\"", pw_stream_state_as_string(state)); logprint(INFO, "pipewire: node id is %d", (int)cast->node_id); switch (state) { case PW_STREAM_STATE_STREAMING: cast->pwr_stream_state = true; if (cast->frame_state == XDPW_FRAME_STATE_NONE) { xdpw_wlr_frame_start(cast); } break; case PW_STREAM_STATE_PAUSED: if (old == PW_STREAM_STATE_STREAMING) { xdpw_pwr_enqueue_buffer(cast); } // fall through default: cast->pwr_stream_state = false; break; } } static void pwr_handle_stream_param_changed(void *data, uint32_t id, const struct spa_pod *param) { logprint(TRACE, "pipewire: stream parameters changed"); struct xdpw_screencast_instance *cast = data; struct pw_stream *stream = cast->stream; uint8_t params_buffer[3][1024]; struct spa_pod_dynamic_builder b[3]; const struct spa_pod *params[4]; uint32_t blocks; uint32_t data_type; if (!param || id != SPA_PARAM_Format) { return; } spa_pod_dynamic_builder_init(&b[0], params_buffer[0], sizeof(params_buffer[0]), 2048); spa_pod_dynamic_builder_init(&b[1], params_buffer[1], sizeof(params_buffer[1]), 2048); spa_pod_dynamic_builder_init(&b[2], params_buffer[2], sizeof(params_buffer[2]), 2048); spa_format_video_raw_parse(param, &cast->pwr_format); cast->framerate = (uint32_t)(cast->pwr_format.max_framerate.num / cast->pwr_format.max_framerate.denom); const struct spa_pod_prop *prop_modifier; if ((prop_modifier = spa_pod_find_prop(param, NULL, SPA_FORMAT_VIDEO_modifier)) != NULL) { cast->buffer_type = DMABUF; data_type = 1<pwr_format.format == xdpw_format_pw_from_drm_fourcc(cast->screencopy_frame_info[DMABUF].format)); if ((prop_modifier->flags & SPA_POD_PROP_FLAG_DONT_FIXATE) > 0) { const struct spa_pod *pod_modifier = &prop_modifier->value; uint32_t n_modifiers = SPA_POD_CHOICE_N_VALUES(pod_modifier) - 1; uint64_t *modifiers = SPA_POD_CHOICE_VALUES(pod_modifier); modifiers++; uint32_t flags = GBM_BO_USE_RENDERING; uint64_t modifier; uint32_t n_params; struct spa_pod_builder *builder[2] = {&b[0].b, &b[1].b}; struct gbm_bo *bo = gbm_bo_create_with_modifiers2(cast->ctx->gbm, cast->screencopy_frame_info[cast->buffer_type].width, cast->screencopy_frame_info[cast->buffer_type].height, cast->screencopy_frame_info[cast->buffer_type].format, modifiers, n_modifiers, flags); if (bo) { modifier = gbm_bo_get_modifier(bo); gbm_bo_destroy(bo); goto fixate_format; } logprint(INFO, "pipewire: unable to allocate a dmabuf with modifiers. Falling back to the old api"); for (uint32_t i = 0; i < n_modifiers; i++) { switch (modifiers[i]) { case DRM_FORMAT_MOD_INVALID: flags = cast->ctx->state->config->screencast_conf.force_mod_linear ? GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR : GBM_BO_USE_RENDERING; break; case DRM_FORMAT_MOD_LINEAR: flags = GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR; break; default: continue; } bo = gbm_bo_create(cast->ctx->gbm, cast->screencopy_frame_info[cast->buffer_type].width, cast->screencopy_frame_info[cast->buffer_type].height, cast->screencopy_frame_info[cast->buffer_type].format, flags); if (bo) { modifier = gbm_bo_get_modifier(bo); gbm_bo_destroy(bo); goto fixate_format; } } logprint(WARN, "pipewire: unable to allocate a dmabuf. Falling back to shm"); cast->avoid_dmabufs = true; n_params = build_formats(builder, cast, ¶ms[0]); pw_stream_update_params(stream, params, n_params); spa_pod_dynamic_builder_clean(&b[0]); spa_pod_dynamic_builder_clean(&b[1]); spa_pod_dynamic_builder_clean(&b[2]); return; fixate_format: params[0] = fixate_format(&b[2].b, xdpw_format_pw_from_drm_fourcc(cast->screencopy_frame_info[cast->buffer_type].format), cast->screencopy_frame_info[cast->buffer_type].width, cast->screencopy_frame_info[cast->buffer_type].height, cast->framerate, &modifier); n_params = build_formats(builder, cast, ¶ms[1]); n_params++; pw_stream_update_params(stream, params, n_params); spa_pod_dynamic_builder_clean(&b[0]); spa_pod_dynamic_builder_clean(&b[1]); spa_pod_dynamic_builder_clean(&b[2]); return; } if (cast->pwr_format.modifier == DRM_FORMAT_MOD_INVALID) { blocks = 1; } else { blocks = gbm_device_get_format_modifier_plane_count(cast->ctx->gbm, cast->screencopy_frame_info[DMABUF].format, cast->pwr_format.modifier); } } else { cast->buffer_type = WL_SHM; blocks = 1; data_type = 1<buffer_type, data_type); logprint(DEBUG, "pipewire: format: %u", cast->pwr_format.format); logprint(DEBUG, "pipewire: modifier: %lu", cast->pwr_format.modifier); logprint(DEBUG, "pipewire: size: (%u, %u)", cast->pwr_format.size.width, cast->pwr_format.size.height); logprint(DEBUG, "pipewire: max_framerate: (%u / %u)", cast->pwr_format.max_framerate.num, cast->pwr_format.max_framerate.denom); params[0] = build_buffer(&b[0].b, blocks, cast->screencopy_frame_info[cast->buffer_type].size, cast->screencopy_frame_info[cast->buffer_type].stride, data_type); params[1] = spa_pod_builder_add_object(&b[1].b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); params[2] = spa_pod_builder_add_object(&b[1].b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoTransform), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_videotransform))); params[3] = spa_pod_builder_add_object(&b[2].b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage), SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int( sizeof(struct spa_meta_region) * 4, sizeof(struct spa_meta_region) * 1, sizeof(struct spa_meta_region) * 4)); pw_stream_update_params(stream, params, 4); spa_pod_dynamic_builder_clean(&b[0]); spa_pod_dynamic_builder_clean(&b[1]); spa_pod_dynamic_builder_clean(&b[2]); } static void pwr_handle_stream_add_buffer(void *data, struct pw_buffer *buffer) { struct xdpw_screencast_instance *cast = data; struct spa_data *d; enum spa_data_type t; logprint(DEBUG, "pipewire: add buffer event handle"); d = buffer->buffer->datas; // Select buffer type from negotiation result if ((d[0].type & (1u << SPA_DATA_MemFd)) > 0) { assert(cast->buffer_type == WL_SHM); t = SPA_DATA_MemFd; } else if ((d[0].type & (1u << SPA_DATA_DmaBuf)) > 0) { assert(cast->buffer_type == DMABUF); t = SPA_DATA_DmaBuf; } else { logprint(ERROR, "pipewire: unsupported buffer type"); cast->err = 1; return; } logprint(TRACE, "pipewire: selected buffertype %u", t); struct xdpw_buffer *xdpw_buffer = xdpw_buffer_create(cast, cast->buffer_type, &cast->screencopy_frame_info[cast->buffer_type]); if (xdpw_buffer == NULL) { logprint(ERROR, "pipewire: failed to create xdpw buffer"); cast->err = 1; return; } wl_list_insert(&cast->buffer_list, &xdpw_buffer->link); buffer->user_data = xdpw_buffer; assert(xdpw_buffer->plane_count >= 0 && buffer->buffer->n_datas == (uint32_t)xdpw_buffer->plane_count); for (uint32_t plane = 0; plane < buffer->buffer->n_datas; plane++) { d[plane].type = t; d[plane].maxsize = xdpw_buffer->size[plane]; d[plane].mapoffset = 0; d[plane].chunk->size = xdpw_buffer->size[plane]; d[plane].chunk->stride = xdpw_buffer->stride[plane]; d[plane].chunk->offset = xdpw_buffer->offset[plane]; d[plane].flags = 0; d[plane].fd = xdpw_buffer->fd[plane]; d[plane].data = NULL; // clients have implemented to check chunk->size if the buffer is valid instead // of using the flags. Until they are patched we should use some arbitrary value. if (xdpw_buffer->buffer_type == DMABUF && d[plane].chunk->size == 0) { d[plane].chunk->size = 9; // This was choosen by a fair d20. } } } static void pwr_handle_stream_remove_buffer(void *data, struct pw_buffer *buffer) { struct xdpw_screencast_instance *cast = data; logprint(DEBUG, "pipewire: remove buffer event handle"); struct xdpw_buffer *xdpw_buffer = buffer->user_data; if (xdpw_buffer) { xdpw_buffer_destroy(xdpw_buffer); } if (cast->current_frame.pw_buffer == buffer) { cast->current_frame.pw_buffer = NULL; } for (uint32_t plane = 0; plane < buffer->buffer->n_datas; plane++) { buffer->buffer->datas[plane].fd = -1; } buffer->user_data = NULL; } static const struct pw_stream_events pwr_stream_events = { PW_VERSION_STREAM_EVENTS, .state_changed = pwr_handle_stream_state_changed, .param_changed = pwr_handle_stream_param_changed, .add_buffer = pwr_handle_stream_add_buffer, .remove_buffer = pwr_handle_stream_remove_buffer, }; void xdpw_pwr_dequeue_buffer(struct xdpw_screencast_instance *cast) { logprint(TRACE, "pipewire: dequeueing buffer"); assert(!cast->current_frame.pw_buffer); if ((cast->current_frame.pw_buffer = pw_stream_dequeue_buffer(cast->stream)) == NULL) { logprint(WARN, "pipewire: out of buffers"); return; } cast->current_frame.xdpw_buffer = cast->current_frame.pw_buffer->user_data; } void xdpw_pwr_enqueue_buffer(struct xdpw_screencast_instance *cast) { logprint(TRACE, "pipewire: enqueueing buffer"); if (!cast->current_frame.pw_buffer) { logprint(WARN, "pipewire: no buffer to queue"); goto done; } struct pw_buffer *pw_buf = cast->current_frame.pw_buffer; struct spa_buffer *spa_buf = pw_buf->buffer; struct spa_data *d = spa_buf->datas; bool buffer_corrupt = cast->frame_state != XDPW_FRAME_STATE_SUCCESS; if (cast->current_frame.y_invert) { //TODO: Flip buffer or set stride negative buffer_corrupt = true; cast->err = 1; } logprint(TRACE, "********************"); struct spa_meta_header *h; if ((h = spa_buffer_find_meta_data(spa_buf, SPA_META_Header, sizeof(*h)))) { h->pts = SPA_TIMESPEC_TO_NSEC(&cast->current_frame); h->flags = buffer_corrupt ? SPA_META_HEADER_FLAG_CORRUPTED : 0; h->seq = cast->seq++; h->dts_offset = 0; logprint(TRACE, "pipewire: timestamp %"PRId64, h->pts); } struct spa_meta_videotransform *vt; if ((vt = spa_buffer_find_meta_data(spa_buf, SPA_META_VideoTransform, sizeof(*vt)))) { vt->transform = cast->target->output->transformation; logprint(TRACE, "pipewire: transformation %u", vt->transform); } struct spa_meta *damage; if ((damage = spa_buffer_find_meta(spa_buf, SPA_META_VideoDamage))) { struct spa_region *d_region = spa_meta_first(damage); uint32_t damage_counter = 0; do { if (damage_counter >= cast->current_frame.damage_count) { *d_region = SPA_REGION(0, 0, 0, 0); logprint(TRACE, "pipewire: end damage %u %u,%u (%ux%u)", damage_counter, d_region->position.x, d_region->position.y, d_region->size.width, d_region->size.height); break; } *d_region = SPA_REGION(cast->current_frame.damage[damage_counter].x, cast->current_frame.damage[damage_counter].y, cast->current_frame.damage[damage_counter].width, cast->current_frame.damage[damage_counter].height); logprint(TRACE, "pipewire: damage %u %u,%u (%ux%u)", damage_counter, d_region->position.x, d_region->position.y, d_region->size.width, d_region->size.height); damage_counter++; } while (spa_meta_check(d_region + 1, damage) && d_region++); if (damage_counter < cast->current_frame.damage_count) { struct xdpw_frame_damage damage = {d_region->position.x, d_region->position.x, d_region->size.width, d_region->size.height}; for (; damage_counter < cast->current_frame.damage_count; damage_counter++) { damage = merge_damage(&damage, &cast->current_frame.damage[damage_counter]); } *d_region = SPA_REGION(damage.x, damage.y, damage.width, damage.height); logprint(TRACE, "pipewire: collected damage %u %u,%u (%ux%u)", damage_counter, d_region->position.x, d_region->position.y, d_region->size.width, d_region->size.height); } } if (buffer_corrupt) { for (uint32_t plane = 0; plane < spa_buf->n_datas; plane++) { d[plane].chunk->flags = SPA_CHUNK_FLAG_CORRUPTED; } } else { for (uint32_t plane = 0; plane < spa_buf->n_datas; plane++) { d[plane].chunk->flags = SPA_CHUNK_FLAG_NONE; } } for (uint32_t plane = 0; plane < spa_buf->n_datas; plane++) { logprint(TRACE, "pipewire: plane %d", plane); logprint(TRACE, "pipewire: fd %u", d[plane].fd); logprint(TRACE, "pipewire: maxsize %d", d[plane].maxsize); logprint(TRACE, "pipewire: size %d", d[plane].chunk->size); logprint(TRACE, "pipewire: stride %d", d[plane].chunk->stride); logprint(TRACE, "pipewire: offset %d", d[plane].chunk->offset); logprint(TRACE, "pipewire: chunk flags %d", d[plane].chunk->flags); } logprint(TRACE, "pipewire: width %d", cast->current_frame.xdpw_buffer->width); logprint(TRACE, "pipewire: height %d", cast->current_frame.xdpw_buffer->height); logprint(TRACE, "pipewire: y_invert %d", cast->current_frame.y_invert); logprint(TRACE, "********************"); pw_stream_queue_buffer(cast->stream, pw_buf); done: cast->current_frame.xdpw_buffer = NULL; cast->current_frame.pw_buffer = NULL; } void pwr_update_stream_param(struct xdpw_screencast_instance *cast) { logprint(TRACE, "pipewire: stream update parameters"); struct pw_stream *stream = cast->stream; uint8_t params_buffer[2][1024]; struct spa_pod_dynamic_builder b[2]; spa_pod_dynamic_builder_init(&b[0], params_buffer[0], sizeof(params_buffer[0]), 2048); spa_pod_dynamic_builder_init(&b[1], params_buffer[1], sizeof(params_buffer[1]), 2048); const struct spa_pod *params[2]; struct spa_pod_builder *builder[2] = {&b[0].b, &b[1].b}; uint32_t n_params = build_formats(builder, cast, params); pw_stream_update_params(stream, params, n_params); spa_pod_dynamic_builder_clean(&b[0]); spa_pod_dynamic_builder_clean(&b[1]); } void xdpw_pwr_stream_create(struct xdpw_screencast_instance *cast) { struct xdpw_screencast_context *ctx = cast->ctx; struct xdpw_state *state = ctx->state; pw_loop_enter(state->pw_loop); uint8_t buffer[2][1024]; struct spa_pod_dynamic_builder b[2]; spa_pod_dynamic_builder_init(&b[0], buffer[0], sizeof(buffer[0]), 2048); spa_pod_dynamic_builder_init(&b[1], buffer[1], sizeof(buffer[1]), 2048); const struct spa_pod *params[2]; char name[] = "xdpw-stream-XXXXXX"; randname(name + strlen(name) - 6); cast->stream = pw_stream_new(ctx->core, name, pw_properties_new( PW_KEY_MEDIA_CLASS, "Video/Source", NULL)); if (!cast->stream) { logprint(ERROR, "pipewire: failed to create stream"); abort(); } cast->pwr_stream_state = false; struct spa_pod_builder *builder[2] = {&b[0].b, &b[1].b}; uint32_t param_count = build_formats(builder, cast, params); spa_pod_dynamic_builder_clean(&b[0]); spa_pod_dynamic_builder_clean(&b[1]); pw_stream_add_listener(cast->stream, &cast->stream_listener, &pwr_stream_events, cast); pw_stream_connect(cast->stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, (PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS), params, param_count); } void xdpw_pwr_stream_destroy(struct xdpw_screencast_instance *cast) { if (!cast->stream) { return; } logprint(DEBUG, "pipewire: destroying stream"); pw_stream_flush(cast->stream, false); pw_stream_disconnect(cast->stream); pw_stream_destroy(cast->stream); cast->stream = NULL; } static void on_core_error(void *data, uint32_t id, int seq, int res, const char* message) { // If our pipewire connection drops then we won't be able to actually // do a screencast. Exit the process so someone restarts us and the // new xdpw can reconnect to pipewire. logprint(ERROR, "pipewire: fatal error event from core"); exit(1); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .error = on_core_error, }; static struct spa_hook core_listener; int xdpw_pwr_context_create(struct xdpw_state *state) { struct xdpw_screencast_context *ctx = &state->screencast; logprint(DEBUG, "pipewire: establishing connection to core"); if (!ctx->pwr_context) { ctx->pwr_context = pw_context_new(state->pw_loop, NULL, 0); if (!ctx->pwr_context) { logprint(ERROR, "pipewire: failed to create context"); return -1; } } if (!ctx->core) { ctx->core = pw_context_connect(ctx->pwr_context, NULL, 0); if (!ctx->core) { logprint(ERROR, "pipewire: couldn't connect to context"); return -1; } // Setup a core listener to detect errors / disconnects // (i.e. in case the pipewire daemon is restarted). spa_zero(core_listener); pw_core_add_listener(ctx->core, &core_listener, &core_events, state); } return 0; } void xdpw_pwr_context_destroy(struct xdpw_state *state) { struct xdpw_screencast_context *ctx = &state->screencast; logprint(DEBUG, "pipewire: disconnecting fom core"); if (ctx->core) { pw_core_disconnect(ctx->core); ctx->core = NULL; } if (ctx->pwr_context) { pw_context_destroy(ctx->pwr_context); ctx->pwr_context = NULL; } } xdg-desktop-portal-wlr-0.7.1/src/screencast/screencast.c000066400000000000000000000505741455600706000233060ustar00rootroot00000000000000#include "screencast.h" #include #include #include #include #include #include #include #include #include #include #include "pipewire_screencast.h" #include "wlr_screencast.h" #include "xdpw.h" #include "logger.h" static const char object_path[] = "/org/freedesktop/portal/desktop"; static const char interface_name[] = "org.freedesktop.impl.portal.ScreenCast"; void exec_with_shell(char *command) { pid_t pid1 = fork(); if (pid1 < 0) { perror("fork"); return; } else if (pid1 == 0) { pid_t pid2 = fork(); if (pid2 < 0) { perror("fork"); } else if (pid2 == 0) { char *const argv[] = { "sh", "-c", command, NULL, }; execvp("sh", argv); perror("execvp"); _exit(127); } _exit(0); } int stat; if (waitpid(pid1, &stat, 0) < 0) { perror("waitpid"); } } void xdpw_screencast_instance_init(struct xdpw_screencast_context *ctx, struct xdpw_screencast_instance *cast, struct xdpw_screencast_target *target) { // only run exec_before if there's no other instance running that already ran it if (wl_list_empty(&ctx->screencast_instances)) { char *exec_before = ctx->state->config->screencast_conf.exec_before; if (exec_before) { logprint(INFO, "xdpw: executing %s before screencast", exec_before); exec_with_shell(exec_before); } } cast->ctx = ctx; cast->target = target; if (ctx->state->config->screencast_conf.max_fps > 0) { cast->max_framerate = ctx->state->config->screencast_conf.max_fps < (uint32_t)target->output->framerate ? ctx->state->config->screencast_conf.max_fps : (uint32_t)target->output->framerate; } else { cast->max_framerate = (uint32_t)target->output->framerate; } cast->framerate = cast->max_framerate; cast->refcount = 1; cast->node_id = SPA_ID_INVALID; cast->avoid_dmabufs = false; cast->teardown = false; wl_list_init(&cast->buffer_list); logprint(INFO, "xdpw: screencast instance %p has %d references", cast, cast->refcount); wl_list_insert(&ctx->screencast_instances, &cast->link); logprint(INFO, "xdpw: %d active screencast instances", wl_list_length(&ctx->screencast_instances)); } void xdpw_screencast_instance_destroy(struct xdpw_screencast_instance *cast) { assert(cast->refcount == 0); // Fails assert if called by screencast_finish logprint(DEBUG, "xdpw: destroying cast instance"); // make sure this is the last running instance that is being destroyed if (wl_list_length(&cast->link) == 1) { char *exec_after = cast->ctx->state->config->screencast_conf.exec_after; if (exec_after) { logprint(INFO, "xdpw: executing %s after screencast", exec_after); exec_with_shell(exec_after); } } free(cast->target); wl_list_remove(&cast->link); xdpw_pwr_stream_destroy(cast); assert(wl_list_length(&cast->buffer_list) == 0); free(cast); } void xdpw_screencast_instance_teardown(struct xdpw_screencast_instance *cast) { struct xdpw_session *sess, *tmp; wl_list_for_each_safe(sess, tmp, &cast->ctx->state->xdpw_sessions, link) { if (sess->screencast_data.screencast_instance == cast) { xdpw_session_destroy(sess); } } } bool setup_target(struct xdpw_screencast_context *ctx, struct xdpw_session *sess, struct xdpw_screencast_restore_data *data) { bool target_initialized = false; struct xdpw_wlr_output *output, *tmp_o; wl_list_for_each_reverse_safe(output, tmp_o, &ctx->output_list, link) { logprint(INFO, "wlroots: capturable output: %s model: %s: id: %i name: %s", output->make, output->model, output->id, output->name); } struct xdpw_screencast_target *target = calloc(1, sizeof(struct xdpw_screencast_target)); if (!target) { logprint(ERROR, "wlroots: unable to allocate target"); return false; } target->with_cursor = sess->screencast_data.cursor_mode == EMBEDDED; if (data) { target_initialized = xdpw_wlr_target_from_data(ctx, target, data); } if (!target_initialized) { target_initialized = xdpw_wlr_target_chooser(ctx, target); //TODO: Chooser option to confirm the persist mode const char *env_persist_str = getenv("XDPW_PERSIST_MODE"); if (env_persist_str) { if (strcmp(env_persist_str, "transient") == 0) { sess->screencast_data.persist_mode = sess->screencast_data.persist_mode > PERSIST_TRANSIENT ? PERSIST_TRANSIENT : sess->screencast_data.persist_mode; } else if (strcmp(env_persist_str, "permanent") == 0) { sess->screencast_data.persist_mode = sess->screencast_data.persist_mode > PERSIST_PERMANENT ? PERSIST_PERMANENT : sess->screencast_data.persist_mode; } else { sess->screencast_data.persist_mode = PERSIST_NONE; } } else { sess->screencast_data.persist_mode = PERSIST_NONE; } } if (!target_initialized) { logprint(ERROR, "wlroots: no output found"); free(target); return false; } assert(target->output); // Disable screencast sharing to avoid sharing between dmabuf and shm capable clients /* struct xdpw_screencast_instance *cast, *tmp_c; wl_list_for_each_reverse_safe(cast, tmp_c, &ctx->screencast_instances, link) { logprint(INFO, "xdpw: existing screencast instance: %d %s cursor", cast->target->output->id, cast->target->with_cursor ? "with" : "without"); if (cast->target->output->id == target->output->id && cast->target->with_cursor == target->with_cursor) { if (cast->refcount == 0) { logprint(DEBUG, "xdpw: matching cast instance found, " "but is already scheduled for destruction, skipping"); } else { sess->screencast_data.screencast_instance = cast; ++cast->refcount; } logprint(INFO, "xdpw: screencast instance %p now has %d references", cast, cast->refcount); } } */ if (!sess->screencast_data.screencast_instance) { sess->screencast_data.screencast_instance = calloc(1, sizeof(struct xdpw_screencast_instance)); xdpw_screencast_instance_init(ctx, sess->screencast_data.screencast_instance, target); } logprint(INFO, "wlroots: output: %s", sess->screencast_data.screencast_instance->target->output->name); return true; } static int start_screencast(struct xdpw_screencast_instance *cast) { xdpw_wlr_register_cb(cast); // process at least one frame so that we know // some of the metadata required for the pipewire // remote state connected event wl_display_dispatch(cast->ctx->state->wl_display); wl_display_roundtrip(cast->ctx->state->wl_display); if (cast->screencopy_frame_info[WL_SHM].format == DRM_FORMAT_INVALID || (cast->ctx->state->screencast_version >= 3 && cast->screencopy_frame_info[DMABUF].format == DRM_FORMAT_INVALID)) { logprint(INFO, "wlroots: unable to receive a valid format from wlr_screencopy"); return -1; } xdpw_pwr_stream_create(cast); cast->initialized = true; return 0; } static int method_screencast_create_session(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { struct xdpw_state *state = data; int ret = 0; logprint(INFO, "dbus: create session method invoked"); char *request_handle, *session_handle, *app_id; ret = sd_bus_message_read(msg, "oos", &request_handle, &session_handle, &app_id); if (ret < 0) { return ret; } ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); if (ret < 0) { return ret; } logprint(INFO, "dbus: request_handle: %s", request_handle); logprint(INFO, "dbus: session_handle: %s", session_handle); logprint(INFO, "dbus: app_id: %s", app_id); char *key; int innerRet = 0; while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { innerRet = sd_bus_message_read(msg, "s", &key); if (innerRet < 0) { return innerRet; } if (strcmp(key, "session_handle_token") == 0) { char *token; sd_bus_message_read(msg, "v", "s", &token); logprint(INFO, "dbus: option token: %s", token); } else { logprint(WARN, "dbus: unknown option: %s", key); sd_bus_message_skip(msg, "v"); } innerRet = sd_bus_message_exit_container(msg); if (innerRet < 0) { return innerRet; } } if (ret < 0) { return ret; } ret = sd_bus_message_exit_container(msg); if (ret < 0) { return ret; } struct xdpw_request *req = xdpw_request_create(sd_bus_message_get_bus(msg), request_handle); if (req == NULL) { return -ENOMEM; } struct xdpw_session *sess = xdpw_session_create(state, sd_bus_message_get_bus(msg), strdup(session_handle)); if (sess == NULL) { return -ENOMEM; } sd_bus_message *reply = NULL; ret = sd_bus_message_new_method_return(msg, &reply); if (ret < 0) { return ret; } ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_SUCCESS, 0); if (ret < 0) { return ret; } ret = sd_bus_send(NULL, reply, NULL); if (ret < 0) { return ret; } sd_bus_message_unref(reply); return 0; } static int method_screencast_select_sources(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { struct xdpw_state *state = data; struct xdpw_screencast_context *ctx = &state->screencast; int ret = 0; struct xdpw_session *sess, *tmp_s; sd_bus_message *reply = NULL; logprint(INFO, "dbus: select sources method invoked"); char *request_handle, *session_handle, *app_id; ret = sd_bus_message_read(msg, "oos", &request_handle, &session_handle, &app_id); if (ret < 0) { return ret; } ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); if (ret < 0) { return ret; } logprint(INFO, "dbus: request_handle: %s", request_handle); logprint(INFO, "dbus: session_handle: %s", session_handle); logprint(INFO, "dbus: app_id: %s", app_id); sess = NULL; wl_list_for_each_reverse_safe(sess, tmp_s, &state->xdpw_sessions, link) { if (strcmp(sess->session_handle, session_handle) == 0) { logprint(DEBUG, "dbus: select sources: found matching session %s", sess->session_handle); break; } } if (!sess) { logprint(WARN, "dbus: select sources: no matching session %s found", sess->session_handle); goto error; } // default to embedded cursor mode if not specified sess->screencast_data.cursor_mode = EMBEDDED; // default to no persist if not specified sess->screencast_data.persist_mode = PERSIST_NONE; char *key; int innerRet = 0; struct xdpw_screencast_restore_data restore_data = {0}; while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { innerRet = sd_bus_message_read(msg, "s", &key); if (innerRet < 0) { return innerRet; } if (strcmp(key, "multiple") == 0) { int multiple; sd_bus_message_read(msg, "v", "b", &multiple); logprint(INFO, "dbus: option multiple: %d", multiple); } else if (strcmp(key, "types") == 0) { uint32_t mask; sd_bus_message_read(msg, "v", "u", &mask); if (mask & (1<screencast_data.cursor_mode); if (sess->screencast_data.cursor_mode & METADATA) { logprint(ERROR, "dbus: unsupported cursor mode requested, cancelling"); goto error; } logprint(INFO, "dbus: option cursor_mode:%x", sess->screencast_data.cursor_mode); } else if (strcmp(key, "restore_data") == 0) { logprint(INFO, "dbus: restore data available"); char *portal_vendor; innerRet = sd_bus_message_enter_container(msg, 'v', "(suv)"); if (innerRet < 0) { logprint(ERROR, "dbus: error entering variant"); return innerRet; } innerRet = sd_bus_message_enter_container(msg, 'r', "suv"); if (innerRet < 0) { logprint(ERROR, "dbus: error entering struct"); return innerRet; } sd_bus_message_read(msg, "s", &portal_vendor); if (strcmp(portal_vendor, "wlroots") != 0) { logprint(INFO, "dbus: skipping restore_data from another vendor (%s)", portal_vendor); sd_bus_message_skip(msg, "uv"); continue; } sd_bus_message_read(msg, "u", &restore_data.version); if (restore_data.version == 1) { innerRet = sd_bus_message_enter_container(msg, 'v', "a{sv}"); if (innerRet < 0) { return innerRet; } innerRet = sd_bus_message_enter_container(msg, 'a', "{sv}"); if (innerRet < 0) { return innerRet; } logprint(INFO, "dbus: restoring session from data"); int rdRet; char *rdKey; while ((innerRet = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { rdRet = sd_bus_message_read(msg, "s", &rdKey); if (rdRet < 0) { return rdRet; } if (strcmp(rdKey, "output_name") == 0) { sd_bus_message_read(msg, "v", "s", &restore_data.output_name); logprint(INFO, "dbus: option restore_data.output_name: %s", restore_data.output_name); } else { logprint(WARN, "dbus: unknown option %s", rdKey); sd_bus_message_skip(msg, "v"); } innerRet = sd_bus_message_exit_container(msg); // dictionary if (innerRet < 0) { return innerRet; } } if (innerRet < 0) { return innerRet; } innerRet = sd_bus_message_exit_container(msg); //array if (innerRet < 0) { return innerRet; } innerRet = sd_bus_message_exit_container(msg); //variant if (innerRet < 0) { return innerRet; } } else { sd_bus_message_skip(msg, "v"); logprint(ERROR, "Unknown restore_data version: %u", restore_data.version); } innerRet = sd_bus_message_exit_container(msg); // struct if (innerRet < 0) { return innerRet; } innerRet = sd_bus_message_exit_container(msg); // variant if (innerRet < 0) { return innerRet; } } else if (strcmp(key, "persist_mode") == 0) { sd_bus_message_read(msg, "v", "u", &sess->screencast_data.persist_mode); logprint(INFO, "dbus: option persist_mode:%u", sess->screencast_data.persist_mode); } else { logprint(WARN, "dbus: unknown option %s", key); sd_bus_message_skip(msg, "v"); } innerRet = sd_bus_message_exit_container(msg); if (ret < 0) { return ret; } } if (ret < 0) { return ret; } ret = sd_bus_message_exit_container(msg); if (ret < 0) { return ret; } bool output_selection_canceled = !setup_target(ctx, sess, restore_data.version > 0 ? &restore_data : NULL); ret = sd_bus_message_new_method_return(msg, &reply); if (ret < 0) { return ret; } if (output_selection_canceled) { ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_CANCELLED, 0); } else { ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_SUCCESS, 0); } if (ret < 0) { return ret; } ret = sd_bus_send(NULL, reply, NULL); if (ret < 0) { return ret; } sd_bus_message_unref(reply); return 0; error: if (sess) { xdpw_session_destroy(sess); } ret = sd_bus_message_new_method_return(msg, &reply); if (ret < 0) { return ret; } ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_CANCELLED, 0); if (ret < 0) { return ret; } ret = sd_bus_send(NULL, reply, NULL); if (ret < 0) { return ret; } sd_bus_message_unref(reply); return -1; } static int method_screencast_start(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { struct xdpw_state *state = data; int ret = 0; logprint(INFO, "dbus: start method invoked"); char *request_handle, *session_handle, *app_id, *parent_window; ret = sd_bus_message_read(msg, "ooss", &request_handle, &session_handle, &app_id, &parent_window); if (ret < 0) { return ret; } ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); if (ret < 0) { return ret; } logprint(INFO, "dbus: request_handle: %s", request_handle); logprint(INFO, "dbus: session_handle: %s", session_handle); logprint(INFO, "dbus: app_id: %s", app_id); logprint(INFO, "dbus: parent_window: %s", parent_window); char *key; int innerRet = 0; while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { innerRet = sd_bus_message_read(msg, "s", &key); if (innerRet < 0) { return innerRet; } logprint(WARN, "dbus: unknown option: %s", key); sd_bus_message_skip(msg, "v"); innerRet = sd_bus_message_exit_container(msg); if (innerRet < 0) { return innerRet; } } if (ret < 0) { return ret; } ret = sd_bus_message_exit_container(msg); if (ret < 0) { return ret; } struct xdpw_screencast_instance *cast = NULL; struct xdpw_session *sess, *tmp_s; wl_list_for_each_reverse_safe(sess, tmp_s, &state->xdpw_sessions, link) { if (strcmp(sess->session_handle, session_handle) == 0) { logprint(DEBUG, "dbus: start: found matching session %s", sess->session_handle); cast = sess->screencast_data.screencast_instance; } } if (!cast) { return -1; } if (!cast->initialized) { ret = start_screencast(cast); } if (ret < 0) { return ret; } while (cast->node_id == SPA_ID_INVALID) { int ret = pw_loop_iterate(state->pw_loop, 0); if (ret < 0) { logprint(ERROR, "pipewire_loop_iterate failed: %s", spa_strerror(ret)); return ret; } } sd_bus_message *reply = NULL; ret = sd_bus_message_new_method_return(msg, &reply); if (ret < 0) { return ret; } logprint(DEBUG, "dbus: start: returning node %d", (int)cast->node_id); ret = sd_bus_message_append(reply, "u", PORTAL_RESPONSE_SUCCESS); if (ret < 0) { return ret; } ret = sd_bus_message_open_container(reply, 'a', "{sv}"); if (ret < 0) { return ret; } ret = sd_bus_message_open_container(reply, 'e', "sv"); if (ret < 0) { return ret; } ret = sd_bus_message_append(reply, "s", "streams"); if (ret < 0) { return ret; } ret = sd_bus_message_open_container(reply, 'v', "a(ua{sv})"); if (ret < 0) { return ret; } ret = sd_bus_message_open_container(reply, 'a', "(ua{sv})"); if (ret < 0) { return ret; } ret = sd_bus_message_open_container(reply, 'r', "ua{sv}"); if (ret < 0) { return ret; } ret = sd_bus_message_append(reply, "u", cast->node_id); if (ret < 0) { return ret; } ret = sd_bus_message_open_container(reply, 'a', "{sv}"); if (ret < 0) { return ret; } if (cast->target->output->xdg_output) { ret = sd_bus_message_append(reply, "{sv}", "position", "(ii)", cast->target->output->x, cast->target->output->y); if (ret < 0) { return ret; } ret = sd_bus_message_append(reply, "{sv}", "size", "(ii)", cast->target->output->width, cast->target->output->height); if (ret < 0) { return ret; } } ret = sd_bus_message_append(reply, "{sv}", "source_type", "u", MONITOR); if (ret < 0) { return ret; } ret = sd_bus_message_close_container(reply); if (ret < 0) { return ret; } ret = sd_bus_message_close_container(reply); if (ret < 0) { return ret; } ret = sd_bus_message_close_container(reply); if (ret < 0) { return ret; } ret = sd_bus_message_close_container(reply); if (ret < 0) { return ret; } ret = sd_bus_message_close_container(reply); if (ret < 0) { return ret; } ret = sd_bus_message_append(reply, "{sv}", "persist_mode", "u", sess->screencast_data.persist_mode); if (ret < 0) { return ret; } if (sess->screencast_data.persist_mode != PERSIST_NONE) { struct xdpw_screencast_restore_data restore_data; restore_data.output_name = cast->target->output->name; ret = sd_bus_message_append(reply, "{sv}", "restore_data", "(suv)", "wlroots", XDP_CAST_DATA_VER, "a{sv}", 1, "output_name", "s", restore_data.output_name); if (ret < 0) { return ret; } } ret = sd_bus_message_close_container(reply); if (ret < 0) { return ret; } ret = sd_bus_send(NULL, reply, NULL); if (ret < 0) { return ret; } sd_bus_message_unref(reply); return 0; } static const sd_bus_vtable screencast_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("CreateSession", "oosa{sv}", "ua{sv}", method_screencast_create_session, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("SelectSources", "oosa{sv}", "ua{sv}", method_screencast_select_sources, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("Start", "oossa{sv}", "ua{sv}", method_screencast_start, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_PROPERTY("AvailableSourceTypes", "u", NULL, offsetof(struct xdpw_state, screencast_source_types), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("AvailableCursorModes", "u", NULL, offsetof(struct xdpw_state, screencast_cursor_modes), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("version", "u", NULL, offsetof(struct xdpw_state, screencast_version), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_VTABLE_END }; int xdpw_screencast_init(struct xdpw_state *state) { sd_bus_slot *slot = NULL; state->screencast = (struct xdpw_screencast_context) { 0 }; state->screencast.state = state; int err; err = xdpw_pwr_context_create(state); if (err) { goto fail_pipewire; } err = xdpw_wlr_screencopy_init(state); if (err) { goto fail_screencopy; } return sd_bus_add_object_vtable(state->bus, &slot, object_path, interface_name, screencast_vtable, state); fail_screencopy: xdpw_wlr_screencopy_finish(&state->screencast); fail_pipewire: xdpw_pwr_context_destroy(state); return err; } xdg-desktop-portal-wlr-0.7.1/src/screencast/screencast_common.c000066400000000000000000000310701455600706000246440ustar00rootroot00000000000000#include "xdpw.h" #include "screencast_common.h" #include #include #include #include #include #include #include #include "linux-dmabuf-unstable-v1-client-protocol.h" #include "logger.h" void randname(char *buf) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); long r = ts.tv_nsec; for (int i = 0; i < 6; ++i) { assert(buf[i] == 'X'); buf[i] = 'A'+(r&15)+(r&16)*2; r >>= 5; } } static char *gbm_find_render_node(drmDevice *device) { drmDevice *devices[64]; char *render_node = NULL; int n = drmGetDevices2(0, devices, sizeof(devices) / sizeof(devices[0])); for (int i = 0; i < n; ++i) { drmDevice *dev = devices[i]; if (device && !drmDevicesEqual(device, dev)) { continue; } if (!(dev->available_nodes & (1 << DRM_NODE_RENDER))) continue; render_node = strdup(dev->nodes[DRM_NODE_RENDER]); break; } drmFreeDevices(devices, n); return render_node; } struct gbm_device *xdpw_gbm_device_create(drmDevice *device) { struct gbm_device *gbm; char *render_node = NULL; render_node = gbm_find_render_node(device); if (render_node == NULL) { logprint(ERROR, "xdpw: Could not find render node"); return NULL; } logprint(INFO, "xdpw: Using render node %s", render_node); int fd = open(render_node, O_RDWR | O_CLOEXEC); if (fd < 0) { logprint(ERROR, "xdpw: Could not open render node %s", render_node); free(render_node); return NULL; } free(render_node); gbm = gbm_create_device(fd); return gbm; } static int anonymous_shm_open(void) { char name[] = "/xdpw-shm-XXXXXX"; int retries = 100; do { randname(name + strlen(name) - 6); --retries; // shm_open guarantees that O_CLOEXEC is set int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); if (fd >= 0) { shm_unlink(name); return fd; } } while (retries > 0 && errno == EEXIST); return -1; } static struct wl_buffer *import_wl_shm_buffer(struct xdpw_screencast_instance *cast, int fd, enum wl_shm_format fmt, int width, int height, int stride) { struct xdpw_screencast_context *ctx = cast->ctx; int size = stride * height; if (fd < 0) { return NULL; } struct wl_shm_pool *pool = wl_shm_create_pool(ctx->shm, fd, size); struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, fmt); wl_shm_pool_destroy(pool); return buffer; } struct xdpw_buffer *xdpw_buffer_create(struct xdpw_screencast_instance *cast, enum buffer_type buffer_type, struct xdpw_screencopy_frame_info *frame_info) { struct xdpw_buffer *buffer = calloc(1, sizeof(struct xdpw_buffer)); buffer->width = frame_info->width; buffer->height = frame_info->height; buffer->format = frame_info->format; buffer->buffer_type = buffer_type; switch (buffer_type) { case WL_SHM: buffer->plane_count = 1; buffer->size[0] = frame_info->size; buffer->stride[0] = frame_info->stride; buffer->offset[0] = 0; buffer->fd[0] = anonymous_shm_open(); if (buffer->fd[0] == -1) { logprint(ERROR, "xdpw: unable to create anonymous filedescriptor"); free(buffer); return NULL; } if (ftruncate(buffer->fd[0], buffer->size[0]) < 0) { logprint(ERROR, "xdpw: unable to truncate filedescriptor"); close(buffer->fd[0]); free(buffer); return NULL; } buffer->buffer = import_wl_shm_buffer(cast, buffer->fd[0], xdpw_format_wl_shm_from_drm_fourcc(frame_info->format), frame_info->width, frame_info->height, frame_info->stride); if (buffer->buffer == NULL) { logprint(ERROR, "xdpw: unable to create wl_buffer"); close(buffer->fd[0]); free(buffer); return NULL; } break; case DMABUF:; uint32_t flags = GBM_BO_USE_RENDERING; if (cast->pwr_format.modifier != DRM_FORMAT_MOD_INVALID) { uint64_t *modifiers = (uint64_t*)&cast->pwr_format.modifier; buffer->bo = gbm_bo_create_with_modifiers2(cast->ctx->gbm, frame_info->width, frame_info->height, frame_info->format, modifiers, 1, flags); } else { if (cast->ctx->state->config->screencast_conf.force_mod_linear) { flags |= GBM_BO_USE_LINEAR; } buffer->bo = gbm_bo_create(cast->ctx->gbm, frame_info->width, frame_info->height, frame_info->format, flags); } // Fallback for linear buffers via the implicit api if (buffer->bo == NULL && cast->pwr_format.modifier == DRM_FORMAT_MOD_LINEAR) { buffer->bo = gbm_bo_create(cast->ctx->gbm, frame_info->width, frame_info->height, frame_info->format, flags | GBM_BO_USE_LINEAR); } if (buffer->bo == NULL) { logprint(ERROR, "xdpw: failed to create gbm_bo"); free(buffer); return NULL; } buffer->plane_count = gbm_bo_get_plane_count(buffer->bo); struct zwp_linux_buffer_params_v1 *params; params = zwp_linux_dmabuf_v1_create_params(cast->ctx->linux_dmabuf); if (!params) { logprint(ERROR, "xdpw: failed to create linux_buffer_params"); gbm_bo_destroy(buffer->bo); free(buffer); return NULL; } for (int plane = 0; plane < buffer->plane_count; plane++) { buffer->size[plane] = 0; buffer->stride[plane] = gbm_bo_get_stride_for_plane(buffer->bo, plane); buffer->offset[plane] = gbm_bo_get_offset(buffer->bo, plane); uint64_t mod = gbm_bo_get_modifier(buffer->bo); buffer->fd[plane] = gbm_bo_get_fd_for_plane(buffer->bo, plane); if (buffer->fd[plane] < 0) { logprint(ERROR, "xdpw: failed to get file descriptor"); zwp_linux_buffer_params_v1_destroy(params); gbm_bo_destroy(buffer->bo); for (int plane_tmp = 0; plane_tmp < plane; plane_tmp++) { close(buffer->fd[plane_tmp]); } free(buffer); return NULL; } zwp_linux_buffer_params_v1_add(params, buffer->fd[plane], plane, buffer->offset[plane], buffer->stride[plane], mod >> 32, mod & 0xffffffff); } buffer->buffer = zwp_linux_buffer_params_v1_create_immed(params, buffer->width, buffer->height, buffer->format, /* flags */ 0); zwp_linux_buffer_params_v1_destroy(params); if (!buffer->buffer) { logprint(ERROR, "xdpw: failed to create buffer"); gbm_bo_destroy(buffer->bo); for (int plane = 0; plane < buffer->plane_count; plane++) { close(buffer->fd[plane]); } free(buffer); return NULL; } } return buffer; } void xdpw_buffer_destroy(struct xdpw_buffer *buffer) { wl_buffer_destroy(buffer->buffer); if (buffer->buffer_type == DMABUF) { gbm_bo_destroy(buffer->bo); } for (int plane = 0; plane < buffer->plane_count; plane++) { close(buffer->fd[plane]); } wl_list_remove(&buffer->link); free(buffer); } bool wlr_query_dmabuf_modifiers(struct xdpw_screencast_context *ctx, uint32_t drm_format, uint32_t num_modifiers, uint64_t *modifiers, uint32_t *max_modifiers) { if (ctx->format_modifier_pairs.size == 0) return false; struct xdpw_format_modifier_pair *fm_pair; if (num_modifiers == 0) { *max_modifiers = 0; wl_array_for_each(fm_pair, &ctx->format_modifier_pairs) { if (fm_pair->fourcc == drm_format && (fm_pair->modifier == DRM_FORMAT_MOD_INVALID || gbm_device_get_format_modifier_plane_count(ctx->gbm, fm_pair->fourcc, fm_pair->modifier) > 0)) *max_modifiers += 1; } return true; } uint32_t i = 0; wl_array_for_each(fm_pair, &ctx->format_modifier_pairs) { if (i == num_modifiers) break; if (fm_pair->fourcc == drm_format && (fm_pair->modifier == DRM_FORMAT_MOD_INVALID || gbm_device_get_format_modifier_plane_count(ctx->gbm, fm_pair->fourcc, fm_pair->modifier) > 0)) { modifiers[i] = fm_pair->modifier; i++; } } *max_modifiers = num_modifiers; return true; } enum wl_shm_format xdpw_format_wl_shm_from_drm_fourcc(uint32_t format) { switch (format) { case DRM_FORMAT_ARGB8888: return WL_SHM_FORMAT_ARGB8888; case DRM_FORMAT_XRGB8888: return WL_SHM_FORMAT_XRGB8888; case DRM_FORMAT_RGBA8888: case DRM_FORMAT_RGBX8888: case DRM_FORMAT_ABGR8888: case DRM_FORMAT_XBGR8888: case DRM_FORMAT_BGRA8888: case DRM_FORMAT_BGRX8888: case DRM_FORMAT_NV12: case DRM_FORMAT_XRGB2101010: case DRM_FORMAT_XBGR2101010: case DRM_FORMAT_RGBX1010102: case DRM_FORMAT_BGRX1010102: case DRM_FORMAT_ARGB2101010: case DRM_FORMAT_ABGR2101010: case DRM_FORMAT_RGBA1010102: case DRM_FORMAT_BGRA1010102: case DRM_FORMAT_BGR888: case DRM_FORMAT_RGB888: return (enum wl_shm_format)format; default: logprint(ERROR, "xdg-desktop-portal-wlr: unsupported drm " "format 0x%08x", format); abort(); } } uint32_t xdpw_format_drm_fourcc_from_wl_shm(enum wl_shm_format format) { switch (format) { case WL_SHM_FORMAT_ARGB8888: return DRM_FORMAT_ARGB8888; case WL_SHM_FORMAT_XRGB8888: return DRM_FORMAT_XRGB8888; case WL_SHM_FORMAT_RGBA8888: case WL_SHM_FORMAT_RGBX8888: case WL_SHM_FORMAT_ABGR8888: case WL_SHM_FORMAT_XBGR8888: case WL_SHM_FORMAT_BGRA8888: case WL_SHM_FORMAT_BGRX8888: case WL_SHM_FORMAT_NV12: case WL_SHM_FORMAT_XRGB2101010: case WL_SHM_FORMAT_XBGR2101010: case WL_SHM_FORMAT_RGBX1010102: case WL_SHM_FORMAT_BGRX1010102: case WL_SHM_FORMAT_ARGB2101010: case WL_SHM_FORMAT_ABGR2101010: case WL_SHM_FORMAT_RGBA1010102: case WL_SHM_FORMAT_BGRA1010102: case WL_SHM_FORMAT_BGR888: case WL_SHM_FORMAT_RGB888: return (uint32_t)format; default: logprint(ERROR, "xdg-desktop-portal-wlr: unsupported wl_shm " "format 0x%08x", format); abort(); } } enum spa_video_format xdpw_format_pw_from_drm_fourcc(uint32_t format) { switch (format) { case DRM_FORMAT_ARGB8888: return SPA_VIDEO_FORMAT_BGRA; case DRM_FORMAT_XRGB8888: return SPA_VIDEO_FORMAT_BGRx; case DRM_FORMAT_RGBA8888: return SPA_VIDEO_FORMAT_ABGR; case DRM_FORMAT_RGBX8888: return SPA_VIDEO_FORMAT_xBGR; case DRM_FORMAT_ABGR8888: return SPA_VIDEO_FORMAT_RGBA; case DRM_FORMAT_XBGR8888: return SPA_VIDEO_FORMAT_RGBx; case DRM_FORMAT_BGRA8888: return SPA_VIDEO_FORMAT_ARGB; case DRM_FORMAT_BGRX8888: return SPA_VIDEO_FORMAT_xRGB; case DRM_FORMAT_NV12: return SPA_VIDEO_FORMAT_NV12; case DRM_FORMAT_XRGB2101010: return SPA_VIDEO_FORMAT_xRGB_210LE; case DRM_FORMAT_XBGR2101010: return SPA_VIDEO_FORMAT_xBGR_210LE; case DRM_FORMAT_RGBX1010102: return SPA_VIDEO_FORMAT_RGBx_102LE; case DRM_FORMAT_BGRX1010102: return SPA_VIDEO_FORMAT_BGRx_102LE; case DRM_FORMAT_ARGB2101010: return SPA_VIDEO_FORMAT_ARGB_210LE; case DRM_FORMAT_ABGR2101010: return SPA_VIDEO_FORMAT_ABGR_210LE; case DRM_FORMAT_RGBA1010102: return SPA_VIDEO_FORMAT_RGBA_102LE; case DRM_FORMAT_BGRA1010102: return SPA_VIDEO_FORMAT_BGRA_102LE; case DRM_FORMAT_BGR888: return SPA_VIDEO_FORMAT_RGB; case DRM_FORMAT_RGB888: return SPA_VIDEO_FORMAT_BGR; default: logprint(ERROR, "xdg-desktop-portal-wlr: failed to convert drm " "format 0x%08x to spa_video_format", format); abort(); } } enum spa_video_format xdpw_format_pw_strip_alpha(enum spa_video_format format) { switch (format) { case SPA_VIDEO_FORMAT_BGRA: return SPA_VIDEO_FORMAT_BGRx; case SPA_VIDEO_FORMAT_ABGR: return SPA_VIDEO_FORMAT_xBGR; case SPA_VIDEO_FORMAT_RGBA: return SPA_VIDEO_FORMAT_RGBx; case SPA_VIDEO_FORMAT_ARGB: return SPA_VIDEO_FORMAT_xRGB; case SPA_VIDEO_FORMAT_ARGB_210LE: return SPA_VIDEO_FORMAT_xRGB_210LE; case SPA_VIDEO_FORMAT_ABGR_210LE: return SPA_VIDEO_FORMAT_xBGR_210LE; case SPA_VIDEO_FORMAT_RGBA_102LE: return SPA_VIDEO_FORMAT_RGBx_102LE; case SPA_VIDEO_FORMAT_BGRA_102LE: return SPA_VIDEO_FORMAT_BGRx_102LE; default: return SPA_VIDEO_FORMAT_UNKNOWN; } } enum xdpw_chooser_types get_chooser_type(const char *chooser_type) { if (!chooser_type || strcmp(chooser_type, "default") == 0) { return XDPW_CHOOSER_DEFAULT; } else if (strcmp(chooser_type, "none") == 0) { return XDPW_CHOOSER_NONE; } else if (strcmp(chooser_type, "simple") == 0) { return XDPW_CHOOSER_SIMPLE; } else if (strcmp(chooser_type, "dmenu") == 0) { return XDPW_CHOOSER_DMENU; } fprintf(stderr, "Could not understand chooser type %s\n", chooser_type); exit(1); } const char *chooser_type_str(enum xdpw_chooser_types chooser_type) { switch (chooser_type) { case XDPW_CHOOSER_DEFAULT: return "default"; case XDPW_CHOOSER_NONE: return "none"; case XDPW_CHOOSER_SIMPLE: return "simple"; case XDPW_CHOOSER_DMENU: return "dmenu"; } fprintf(stderr, "Could not find chooser type %d\n", chooser_type); abort(); } struct xdpw_frame_damage merge_damage(struct xdpw_frame_damage *damage1, struct xdpw_frame_damage *damage2) { struct xdpw_frame_damage damage; uint32_t x0, y0; damage.x = damage1->x < damage2->y ? damage1->x : damage2->x; damage.y = damage1->y < damage2->y ? damage1->y : damage2->y; x0 = damage1->x + damage1->width < damage2->x + damage2->width ? damage2->x + damage2->width : damage1->x + damage1->width; y0 = damage1->y + damage1->height < damage2->y + damage2->height ? damage2->y + damage2->height : damage1->y + damage1->height; damage.width = x0 - damage.x; damage.height = y0 - damage.y; return damage; } xdg-desktop-portal-wlr-0.7.1/src/screencast/wlr_screencast.c000066400000000000000000000732141455600706000241660ustar00rootroot00000000000000#include "wlr_screencast.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" #include "wlr-screencopy-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "screencast.h" #include "pipewire_screencast.h" #include "xdpw.h" #include "logger.h" #include "fps_limit.h" void wlr_frame_free(struct xdpw_screencast_instance *cast) { if (!cast->wlr_frame) { return; } zwlr_screencopy_frame_v1_destroy(cast->wlr_frame); cast->wlr_frame = NULL; logprint(TRACE, "wlroots: frame destroyed"); } void xdpw_wlr_frame_finish(struct xdpw_screencast_instance *cast) { logprint(TRACE, "wlroots: finish screencopy"); wlr_frame_free(cast); if (cast->quit || cast->err) { // TODO: revisit the exit condition (remove quit?) // and clean up sessions that still exist if err // is the cause of the instance_destroy call xdpw_screencast_instance_destroy(cast); return; } if (!cast->pwr_stream_state) { cast->frame_state = XDPW_FRAME_STATE_NONE; return; } if (cast->frame_state == XDPW_FRAME_STATE_RENEG) { pwr_update_stream_param(cast); } if (cast->frame_state == XDPW_FRAME_STATE_FAILED) { xdpw_pwr_enqueue_buffer(cast); } if (cast->frame_state == XDPW_FRAME_STATE_SUCCESS) { xdpw_pwr_enqueue_buffer(cast); uint64_t delay_ns = fps_limit_measure_end(&cast->fps_limit, cast->framerate); if (delay_ns > 0) { xdpw_add_timer(cast->ctx->state, delay_ns, (xdpw_event_loop_timer_func_t) xdpw_wlr_frame_start, cast); return; } } xdpw_wlr_frame_start(cast); } void xdpw_wlr_frame_start(struct xdpw_screencast_instance *cast) { logprint(TRACE, "wlroots: start screencopy"); if (cast->quit || cast->err) { xdpw_screencast_instance_destroy(cast); return; } if (cast->initialized && !cast->pwr_stream_state) { cast->frame_state = XDPW_FRAME_STATE_NONE; return; } cast->frame_state = XDPW_FRAME_STATE_STARTED; xdpw_wlr_register_cb(cast); } static void wlr_frame_buffer_done(void *data, struct zwlr_screencopy_frame_v1 *frame); static void wlr_frame_buffer(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { struct xdpw_screencast_instance *cast = data; if (!frame) { return; } logprint(TRACE, "wlroots: buffer event handler"); cast->wlr_frame = frame; cast->screencopy_frame_info[WL_SHM].width = width; cast->screencopy_frame_info[WL_SHM].height = height; cast->screencopy_frame_info[WL_SHM].stride = stride; cast->screencopy_frame_info[WL_SHM].size = stride * height; cast->screencopy_frame_info[WL_SHM].format = xdpw_format_drm_fourcc_from_wl_shm(format); if (zwlr_screencopy_manager_v1_get_version(cast->ctx->screencopy_manager) < 3) { wlr_frame_buffer_done(cast, frame); } } static void wlr_frame_linux_dmabuf(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t format, uint32_t width, uint32_t height) { struct xdpw_screencast_instance *cast = data; if (!frame) { return; } logprint(TRACE, "wlroots: linux_dmabuf event handler"); cast->screencopy_frame_info[DMABUF].width = width; cast->screencopy_frame_info[DMABUF].height = height; cast->screencopy_frame_info[DMABUF].format = format; } static void wlr_frame_buffer_done(void *data, struct zwlr_screencopy_frame_v1 *frame) { struct xdpw_screencast_instance *cast = data; if (!frame) { return; } logprint(TRACE, "wlroots: buffer_done event handler"); if (!cast->initialized) { xdpw_wlr_frame_finish(cast); return; } // Check if announced screencopy information is compatible with pipewire meta if ((cast->pwr_format.format != xdpw_format_pw_from_drm_fourcc(cast->screencopy_frame_info[cast->buffer_type].format) && cast->pwr_format.format != xdpw_format_pw_strip_alpha(xdpw_format_pw_from_drm_fourcc(cast->screencopy_frame_info[cast->buffer_type].format))) || cast->pwr_format.size.width != cast->screencopy_frame_info[cast->buffer_type].width || cast->pwr_format.size.height != cast->screencopy_frame_info[cast->buffer_type].height) { logprint(DEBUG, "wlroots: pipewire and wlroots metadata are incompatible. Renegotiate stream"); cast->frame_state = XDPW_FRAME_STATE_RENEG; xdpw_wlr_frame_finish(cast); return; } if (!cast->current_frame.xdpw_buffer) { xdpw_pwr_dequeue_buffer(cast); } if (!cast->current_frame.xdpw_buffer) { logprint(WARN, "wlroots: no current buffer"); xdpw_wlr_frame_finish(cast); return; } assert(cast->current_frame.xdpw_buffer); // Check if dequeued buffer is compatible with announced buffer if (( cast->buffer_type == WL_SHM && (cast->current_frame.xdpw_buffer->size[0] != cast->screencopy_frame_info[cast->buffer_type].size || cast->current_frame.xdpw_buffer->stride[0] != cast->screencopy_frame_info[cast->buffer_type].stride)) || cast->current_frame.xdpw_buffer->width != cast->screencopy_frame_info[cast->buffer_type].width || cast->current_frame.xdpw_buffer->height != cast->screencopy_frame_info[cast->buffer_type].height) { logprint(DEBUG, "wlroots: pipewire buffer has wrong dimensions"); cast->frame_state = XDPW_FRAME_STATE_FAILED; xdpw_wlr_frame_finish(cast); return; } cast->current_frame.damage_count = 0; zwlr_screencopy_frame_v1_copy_with_damage(frame, cast->current_frame.xdpw_buffer->buffer); logprint(TRACE, "wlroots: frame copied"); fps_limit_measure_start(&cast->fps_limit, cast->framerate); } static void wlr_frame_flags(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t flags) { struct xdpw_screencast_instance *cast = data; if (!frame) { return; } logprint(TRACE, "wlroots: flags event handler"); cast->current_frame.y_invert = flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT; } static void wlr_frame_damage(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { struct xdpw_screencast_instance *cast = data; if (!frame) { return; } logprint(TRACE, "wlroots: damage event handler"); logprint(TRACE, "wlroots: damage %"PRIu32": %"PRIu32",%"PRIu32"x%"PRIu32",%"PRIu32, cast->current_frame.damage_count, x, y, width, height); struct xdpw_frame_damage damage = {x, y, width, height}; if (cast->current_frame.damage_count < 4) { cast->current_frame.damage[cast->current_frame.damage_count++] = damage; } else { cast->current_frame.damage[3] = merge_damage(&cast->current_frame.damage[3], &damage); } } static void wlr_frame_ready(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { struct xdpw_screencast_instance *cast = data; if (!frame) { return; } logprint(TRACE, "wlroots: ready event handler"); cast->current_frame.tv_sec = ((((uint64_t)tv_sec_hi) << 32) | tv_sec_lo); cast->current_frame.tv_nsec = tv_nsec; logprint(TRACE, "wlroots: timestamp %"PRIu64":%"PRIu32, cast->current_frame.tv_sec, cast->current_frame.tv_nsec); cast->frame_state = XDPW_FRAME_STATE_SUCCESS; xdpw_wlr_frame_finish(cast); } static void wlr_frame_failed(void *data, struct zwlr_screencopy_frame_v1 *frame) { struct xdpw_screencast_instance *cast = data; if (!frame) { return; } logprint(TRACE, "wlroots: failed event handler"); cast->frame_state = XDPW_FRAME_STATE_FAILED; xdpw_wlr_frame_finish(cast); } static const struct zwlr_screencopy_frame_v1_listener wlr_frame_listener = { .buffer = wlr_frame_buffer, .buffer_done = wlr_frame_buffer_done, .linux_dmabuf = wlr_frame_linux_dmabuf, .flags = wlr_frame_flags, .ready = wlr_frame_ready, .failed = wlr_frame_failed, .damage = wlr_frame_damage, }; void xdpw_wlr_register_cb(struct xdpw_screencast_instance *cast) { cast->frame_callback = zwlr_screencopy_manager_v1_capture_output( cast->ctx->screencopy_manager, cast->target->with_cursor, cast->target->output->output); zwlr_screencopy_frame_v1_add_listener(cast->frame_callback, &wlr_frame_listener, cast); logprint(TRACE, "wlroots: callbacks registered"); } static void wlr_output_handle_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t phys_width, int32_t phys_height, int32_t subpixel, const char *make, const char *model, int32_t transform) { struct xdpw_wlr_output *output = data; output->make = strdup(make); output->model = strdup(model); output->transformation = transform; } static void wlr_output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { if (flags & WL_OUTPUT_MODE_CURRENT) { struct xdpw_wlr_output *output = data; output->framerate = (float)refresh/1000; } } static void wlr_output_handle_done(void *data, struct wl_output *wl_output) { /* Nothing to do */ } static void wlr_output_handle_scale(void *data, struct wl_output *wl_output, int32_t factor) { /* Nothing to do */ } static void wlr_output_handle_name(void *data, struct wl_output *wl_output, const char *name) { struct xdpw_wlr_output *output = data; output->name = strdup(name); } static void wlr_output_handle_description(void *data, struct wl_output *wl_output, const char *description) { /* Nothing to do */ } static const struct wl_output_listener wlr_output_listener = { .geometry = wlr_output_handle_geometry, .mode = wlr_output_handle_mode, .done = wlr_output_handle_done, .scale = wlr_output_handle_scale, .name = wlr_output_handle_name, .description = wlr_output_handle_description, }; static void xdg_output_handle_logical_position(void *data, struct zxdg_output_v1 *xdg_output_v1, int32_t x, int32_t y) { struct xdpw_wlr_output *output = data; output->x = x; output->y = y; } static void xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output_v1, int32_t width, int32_t height) { struct xdpw_wlr_output *output = data; output->width = width; output->height = height; } static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output_v1) { /* Nothing to do */ } static void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output_v1, const char *name) { /* Nothing to do */ } static void xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output_v1, const char *description) { /* Nothing to do */ } static const struct zxdg_output_v1_listener xdg_output_listener = { .logical_position = xdg_output_handle_logical_position, .logical_size = xdg_output_handle_logical_size, .done = xdg_output_handle_done, .name = xdg_output_handle_name, .description = xdg_output_handle_description, }; static struct xdpw_wlr_output *xdpw_wlr_output_first(struct wl_list *output_list) { struct xdpw_wlr_output *output, *tmp; wl_list_for_each_safe(output, tmp, output_list, link) { return output; } return NULL; } static struct xdpw_wlr_output *xdpw_wlr_output_find_by_name(struct wl_list *output_list, const char *name) { struct xdpw_wlr_output *output, *tmp; wl_list_for_each_safe(output, tmp, output_list, link) { if (strcmp(output->name, name) == 0) { return output; } } return NULL; } static struct xdpw_wlr_output *xdpw_wlr_output_find(struct xdpw_screencast_context *ctx, struct wl_output *out, uint32_t id) { struct xdpw_wlr_output *output, *tmp; wl_list_for_each_safe(output, tmp, &ctx->output_list, link) { if ((output->output == out) || (output->id == id)) { return output; } } return NULL; } static pid_t spawn_chooser(char *cmd, int chooser_in[2], int chooser_out[2]) { logprint(TRACE, "exec chooser called: cmd %s, pipe chooser_in (%d,%d), pipe chooser_out (%d,%d)", cmd, chooser_in[0], chooser_in[1], chooser_out[0], chooser_out[1]); pid_t pid = fork(); if (pid < 0) { perror("fork"); return pid; } else if (pid == 0) { close(chooser_in[1]); close(chooser_out[0]); dup2(chooser_in[0], STDIN_FILENO); dup2(chooser_out[1], STDOUT_FILENO); close(chooser_in[0]); close(chooser_out[1]); execl("/bin/sh", "/bin/sh", "-c", cmd, NULL); perror("execl"); _exit(127); } close(chooser_in[0]); close(chooser_out[1]); return pid; } static bool wait_chooser(pid_t pid) { int status; if (waitpid(pid ,&status, 0) != -1 && WIFEXITED(status)) { return WEXITSTATUS(status) != 127; } return false; } static bool wlr_output_chooser(struct xdpw_output_chooser *chooser, struct wl_list *output_list, struct xdpw_wlr_output **output) { logprint(DEBUG, "wlroots: output chooser called"); struct xdpw_wlr_output *out; size_t name_size = 0; char *name = NULL; *output = NULL; int chooser_in[2]; //p -> c int chooser_out[2]; //c -> p if (pipe(chooser_in) == -1) { perror("pipe chooser_in"); logprint(ERROR, "Failed to open pipe chooser_in"); goto error_chooser_in; } if (pipe(chooser_out) == -1) { perror("pipe chooser_out"); logprint(ERROR, "Failed to open pipe chooser_out"); goto error_chooser_out; } pid_t pid = spawn_chooser(chooser->cmd, chooser_in, chooser_out); if (pid < 0) { logprint(ERROR, "Failed to fork chooser"); goto error_fork; } switch (chooser->type) { case XDPW_CHOOSER_DMENU:; FILE *f = fdopen(chooser_in[1], "w"); if (f == NULL) { perror("fdopen pipe chooser_in"); logprint(ERROR, "Failed to create stream writing to pipe chooser_in"); goto error_fork; } wl_list_for_each(out, output_list, link) { fprintf(f, "%s\n", out->name); } fclose(f); break; default: close(chooser_in[1]); } if (!wait_chooser(pid)) { close(chooser_out[0]); return false; } FILE *f = fdopen(chooser_out[0], "r"); if (f == NULL) { perror("fdopen pipe chooser_out"); logprint(ERROR, "Failed to create stream reading from pipe chooser_out"); close(chooser_out[0]); goto end; } ssize_t nread = getline(&name, &name_size, f); fclose(f); if (nread < 0) { perror("getline failed"); goto end; } //Strip newline char *p = strchr(name, '\n'); if (p != NULL) { *p = '\0'; } logprint(TRACE, "wlroots: output chooser %s selects output %s", chooser->cmd, name); wl_list_for_each(out, output_list, link) { if (strcmp(out->name, name) == 0) { *output = out; break; } } free(name); end: return true; error_fork: close(chooser_out[0]); close(chooser_out[1]); error_chooser_out: close(chooser_in[0]); close(chooser_in[1]); error_chooser_in: *output = NULL; return false; } static struct xdpw_wlr_output *wlr_output_chooser_default(struct wl_list *output_list) { logprint(DEBUG, "wlroots: output chooser called"); struct xdpw_output_chooser default_chooser[] = { {XDPW_CHOOSER_SIMPLE, "slurp -f %o -or"}, {XDPW_CHOOSER_DMENU, "wofi -d -n --prompt='Select the monitor to share:'"}, {XDPW_CHOOSER_DMENU, "bemenu --prompt='Select the monitor to share:'"}, }; size_t N = sizeof(default_chooser)/sizeof(default_chooser[0]); struct xdpw_wlr_output *output = NULL; bool ret; for (size_t i = 0; iname); } else { logprint(DEBUG, "wlroots: output chooser canceled"); } return output; } return xdpw_wlr_output_first(output_list); } static struct xdpw_wlr_output *xdpw_wlr_output_chooser(struct xdpw_screencast_context *ctx) { switch (ctx->state->config->screencast_conf.chooser_type) { case XDPW_CHOOSER_DEFAULT: return wlr_output_chooser_default(&ctx->output_list); case XDPW_CHOOSER_NONE: if (ctx->state->config->screencast_conf.output_name) { return xdpw_wlr_output_find_by_name(&ctx->output_list, ctx->state->config->screencast_conf.output_name); } else { return xdpw_wlr_output_first(&ctx->output_list); } case XDPW_CHOOSER_DMENU: case XDPW_CHOOSER_SIMPLE:; struct xdpw_wlr_output *output = NULL; if (!ctx->state->config->screencast_conf.chooser_cmd) { logprint(ERROR, "wlroots: no output chooser given"); goto end; } struct xdpw_output_chooser chooser = { ctx->state->config->screencast_conf.chooser_type, ctx->state->config->screencast_conf.chooser_cmd }; logprint(DEBUG, "wlroots: output chooser %s (%d)", chooser.cmd, chooser.type); bool ret = wlr_output_chooser(&chooser, &ctx->output_list, &output); if (!ret) { logprint(ERROR, "wlroots: output chooser %s failed", chooser.cmd); goto end; } if (output) { logprint(DEBUG, "wlroots: output chooser selects %s", output->name); } else { logprint(DEBUG, "wlroots: output chooser canceled"); } return output; } end: return NULL; } bool xdpw_wlr_target_chooser(struct xdpw_screencast_context *ctx, struct xdpw_screencast_target *target) { target->output = xdpw_wlr_output_chooser(ctx); return target->output != NULL; } bool xdpw_wlr_target_from_data(struct xdpw_screencast_context *ctx, struct xdpw_screencast_target *target, struct xdpw_screencast_restore_data *data) { struct xdpw_wlr_output *out = NULL; out = xdpw_wlr_output_find_by_name(&ctx->output_list, data->output_name); if (!out) { return false; } target->output = out; return true; } static void wlr_remove_output(struct xdpw_wlr_output *out) { free(out->name); free(out->make); free(out->model); if (out->xdg_output) { zxdg_output_v1_destroy(out->xdg_output); } wl_output_destroy(out->output); wl_list_remove(&out->link); free(out); } static void wlr_format_modifier_pair_add(struct xdpw_screencast_context *ctx, uint32_t format, uint64_t modifier) { struct xdpw_format_modifier_pair *fm_pair; wl_array_for_each(fm_pair, &ctx->format_modifier_pairs) { if (fm_pair->fourcc == format && fm_pair->modifier == modifier) { logprint(TRACE, "wlroots: skipping duplicated format %u (%lu)", fm_pair->fourcc, fm_pair->modifier); return; } } fm_pair = wl_array_add(&ctx->format_modifier_pairs, sizeof(struct xdpw_format_modifier_pair)); fm_pair->fourcc = format; fm_pair->modifier = modifier; logprint(TRACE, "wlroots: format %u (%lu)", fm_pair->fourcc, fm_pair->modifier); } static void linux_dmabuf_handle_modifier(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) { struct xdpw_screencast_context *ctx = data; logprint(TRACE, "wlroots: linux_dmabuf_handle_modifier called"); uint64_t modifier = (((uint64_t)modifier_hi) << 32) | modifier_lo; wlr_format_modifier_pair_add(ctx, format, modifier); } static void noop() { // This space intentionally left blank } static const struct zwp_linux_dmabuf_v1_listener linux_dmabuf_listener = { .format = noop, .modifier = linux_dmabuf_handle_modifier, }; static void linux_dmabuf_feedback_handle_main_device(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, struct wl_array *device_arr) { struct xdpw_screencast_context *ctx = data; logprint(DEBUG, "wlroots: linux_dmabuf_feedback_handle_main_device called"); assert(ctx->gbm == NULL); dev_t device; assert(device_arr->size == sizeof(device)); memcpy(&device, device_arr->data, sizeof(device)); drmDevice *drmDev; if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) { logprint(WARN, "wlroots: unable to open main device"); ctx->state->config->screencast_conf.force_mod_linear = true; return; } ctx->gbm = xdpw_gbm_device_create(drmDev); } static void linux_dmabuf_feedback_format_table(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, int fd, uint32_t size) { struct xdpw_screencast_context *ctx = data; logprint(DEBUG, "wlroots: linux_dmabuf_feedback_format_table called"); wl_array_release(&ctx->format_modifier_pairs); wl_array_init(&ctx->format_modifier_pairs); ctx->feedback_data.format_table_data = mmap(NULL , size, PROT_READ, MAP_PRIVATE, fd, 0); if (ctx->feedback_data.format_table_data == MAP_FAILED) { ctx->feedback_data.format_table_data = NULL; ctx->feedback_data.format_table_size = 0; return; } ctx->feedback_data.format_table_size = size; } static void linux_dmabuf_feedback_handle_done(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1) { struct xdpw_screencast_context *ctx = data; logprint(DEBUG, "wlroots: linux_dmabuf_feedback_handle_done called"); if (ctx->feedback_data.format_table_data) { munmap(ctx->feedback_data.format_table_data, ctx->feedback_data.format_table_size); } ctx->feedback_data.format_table_data = NULL; ctx->feedback_data.format_table_size = 0; } static void linux_dmabuf_feedback_tranche_target_devices(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, struct wl_array *device_arr) { struct xdpw_screencast_context *ctx = data; logprint(DEBUG, "wlroots: linux_dmabuf_feedback_tranche_target_devices called"); dev_t device; assert(device_arr->size == sizeof(device)); memcpy(&device, device_arr->data, sizeof(device)); drmDevice *drmDev; if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) { return; } if (ctx->gbm) { drmDevice *drmDevRenderer = NULL; drmGetDevice2(gbm_device_get_fd(ctx->gbm), /* flags */ 0, &drmDevRenderer); ctx->feedback_data.device_used = drmDevicesEqual(drmDevRenderer, drmDev); } else { ctx->gbm = xdpw_gbm_device_create(drmDev); ctx->feedback_data.device_used = ctx->gbm != NULL; } } static void linux_dmabuf_feedback_tranche_flags(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, uint32_t flags) { logprint(DEBUG, "wlroots: linux_dmabuf_feedback_tranche_flags called"); } static void linux_dmabuf_feedback_tranche_formats(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, struct wl_array *indices) { struct xdpw_screencast_context *ctx = data; logprint(DEBUG, "wlroots: linux_dmabuf_feedback_tranche_formats called"); if (!ctx->feedback_data.device_used || !ctx->feedback_data.format_table_data) { return; } struct fm_entry { uint32_t format; uint32_t padding; uint64_t modifier; }; // An entry in the table has to be 16 bytes long assert(sizeof(struct fm_entry) == 16); uint32_t n_modifiers = ctx->feedback_data.format_table_size/sizeof(struct fm_entry); struct fm_entry *fm_entry = ctx->feedback_data.format_table_data; uint16_t *idx; wl_array_for_each(idx, indices) { if (*idx >= n_modifiers) { continue; } wlr_format_modifier_pair_add(ctx, (fm_entry + *idx)->format, (fm_entry + *idx)->modifier); } } static void linux_dmabuf_feedback_tranche_done(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1) { struct xdpw_screencast_context *ctx = data; logprint(DEBUG, "wlroots: linux_dmabuf_feedback_tranche_done called"); ctx->feedback_data.device_used = false; } static const struct zwp_linux_dmabuf_feedback_v1_listener linux_dmabuf_listener_feedback = { .main_device = linux_dmabuf_feedback_handle_main_device, .format_table = linux_dmabuf_feedback_format_table, .done = linux_dmabuf_feedback_handle_done, .tranche_target_device = linux_dmabuf_feedback_tranche_target_devices, .tranche_flags = linux_dmabuf_feedback_tranche_flags, .tranche_formats = linux_dmabuf_feedback_tranche_formats, .tranche_done = linux_dmabuf_feedback_tranche_done, }; static void wlr_registry_handle_add(void *data, struct wl_registry *reg, uint32_t id, const char *interface, uint32_t ver) { struct xdpw_screencast_context *ctx = data; logprint(DEBUG, "wlroots: interface to register %s (Version: %u)",interface, ver); if (!strcmp(interface, wl_output_interface.name)) { struct xdpw_wlr_output *output = calloc(1, sizeof(*output)); output->id = id; logprint(DEBUG, "wlroots: |-- registered to interface %s (Version %u)", interface, WL_OUTPUT_VERSION); output->output = wl_registry_bind(reg, id, &wl_output_interface, WL_OUTPUT_VERSION); if (ctx->xdg_output_manager) { output->xdg_output = zxdg_output_manager_v1_get_xdg_output( ctx->xdg_output_manager, output->output); zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output); } wl_output_add_listener(output->output, &wlr_output_listener, output); wl_list_insert(&ctx->output_list, &output->link); } if (!strcmp(interface, zwlr_screencopy_manager_v1_interface.name)) { uint32_t version = ver; if (SC_MANAGER_VERSION < ver) { version = SC_MANAGER_VERSION; } else if (ver < SC_MANAGER_VERSION_MIN) { version = SC_MANAGER_VERSION_MIN; } logprint(DEBUG, "wlroots: |-- registered to interface %s (Version %u)", interface, version); ctx->screencopy_manager = wl_registry_bind( reg, id, &zwlr_screencopy_manager_v1_interface, version); } if (strcmp(interface, wl_shm_interface.name) == 0) { logprint(DEBUG, "wlroots: |-- registered to interface %s (Version %u)", interface, WL_SHM_VERSION); ctx->shm = wl_registry_bind(reg, id, &wl_shm_interface, WL_SHM_VERSION); } if (strcmp(interface, zwp_linux_dmabuf_v1_interface.name) == 0) { uint32_t version = ver; if (LINUX_DMABUF_VERSION < ver) { version = LINUX_DMABUF_VERSION; } else if (LINUX_DMABUF_VERSION_MIN > ver) { logprint(INFO, "wlroots: interface %s (Version %u) is required for DMA-BUF screencast", interface, LINUX_DMABUF_VERSION_MIN); return; } logprint(DEBUG, "wlroots: |-- registered to interface %s (Version %u)", interface, version); ctx->linux_dmabuf = wl_registry_bind(reg, id, &zwp_linux_dmabuf_v1_interface, version); if (version >= 4) { ctx->linux_dmabuf_feedback = zwp_linux_dmabuf_v1_get_default_feedback(ctx->linux_dmabuf); zwp_linux_dmabuf_feedback_v1_add_listener(ctx->linux_dmabuf_feedback, &linux_dmabuf_listener_feedback, ctx); } else { zwp_linux_dmabuf_v1_add_listener(ctx->linux_dmabuf, &linux_dmabuf_listener, ctx); } } if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 && ver >= XDG_OUTPUT_VERSION_MIN) { uint32_t version = ver; if (XDG_OUTPUT_VERSION < ver) { version = XDG_OUTPUT_VERSION; } logprint(DEBUG, "wlroots: |-- registered to interface %s (Version %u)", interface, version); ctx->xdg_output_manager = wl_registry_bind( reg, id, &zxdg_output_manager_v1_interface, version); } } static void wlr_registry_handle_remove(void *data, struct wl_registry *reg, uint32_t id) { struct xdpw_screencast_context *ctx = data; struct xdpw_wlr_output *output = xdpw_wlr_output_find(ctx, NULL, id); if (output) { logprint(DEBUG, "wlroots: output removed (%s)", output->name); struct xdpw_screencast_instance *cast, *tmp; wl_list_for_each_safe(cast, tmp, &ctx->screencast_instances, link) { if (cast->target->output == output) { // screencopy might be in process for this instance wlr_frame_free(cast); // instance might be waiting for wakeup by the frame limiter struct xdpw_timer *timer, *ttmp; wl_list_for_each_safe(timer, ttmp, &cast->ctx->state->timers, link) { if (timer->user_data == cast) { xdpw_destroy_timer(timer); } } cast->teardown = true; xdpw_screencast_instance_teardown(cast); } } wlr_remove_output(output); } } static const struct wl_registry_listener wlr_registry_listener = { .global = wlr_registry_handle_add, .global_remove = wlr_registry_handle_remove, }; int xdpw_wlr_screencopy_init(struct xdpw_state *state) { struct xdpw_screencast_context *ctx = &state->screencast; // initialize a list of outputs wl_list_init(&ctx->output_list); // initialize a list of active screencast instances wl_list_init(&ctx->screencast_instances); // initialize a list of format modifier pairs wl_array_init(&ctx->format_modifier_pairs); // retrieve registry ctx->registry = wl_display_get_registry(state->wl_display); wl_registry_add_listener(ctx->registry, &wlr_registry_listener, ctx); wl_display_roundtrip(state->wl_display); logprint(DEBUG, "wayland: registry listeners run"); if (ctx->linux_dmabuf_feedback) { wl_display_roundtrip(state->wl_display); logprint(DEBUG, "wayland: dmabuf_feedback listeners run"); } // make sure our wlroots supports shm protocol if (!ctx->shm) { logprint(ERROR, "Compositor doesn't support %s!", "wl_shm"); return -1; } // make sure our wlroots supports screencopy protocol if (!ctx->screencopy_manager) { logprint(ERROR, "Compositor doesn't support %s!", zwlr_screencopy_manager_v1_interface.name); return -1; } // make sure we have a gbm device if (ctx->linux_dmabuf && !ctx->gbm) { ctx->gbm = xdpw_gbm_device_create(NULL); if (!ctx->gbm) { logprint(ERROR, "System doesn't support gbm!"); } } if (ctx->xdg_output_manager) { struct xdpw_wlr_output *output; wl_list_for_each(output, &ctx->output_list, link) { if (!output->xdg_output) { output->xdg_output = zxdg_output_manager_v1_get_xdg_output( ctx->xdg_output_manager, output->output); zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output); } } wl_display_roundtrip(state->wl_display); logprint(DEBUG, "wayland: xdg_output listeners run"); } return 0; } void xdpw_wlr_screencopy_finish(struct xdpw_screencast_context *ctx) { wl_array_release(&ctx->format_modifier_pairs); struct xdpw_wlr_output *output, *tmp_o; wl_list_for_each_safe(output, tmp_o, &ctx->output_list, link) { wl_list_remove(&output->link); if (output->xdg_output) { zxdg_output_v1_destroy(output->xdg_output); } wl_output_destroy(output->output); } struct xdpw_screencast_instance *cast, *tmp_c; wl_list_for_each_safe(cast, tmp_c, &ctx->screencast_instances, link) { cast->quit = true; } if (ctx->screencopy_manager) { zwlr_screencopy_manager_v1_destroy(ctx->screencopy_manager); } if (ctx->shm) { wl_shm_destroy(ctx->shm); } if (ctx->gbm) { int fd = gbm_device_get_fd(ctx->gbm); gbm_device_destroy(ctx->gbm); close(fd); } if (ctx->linux_dmabuf_feedback) { zwp_linux_dmabuf_feedback_v1_destroy(ctx->linux_dmabuf_feedback); } if (ctx->linux_dmabuf) { zwp_linux_dmabuf_v1_destroy(ctx->linux_dmabuf); } if (ctx->xdg_output_manager) { zxdg_output_manager_v1_destroy(ctx->xdg_output_manager); } if (ctx->registry) { wl_registry_destroy(ctx->registry); } } xdg-desktop-portal-wlr-0.7.1/src/screenshot/000077500000000000000000000000001455600706000210205ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.7.1/src/screenshot/screenshot.c000066400000000000000000000145161455600706000233500ustar00rootroot00000000000000#include #include #include #include #include #include #include "xdpw.h" #include "screenshot.h" static const char object_path[] = "/org/freedesktop/portal/desktop"; static const char interface_name[] = "org.freedesktop.impl.portal.Screenshot"; static bool exec_screenshooter(const char *path) { pid_t pid = fork(); if (pid < 0) { perror("fork"); return false; } else if (pid == 0) { char *const argv[] = { "grim", "--", (char *)path, NULL, }; execvp("grim", argv); perror("execvp"); exit(127); } int stat; if (waitpid(pid, &stat, 0) < 0) { perror("waitpid"); return false; } return stat == 0; } static bool exec_screenshooter_interactive(const char *path) { pid_t pid = fork(); if (pid < 0) { perror("fork"); return false; } else if (pid == 0) { char cmd[strlen(path) + 25]; snprintf(cmd, sizeof(cmd), "grim -g \"$(slurp)\" -- %s", path); execl("/bin/sh", "/bin/sh", "-c", cmd, NULL); perror("execl"); exit(127); } int stat; if (waitpid(pid, &stat, 0) < 0) { perror("waitpid"); return false; } return stat == 0; } static int method_screenshot(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { int ret = 0; bool interactive = false; char *handle, *app_id, *parent_window; ret = sd_bus_message_read(msg, "oss", &handle, &app_id, &parent_window); if (ret < 0) { return ret; } ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); if (ret < 0) { return ret; } char *key; int inner_ret = 0; while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { inner_ret = sd_bus_message_read(msg, "s", &key); if (inner_ret < 0) { return inner_ret; } if (strcmp(key, "interactive") == 0) { int mode; sd_bus_message_read(msg, "v", "b", &mode); logprint(DEBUG, "dbus: option interactive: %d", mode); interactive = mode; } else if (strcmp(key, "modal") == 0) { int modal; sd_bus_message_read(msg, "v", "b", &modal); logprint(DEBUG, "dbus: option modal: %d", modal); } else { logprint(WARN, "dbus: unknown option %s", key); sd_bus_message_skip(msg, "v"); } inner_ret = sd_bus_message_exit_container(msg); if (inner_ret < 0) { return inner_ret; } } if (ret < 0) { return ret; } ret = sd_bus_message_exit_container(msg); if (ret < 0) { return ret; } // TODO: cleanup this struct xdpw_request *req = xdpw_request_create(sd_bus_message_get_bus(msg), handle); if (req == NULL) { return -ENOMEM; } // TODO: choose a better path const char path[] = "/tmp/out.png"; if (interactive && !exec_screenshooter_interactive(path)) { return -1; } if (!interactive && !exec_screenshooter(path)) { return -1; } const char uri_prefix[] = "file://"; char uri[strlen(path) + strlen(uri_prefix) + 1]; snprintf(uri, sizeof(uri), "%s%s", uri_prefix, path); sd_bus_message *reply = NULL; ret = sd_bus_message_new_method_return(msg, &reply); if (ret < 0) { return ret; } ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_SUCCESS, 1, "uri", "s", uri); if (ret < 0) { return ret; } ret = sd_bus_send(NULL, reply, NULL); if (ret < 0) { return ret; } sd_bus_message_unref(reply); return 0; } static bool spawn_chooser(int chooser_out[2]) { pid_t pid = fork(); if (pid < 0) { perror("fork"); return false; } else if (pid == 0) { close(chooser_out[0]); dup2(chooser_out[1], STDOUT_FILENO); close(chooser_out[1]); execl("/bin/sh", "/bin/sh", "-c", "grim -g \"$(slurp -p)\" -t ppm -", NULL); perror("execl"); _exit(127); } int stat; if (waitpid(pid, &stat, 0) < 0) { perror("waitpid"); return false; } close(chooser_out[1]); return stat == 0; } static bool exec_color_picker(struct xdpw_ppm_pixel *pixel) { int chooser_out[2]; if (pipe(chooser_out) == -1) { perror("pipe chooser_out"); return false; } if (!spawn_chooser(chooser_out)) { logprint(ERROR, "Selection failed"); close(chooser_out[0]); return false; } FILE *f = fdopen(chooser_out[0], "r"); if (f == NULL) { perror("fopen pipe chooser_out"); close(chooser_out[0]); return false; } char *format = NULL; size_t len = 1; if (getline(&format, &len, f) < 0) { goto error_img; } if (strcmp(format, "P6\n") != 0) { goto error_img; } if (getline(&format, &len, f) < 0) { goto error_img; } if (strcmp(format, "1 1\n") != 0) { goto error_img; } if (fscanf(f, "%d\n", &pixel->max_color_value) != 1) { goto error_img; } unsigned char pixels[3]; if (fread(pixels, 3, 1, f) != 1) { goto error_img; } pixel->red = pixels[0]; pixel->green = pixels[1]; pixel->blue = pixels[2]; free(format); fclose(f); return true; error_img: logprint(WARN, "Invalid image format or size"); free(format); fclose(f); return false; } static int method_pick_color(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { int ret = 0; char *handle, *app_id, *parent_window; ret = sd_bus_message_read(msg, "oss", &handle, &app_id, &parent_window); if (ret < 0) { return ret; } struct xdpw_request *req = xdpw_request_create(sd_bus_message_get_bus(msg), handle); if (req == NULL) { return -ENOMEM; } struct xdpw_ppm_pixel pixel = {0}; if (!exec_color_picker(&pixel)) { return -1; } double red = pixel.red / (pixel.max_color_value * 1.0); double green = pixel.green / (pixel.max_color_value * 1.0); double blue = pixel.blue / (pixel.max_color_value * 1.0); sd_bus_message *reply = NULL; ret = sd_bus_message_new_method_return(msg, &reply); if (ret < 0) { return ret; } ret = sd_bus_message_append(reply, "ua{sv}", PORTAL_RESPONSE_SUCCESS, 1, "color", "(ddd)", red, green, blue); if (ret < 0) { return ret; } ret = sd_bus_send(NULL, reply, NULL); if (ret < 0) { return ret; } sd_bus_message_unref(reply); return 0; } static const sd_bus_vtable screenshot_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD("Screenshot", "ossa{sv}", "ua{sv}", method_screenshot, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("PickColor", "ossa{sv}", "ua{sv}", method_pick_color, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_PROPERTY("version", "u", NULL, offsetof(struct xdpw_state, screenshot_version), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_VTABLE_END }; int xdpw_screenshot_init(struct xdpw_state *state) { // TODO: cleanup sd_bus_slot *slot = NULL; return sd_bus_add_object_vtable(state->bus, &slot, object_path, interface_name, screenshot_vtable, state); } xdg-desktop-portal-wlr-0.7.1/wlr.portal000066400000000000000000000003051455600706000201010ustar00rootroot00000000000000[portal] DBusName=org.freedesktop.impl.portal.desktop.wlr Interfaces=org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.ScreenCast; UseIn=wlroots;sway;Wayfire;river;phosh;Hyprland; xdg-desktop-portal-wlr-0.7.1/xdg-desktop-portal-wlr.5.scd000066400000000000000000000066351455600706000232560ustar00rootroot00000000000000xdg-desktop-portal-wlr(5) # NAME xdg-desktop-portal-wlr - an xdg-desktop-portal backend for wlroots # DESCRIPTION xdg-desktop-portal-wlr (or xdpw for short) allows applications to request screenshots and screencasts via xdg-desktop-portal in wlroots-based Wayland compositors. xdpw will try to load the configuration file from these locations: - $XDG_CONFIG_HOME/xdg-desktop-portal-wlr/$XDG_CURRENT_DESKTOP - $XDG_CONFIG_HOME/xdg-desktop-portal-wlr/config - /etc/xdg/xdg-desktop-portal-wlr/$XDG_CURRENT_DESKTOP - /etc/xdg/xdg-desktop-portal-wlr/config _$XDG_CONFIG_HOME_ defaults to _~/.config_. _$XDG_CURRENT_DESKTOP_ can be a colon separated list. Each element of that list will be tried. The configuration files use the INI file format. Example: ``` [screencast] output_name=HDMI-A-1 max_fps=30 exec_before=disable_notifications.sh exec_after=enable_notifications.sh chooser_type=simple chooser_cmd=slurp -f %o -or ``` # SCREENCAST OPTIONS These options need to be placed under the **[screencast]** section. **output_name** = _name_ Select which output will be screencast. This option is used with **chooser_type** = none. The list of available outputs can be obtained via **wayland-info**(1) (under the _wl_output_ section). **max_fps** = _limit_ Limit the number of frames per second to the provided rate. This is useful to reduce CPU usage when capturing frames at the output's refresh rate is unnecessary. **exec_before** = _command_ Execute _command_ before starting a screencast. The command will be executed within sh. **exec_after** = _command_ Execute _command_ after ending all screencasts. The command will be executed within sh. **chooser_cmd** = _command_ Run this command to select an output. For more details see **OUTPUT CHOOSER**. **chooser_type** = _type_ Specifies the input send to the chooser. The supported types are: - default: xdpw will try to use the first chooser found in the list of hardcoded choosers (slurp, wofi, bemenu) and will fallback to an arbitrary output if none of those were found. - none: xdpw will allow screencast either on the output given by **output_name**, or if empty an arbitrary output without further interaction. - simple, dmenu: xdpw will launch the chooser given by **chooser_cmd**. For more details see **OUTPUT CHOOSER**. **force_mod_linear** = _bool_ Force buffers with implicit modifiers to be linear (experimental) Setting this option to 1 will force xdpw to allocate dma-bufs with implicit modifier as linear. This option shouldn't be required on single gpu setups, but can increase compatibility especially on setups with multiple gpus. This option is experimental and can be removed or replaced in future versions. ## OUTPUT CHOOSER The chooser can be any program or script with the following behaviour: - It returns any error code except 127. The error code 127 is internally used to signal that no command could be found and all output from it will be ignored. - It returns the name of a valid output on stdout as given by **wayland-info**(1). Everything else will be handled as declined by the user. - To signal that the user has declined screencast, the chooser should exit without anything on stdout. Supported types of choosers via the **chooser_type** option: - simple: the chooser is just called without anything further on stdin. - dmenu: the chooser receives a newline separated list (dmenu style) of outputs on stdin. # SEE ALSO **pipewire**(1)