pax_global_header00006660000000000000000000000064141412463500014512gustar00rootroot0000000000000052 comment=7c0f35256023a8fbb18e0ce079f0c3bf7df9470b xdg-desktop-portal-wlr-0.5.0/000077500000000000000000000000001414124635000160465ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.5.0/.builds/000077500000000000000000000000001414124635000174065ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.5.0/.builds/alpine.yml000066400000000000000000000006371414124635000214070ustar00rootroot00000000000000image: alpine/edge packages: - elogind-dev - gcc - meson - pipewire-dev - wayland-dev - wayland-protocols - inih-dev - scdoc 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.5.0/.builds/archlinux.yml000066400000000000000000000010641414124635000221270ustar00rootroot00000000000000image: archlinux packages: - gcc - clang - meson - wayland - wayland-protocols - pipewire - libinih - scdoc 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.5.0/.builds/freebsd.yml000066400000000000000000000006371414124635000215510ustar00rootroot00000000000000image: freebsd/latest packages: - basu - libepoll-shim - meson - pipewire - pkgconf - wayland - wayland-protocols - inih - scdoc 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.5.0/.editorconfig000066400000000000000000000003051414124635000205210ustar00rootroot00000000000000root = 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.5.0/.gitignore000066400000000000000000000007161414124635000200420ustar00rootroot00000000000000# 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.5.0/CONTRIBUTING.md000066400000000000000000000022611414124635000203000ustar00rootroot00000000000000# 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.5.0/LICENSE000066400000000000000000000020511414124635000170510ustar00rootroot00000000000000MIT 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.5.0/README.md000066400000000000000000000040151414124635000173250ustar00rootroot00000000000000# xdg-desktop-portal-wlr [![builds.sr.ht status](https://builds.sr.ht/~emersion/xdg-desktop-portal-wlr/commits.svg)](https://builds.sr.ht/~emersion/xdg-desktop-portal-wlr/commits?) [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.5.0/contrib/000077500000000000000000000000001414124635000175065ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.5.0/contrib/config.sample000066400000000000000000000001251414124635000221540ustar00rootroot00000000000000[screencast] output_name= max_fps=30 chooser_cmd=slurp -f %o -or chooser_type=simple xdg-desktop-portal-wlr-0.5.0/contrib/systemd/000077500000000000000000000000001414124635000211765ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.5.0/contrib/systemd/xdg-desktop-portal-wlr.service.in000066400000000000000000000004451414124635000275220ustar00rootroot00000000000000[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.5.0/include/000077500000000000000000000000001414124635000174715ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.5.0/include/config.h000066400000000000000000000010101414124635000210770ustar00rootroot00000000000000#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; }; 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.5.0/include/fps_limit.h000066400000000000000000000005611414124635000216320ustar00rootroot00000000000000#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.5.0/include/logger.h000066400000000000000000000005601414124635000211220ustar00rootroot00000000000000#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.5.0/include/pipewire_screencast.h000066400000000000000000000007311414124635000237010ustar00rootroot00000000000000#ifndef PIPEWIRE_SCREENCAST_H #define PIPEWIRE_SCREENCAST_H #include "screencast_common.h" #define XDPW_PWR_BUFFERS 1 #define XDPW_PWR_ALIGN 16 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.5.0/include/screencast.h000066400000000000000000000002411414124635000217710ustar00rootroot00000000000000#ifndef SCREENCAST_H #define SCREENCAST_H #include "screencast_common.h" void xdpw_screencast_instance_destroy(struct xdpw_screencast_instance *cast); #endif xdg-desktop-portal-wlr-0.5.0/include/screencast_common.h000066400000000000000000000052131414124635000233450ustar00rootroot00000000000000#ifndef SCREENCAST_COMMON_H #define SCREENCAST_COMMON_H #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 2 enum cursor_modes { HIDDEN = 1, EMBEDDED = 2, METADATA = 4, }; enum source_types { MONITOR = 1, WINDOW = 2, }; enum xdpw_chooser_types { XDPW_CHOOSER_DEFAULT, XDPW_CHOOSER_NONE, XDPW_CHOOSER_SIMPLE, XDPW_CHOOSER_DMENU, }; 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 { uint32_t width; uint32_t height; uint32_t size; uint32_t stride; bool y_invert; uint64_t tv_sec; uint32_t tv_nsec; enum wl_shm_format format; struct xdpw_frame_damage damage; struct wl_buffer *buffer; void *data; }; 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 zxdg_output_manager_v1 *xdg_output_manager; struct wl_shm *shm; // sessions struct wl_list screencast_instances; }; struct xdpw_screencast_instance { // list struct wl_list link; // xdpw uint32_t refcount; struct xdpw_screencast_context *ctx; bool initialized; // pipewire struct spa_source *event; 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_wlr_output *target_output; uint32_t max_framerate; struct zwlr_screencopy_frame_v1 *wlr_frame; struct xdpw_frame simple_frame; bool with_cursor; int err; bool quit; // fps limit struct fps_limit_state fps_limit; }; 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 width; int height; float framerate; }; void randname(char *buf); int anonymous_shm_open(void); enum spa_video_format xdpw_format_pw_from_wl_shm(enum wl_shm_format 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); #endif /* SCREENCAST_COMMON_H */ xdg-desktop-portal-wlr-0.5.0/include/screenshot.h000066400000000000000000000002051414124635000220140ustar00rootroot00000000000000#ifndef SCREENSHOT_H #define SCREENSHOT_H struct xdpw_ppm_pixel { int max_color_value; unsigned char red, green, blue; }; #endifxdg-desktop-portal-wlr-0.5.0/include/timespec_util.h000066400000000000000000000006111414124635000225060ustar00rootroot00000000000000#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.5.0/include/wlr_screencast.h000066400000000000000000000016161414124635000226640ustar00rootroot00000000000000#ifndef WLR_SCREENCAST_H #define WLR_SCREENCAST_H #include "screencast_common.h" #define WL_OUTPUT_VERSION 1 #define SC_MANAGER_VERSION 3 #define SC_MANAGER_VERSION_MIN 2 #define WL_SHM_VERSION 1 #define XDG_OUTPUT_MANAGER_VERSION 3 struct xdpw_state; int xdpw_wlr_screencopy_init(struct xdpw_state *state); void xdpw_wlr_screencopy_finish(struct xdpw_screencast_context *ctx); struct xdpw_wlr_output *xdpw_wlr_output_find_by_name(struct wl_list *output_list, const char *name); struct xdpw_wlr_output *xdpw_wlr_output_first(struct wl_list *output_list); struct xdpw_wlr_output *xdpw_wlr_output_find(struct xdpw_screencast_context *ctx, struct wl_output *out, uint32_t id); struct xdpw_wlr_output *xdpw_wlr_output_chooser(struct xdpw_screencast_context *ctx); void xdpw_wlr_frame_free(struct xdpw_screencast_instance *cast); void xdpw_wlr_register_cb(struct xdpw_screencast_instance *cast); #endif xdg-desktop-portal-wlr-0.5.0/include/xdpw.h000066400000000000000000000034151414124635000206270ustar00rootroot00000000000000#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 "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; 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_instance *screencast_instance; }; 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.5.0/meson.build000066400000000000000000000076621414124635000202230ustar00rootroot00000000000000project( 'xdg-desktop-portal-wlr', 'c', version: '0.5.0', license: 'MIT', meson_version: '>=0.50.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(join_paths(prefix, sysconfdir)), language : 'c') inc = include_directories('include') rt = cc.find_library('rt') pipewire = dependency('libpipewire-0.3', version: '>= 0.3.2') wayland_client = dependency('wayland-client') wayland_protos = dependency('wayland-protocols', version: '>=1.14') iniparser = dependency('inih') epoll = dependency('', required: false) if (not cc.has_function('timerfd_create', prefix: '#include ') or not cc.has_function('signalfd', 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, epoll, ], include_directories: [inc], install: true, install_dir: get_option('libexecdir'), ) conf_data = configuration_data() conf_data.set('libexecdir', join_paths(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_pkgconfig_variable('systemduserunitdir', define_variable: ['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: join_paths(get_option('datadir'), 'dbus-1', 'services'), ) install_data( 'wlr.portal', install_dir: join_paths(get_option('datadir'), 'xdg-desktop-portal', 'portals'), ) scdoc = dependency('scdoc', required: get_option('man-pages'), version: '>= 1.9.7') 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_pkgconfig_variable('scdoc'), output) ], install: true, install_dir: join_paths(get_option('mandir'), 'man' + section), ) endforeach endif xdg-desktop-portal-wlr-0.5.0/meson_options.txt000066400000000000000000000005451414124635000215070ustar00rootroot00000000000000option('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.5.0/org.freedesktop.impl.portal.desktop.wlr.service.in000066400000000000000000000001701414124635000276470ustar00rootroot00000000000000[D-BUS Service] Name=org.freedesktop.impl.portal.desktop.wlr Exec=@libexecdir@/xdg-desktop-portal-wlr @systemd_service@ xdg-desktop-portal-wlr-0.5.0/protocols/000077500000000000000000000000001414124635000200725ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.5.0/protocols/meson.build000066400000000000000000000015511414124635000222360ustar00rootroot00000000000000wayland_scanner_dep = dependency('wayland-scanner', required: false, native: true) if wayland_scanner_dep.found() wayland_scanner = find_program( wayland_scanner_dep.get_pkgconfig_variable('wayland_scanner'), native: true, ) else wayland_scanner = find_program('wayland-scanner', native: true) endif client_protocols = [ 'wlr-screencopy-unstable-v1.xml', 'xdg-output-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.5.0/protocols/wlr-screencopy-unstable-v1.xml000066400000000000000000000226051414124635000257340ustar00rootroot00000000000000 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.5.0/protocols/xdg-output-unstable-v1.xml000066400000000000000000000221411414124635000250730ustar00rootroot00000000000000 Copyright © 2017 Red Hat Inc. 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 aims at describing outputs in a way which is more in line with the concept of an output on desktop oriented systems. Some information are more specific to the concept of an output for a desktop oriented system and may not make sense in other applications, such as IVI systems for example. Typically, the global compositor space on a desktop system is made of a contiguous or overlapping set of rectangular regions. Some of the information provided in this protocol might be identical to their counterparts already available from wl_output, in which case the information provided by this protocol should be preferred to their equivalent in wl_output. The goal is to move the desktop specific concepts (such as output location within the global compositor space, the connector name and types, etc.) out of the core wl_output protocol. 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. A global factory interface for xdg_output objects. Using this request a client can tell the server that it is not going to use the xdg_output_manager object anymore. Any objects already created through this instance are not affected. This creates a new xdg_output object for the given wl_output. An xdg_output describes part of the compositor geometry. This typically corresponds to a monitor that displays part of the compositor space. For objects version 3 onwards, after all xdg_output properties have been sent (when the object is created and when properties are updated), a wl_output.done event is sent. This allows changes to the output properties to be seen as atomic, even if they happen via multiple events. Using this request a client can tell the server that it is not going to use the xdg_output object anymore. The position event describes the location of the wl_output within the global compositor space. The logical_position event is sent after creating an xdg_output (see xdg_output_manager.get_xdg_output) and whenever the location of the output changes within the global compositor space. The logical_size event describes the size of the output in the global compositor space. For example, a surface without any buffer scale, transformation nor rotation set, with the size matching the logical_size will have the same size as the corresponding output when displayed. Most regular Wayland clients should not pay attention to the logical size and would rather rely on xdg_shell interfaces. Some clients such as Xwayland, however, need this to configure their surfaces in the global compositor space as the compositor may apply a different scale from what is advertised by the output scaling property (to achieve fractional scaling, for example). For example, for a wl_output mode 3840×2160 and a scale factor 2: - A compositor not scaling the surface buffers will advertise a logical size of 3840×2160, - A compositor automatically scaling the surface buffers will advertise a logical size of 1920×1080, - A compositor using a fractional scale of 1.5 will advertise a logical size to 2560×1620. For example, for a wl_output mode 1920×1080 and a 90 degree rotation, the compositor will advertise a logical size of 1080x1920. The logical_size event is sent after creating an xdg_output (see xdg_output_manager.get_xdg_output) and whenever the logical size of the output changes, either as a result of a change in the applied scale or because of a change in the corresponding output mode(see wl_output.mode) or transform (see wl_output.transform). This event is sent after all other properties of an xdg_output have been sent. This allows changes to the xdg_output properties to be seen as atomic, even if they happen via multiple events. For objects version 3 onwards, this event is deprecated. Compositors are not required to send it anymore and must send wl_output.done instead. Many compositors will assign names to their outputs, show them to the user, allow them to be configured by name, etc. The client may wish to know this name as well to offer the user similar behaviors. The naming convention is compositor defined, but limited to alphanumeric characters and dashes (-). Each name is unique among all wl_output globals, but if a wl_output global is destroyed the same name may be reused later. The names will also remain consistent across sessions with the same hardware and software configuration. Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do not assume that the name is a reflection of an underlying DRM connector, X11 connection, etc. The name event is sent after creating an xdg_output (see xdg_output_manager.get_xdg_output). This event is only sent once per xdg_output, and the name does not change over the lifetime of the wl_output global. Many compositors can produce human-readable descriptions of their outputs. The client may wish to know this description as well, to communicate the user for various purposes. The description is a UTF-8 string with no convention defined for its contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11 output via :1'. The description event is sent after creating an xdg_output (see xdg_output_manager.get_xdg_output) and whenever the description changes. The description is optional, and may not be sent at all. For objects of version 2 and lower, this event is only sent once per xdg_output, and the description does not change over the lifetime of the wl_output global. xdg-desktop-portal-wlr-0.5.0/src/000077500000000000000000000000001414124635000166355ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.5.0/src/core/000077500000000000000000000000001414124635000175655ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.5.0/src/core/config.c000066400000000000000000000130021414124635000211720ustar00rootroot00000000000000#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)); } // 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 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 { 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(ERROR, "config: no config file found"); 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.5.0/src/core/logger.c000066400000000000000000000033751414124635000212200ustar00rootroot00000000000000#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.5.0/src/core/main.c000066400000000000000000000160361414124635000206630ustar00rootroot00000000000000#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, .config = &config, }; wl_list_init(&state.xdpw_sessions); xdpw_screenshot_init(&state); 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] = { .fd = sd_bus_get_fd(state.bus), .events = POLLIN, }, [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) { ret = poll(pollfds, sizeof(pollfds) / sizeof(pollfds[0]), -1); 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; } if (pollfds[EVENT_LOOP_DBUS].revents & POLLIN) { 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.5.0/src/core/request.c000066400000000000000000000024621414124635000214250ustar00rootroot00000000000000#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.5.0/src/core/session.c000066400000000000000000000035411414124635000214170ustar00rootroot00000000000000#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_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) { 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.5.0/src/core/timer.c000066400000000000000000000030451414124635000210530ustar00rootroot00000000000000#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.5.0/src/core/timespec_util.c000066400000000000000000000015011414124635000225740ustar00rootroot00000000000000#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.5.0/src/screencast/000077500000000000000000000000001414124635000207675ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.5.0/src/screencast/fps_limit.c000066400000000000000000000035751414124635000231330ustar00rootroot00000000000000#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.5.0/src/screencast/pipewire_screencast.c000066400000000000000000000242221414124635000251730ustar00rootroot00000000000000#include "pipewire_screencast.h" #include #include #include #include #include #include #include #include "wlr_screencast.h" #include "xdpw.h" #include "logger.h" static void writeFrameData(void *pwFramePointer, void *wlrFramePointer, uint32_t height, uint32_t stride, bool inverted) { if (!inverted) { memcpy(pwFramePointer, wlrFramePointer, height * stride); return; } for (size_t i = 0; i < (size_t)height; ++i) { void *flippedWlrRowPointer = wlrFramePointer + ((height - i - 1) * stride); void *pwRowPointer = pwFramePointer + (i * stride); memcpy(pwRowPointer, flippedWlrRowPointer, stride); } return; } 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) { 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 (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); } 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 void pwr_on_event(void *data, uint64_t expirations) { struct xdpw_screencast_instance *cast = data; struct pw_buffer *pw_buf; struct spa_buffer *spa_buf; struct spa_meta_header *h; struct spa_data *d; logprint(TRACE, "********************"); logprint(TRACE, "pipewire: event fired"); if ((pw_buf = pw_stream_dequeue_buffer(cast->stream)) == NULL) { logprint(WARN, "pipewire: out of buffers"); goto out; } spa_buf = pw_buf->buffer; d = spa_buf->datas; if ((d[0].data) == NULL) { logprint(TRACE, "pipewire: data pointer undefined"); goto out; } if ((h = spa_buffer_find_meta_data(spa_buf, SPA_META_Header, sizeof(*h)))) { h->pts = -1; h->flags = 0; h->seq = cast->seq++; h->dts_offset = 0; } writeFrameData(d[0].data, cast->simple_frame.data, cast->simple_frame.height, cast->simple_frame.stride, cast->simple_frame.y_invert); logprint(TRACE, "pipewire: pointer %p", d[0].data); logprint(TRACE, "pipewire: size %d", d[0].maxsize); logprint(TRACE, "pipewire: stride %d", d[0].chunk->stride); logprint(TRACE, "pipewire: width %d", cast->simple_frame.width); logprint(TRACE, "pipewire: height %d", cast->simple_frame.height); logprint(TRACE, "pipewire: y_invert %d", cast->simple_frame.y_invert); logprint(TRACE, "********************"); pw_stream_queue_buffer(cast->stream, pw_buf); out: xdpw_wlr_frame_free(cast); } 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; break; 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[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); const struct spa_pod *params[2]; if (!param || id != SPA_PARAM_Format) { return; } 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); params[0] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(XDPW_PWR_BUFFERS, 1, 32), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(cast->simple_frame.size), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(cast->simple_frame.stride), SPA_PARAM_BUFFERS_align, SPA_POD_Int(XDPW_PWR_ALIGN), SPA_PARAM_BUFFERS_dataType,SPA_POD_CHOICE_FLAGS_Int(1<buffer->datas; // Select buffer type from negotiation result if ((d[0].type & (1u << SPA_DATA_MemFd)) > 0) { d[0].type = SPA_DATA_MemFd; } else { logprint(ERROR, "pipewire: unsupported buffer type"); cast->err = 1; return; } logprint(TRACE, "pipewire: selected buffertype %u", d[0].type); // Prepare buffer for choosen type if (d[0].type == SPA_DATA_MemFd) { d[0].maxsize = cast->simple_frame.size; d[0].mapoffset = 0; d[0].chunk->size = cast->simple_frame.size; d[0].chunk->stride = cast->simple_frame.stride; d[0].chunk->offset = 0; d[0].flags = 0; d[0].fd = anonymous_shm_open(); if (d[0].fd == -1) { logprint(ERROR, "pipewire: unable to create anonymous filedescriptor"); cast->err = 1; return; } if (ftruncate(d[0].fd, d[0].maxsize) < 0) { logprint(ERROR, "pipewire: unable to truncate filedescriptor"); close(d[0].fd); d[0].fd = -1; cast->err = 1; return; } // mmap buffer, so we can use the data_ptr in on_process d[0].data = mmap(NULL, d[0].maxsize, PROT_READ | PROT_WRITE, MAP_SHARED, d[0].fd, d[0].mapoffset); if (d[0].data == MAP_FAILED) { logprint(ERROR, "pipewire: unable to mmap memory"); cast->err = 1; return; } } } static void pwr_handle_stream_remove_buffer(void *data, struct pw_buffer *buffer) { logprint(TRACE, "pipewire: remove buffer event handle"); struct spa_data *d = buffer->buffer->datas; switch (d[0].type) { case SPA_DATA_MemFd: munmap(d[0].data, d[0].maxsize); close(d[0].fd); break; default: break; } } 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 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[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); const struct spa_pod *params[1]; enum spa_video_format format = xdpw_format_pw_from_wl_shm(cast->simple_frame.format); params[0] = build_format(&b, format, cast->simple_frame.width, cast->simple_frame.height, cast->framerate); pw_stream_update_params(stream, params, 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[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); 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; /* make an event to signal frame ready */ cast->event = pw_loop_add_event(state->pw_loop, pwr_on_event, cast); logprint(DEBUG, "pipewire: registered event %p", cast->event); enum spa_video_format format = xdpw_format_pw_from_wl_shm(cast->simple_frame.format); const struct spa_pod *param = build_format(&b, format, cast->simple_frame.width, cast->simple_frame.height, cast->framerate); 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), ¶m, 1); } 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; } 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; } } 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.5.0/src/screencast/screencast.c000066400000000000000000000332241414124635000232710ustar00rootroot00000000000000#include "screencast.h" #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_wlr_output *out, bool with_cursor) { // 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_output = out; if (ctx->state->config->screencast_conf.max_fps > 0) { cast->max_framerate = ctx->state->config->screencast_conf.max_fps < (uint32_t)out->framerate ? ctx->state->config->screencast_conf.max_fps : (uint32_t)out->framerate; } else { cast->max_framerate = (uint32_t)out->framerate; } cast->framerate = cast->max_framerate; cast->with_cursor = with_cursor; cast->refcount = 1; cast->node_id = SPA_ID_INVALID; 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); } } wl_list_remove(&cast->link); xdpw_pwr_stream_destroy(cast); free(cast); } bool setup_outputs(struct xdpw_screencast_context *ctx, struct xdpw_session *sess, bool with_cursor) { 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_wlr_output *out; out = xdpw_wlr_output_chooser(ctx); if (!out) { logprint(ERROR, "wlroots: no output found"); return false; } 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->with_cursor ? "with" : "without"); if (cast->target_output->id == out->id && cast->with_cursor == with_cursor) { if (cast->refcount == 0) { logprint(DEBUG, "xdpw: matching cast instance found, " "but is already scheduled for destruction, skipping"); } else { sess->screencast_instance = cast; ++cast->refcount; } logprint(INFO, "xdpw: screencast instance %p now has %d references", cast, cast->refcount); } } if (!sess->screencast_instance) { sess->screencast_instance = calloc(1, sizeof(struct xdpw_screencast_instance)); xdpw_screencast_instance_init(ctx, sess->screencast_instance, out, with_cursor); } logprint(INFO, "wlroots: output: %s", sess->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); 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"); // default to embedded cursor mode if not specified bool cursor_embedded = true; 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; } 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, "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<xdpw_sessions, link) { if (strcmp(sess->session_handle, session_handle) == 0) { logprint(DEBUG, "dbus: select sources: found matching session %s", sess->session_handle); output_selection_canceled = !setup_outputs(ctx, sess, cursor_embedded); } } 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: 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 error: destroying matching session %s", sess->session_handle); 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_instance; } } if (!cast) { return -1; } if (!cast->initialized) { start_screencast(cast); } 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)); } } 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, "ua{sv}", PORTAL_RESPONSE_SUCCESS, 1, "streams", "a(ua{sv})", 1, cast->node_id, 2, "position", "(ii)", 0, 0, "size", "(ii)", cast->simple_frame.width, cast->simple_frame.height); 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.5.0/src/screencast/screencast_common.c000066400000000000000000000051511414124635000246370ustar00rootroot00000000000000#include "screencast_common.h" #include #include #include #include #include #include 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; } } 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; } enum spa_video_format xdpw_format_pw_from_wl_shm(enum wl_shm_format format) { switch (format) { case WL_SHM_FORMAT_ARGB8888: return SPA_VIDEO_FORMAT_BGRA; case WL_SHM_FORMAT_XRGB8888: return SPA_VIDEO_FORMAT_BGRx; case WL_SHM_FORMAT_RGBA8888: return SPA_VIDEO_FORMAT_ABGR; case WL_SHM_FORMAT_RGBX8888: return SPA_VIDEO_FORMAT_xBGR; case WL_SHM_FORMAT_ABGR8888: return SPA_VIDEO_FORMAT_RGBA; case WL_SHM_FORMAT_XBGR8888: return SPA_VIDEO_FORMAT_RGBx; case WL_SHM_FORMAT_BGRA8888: return SPA_VIDEO_FORMAT_ARGB; case WL_SHM_FORMAT_BGRX8888: return SPA_VIDEO_FORMAT_xRGB; case WL_SHM_FORMAT_NV12: return SPA_VIDEO_FORMAT_NV12; default: 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; 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(); } xdg-desktop-portal-wlr-0.5.0/src/screencast/wlr_screencast.c000066400000000000000000000464751414124635000241710ustar00rootroot00000000000000#include "wlr_screencast.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 "screencast.h" #include "pipewire_screencast.h" #include "xdpw.h" #include "logger.h" #include "fps_limit.h" static void wlr_frame_buffer_destroy(struct xdpw_screencast_instance *cast) { // Even though this check may be deemed unnecessary, // this has been found to cause SEGFAULTs, like this one: // https://github.com/emersion/xdg-desktop-portal-wlr/issues/50 if (cast->simple_frame.data != NULL) { munmap(cast->simple_frame.data, cast->simple_frame.size); cast->simple_frame.data = NULL; } if (cast->simple_frame.buffer != NULL) { wl_buffer_destroy(cast->simple_frame.buffer); cast->simple_frame.buffer = NULL; } } void xdpw_wlr_frame_free(struct xdpw_screencast_instance *cast) { zwlr_screencopy_frame_v1_destroy(cast->wlr_frame); cast->wlr_frame = NULL; if (cast->quit || cast->err) { wlr_frame_buffer_destroy(cast); logprint(TRACE, "xdpw: simple_frame buffer destroyed"); } logprint(TRACE, "wlroots: frame destroyed"); 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 ; } 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_register_cb, cast); } else { xdpw_wlr_register_cb(cast); } } static struct wl_buffer *create_shm_buffer(struct xdpw_screencast_instance *cast, enum wl_shm_format fmt, int width, int height, int stride, void **data_out) { struct xdpw_screencast_context *ctx = cast->ctx; int size = stride * height; int fd = anonymous_shm_open(); if (fd < 0) { logprint(ERROR, "wlroots: shm_open failed"); return NULL; } int ret; while ((ret = ftruncate(fd, size)) == EINTR); if (ret < 0) { close(fd); logprint(ERROR, "wlroots: ftruncate failed"); return NULL; } void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { logprint(ERROR, "wlroots: mmap failed: %m"); close(fd); return NULL; } struct wl_shm_pool *pool = wl_shm_create_pool(ctx->shm, fd, size); close(fd); struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, fmt); wl_shm_pool_destroy(pool); *data_out = data; return buffer; } static void wlr_frame_buffer_chparam(struct xdpw_screencast_instance *cast, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { logprint(DEBUG, "wlroots: reset buffer"); cast->simple_frame.width = width; cast->simple_frame.height = height; cast->simple_frame.stride = stride; cast->simple_frame.size = stride * height; cast->simple_frame.format = format; wlr_frame_buffer_destroy(cast); if (cast->pwr_stream_state) { logprint(DEBUG, "wlroots: request pipewire param change"); pwr_update_stream_param(cast); } } static void wlr_frame_linux_dmabuf(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t format, uint32_t width, uint32_t height) { logprint(TRACE, "wlroots: linux_dmabuf event handler"); } static void wlr_frame_buffer_done(void *data, struct zwlr_screencopy_frame_v1 *frame) { struct xdpw_screencast_instance *cast = data; logprint(TRACE, "wlroots: buffer_done event handler"); zwlr_screencopy_frame_v1_copy_with_damage(frame, cast->simple_frame.buffer); logprint(TRACE, "wlroots: frame copied"); fps_limit_measure_start(&cast->fps_limit, cast->framerate); } 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; logprint(TRACE, "wlroots: buffer event handler"); cast->wlr_frame = frame; if (cast->simple_frame.width != width || cast->simple_frame.height != height || cast->simple_frame.stride != stride || cast->simple_frame.format != format) { logprint(TRACE, "wlroots: buffer properties changed"); wlr_frame_buffer_chparam(cast, format, width, height, stride); } if (cast->simple_frame.buffer == NULL) { logprint(DEBUG, "wlroots: create shm buffer"); cast->simple_frame.buffer = create_shm_buffer(cast, format, width, height, stride, &cast->simple_frame.data); } else { logprint(TRACE,"wlroots: shm buffer exists"); } if (cast->simple_frame.buffer == NULL) { logprint(ERROR, "wlroots: failed to create buffer"); abort(); } if (zwlr_screencopy_manager_v1_get_version(cast->ctx->screencopy_manager) < 3) { wlr_frame_buffer_done(cast,frame); } } static void wlr_frame_flags(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t flags) { struct xdpw_screencast_instance *cast = data; logprint(TRACE, "wlroots: flags event handler"); cast->simple_frame.y_invert = flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT; } 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; logprint(TRACE, "wlroots: ready event handler"); cast->simple_frame.tv_sec = ((((uint64_t)tv_sec_hi) << 32) | tv_sec_lo); cast->simple_frame.tv_nsec = tv_nsec; if (!cast->quit && !cast->err && cast->pwr_stream_state) { pw_loop_signal_event(cast->ctx->state->pw_loop, cast->event); return; } xdpw_wlr_frame_free(cast); } static void wlr_frame_failed(void *data, struct zwlr_screencopy_frame_v1 *frame) { struct xdpw_screencast_instance *cast = data; logprint(TRACE, "wlroots: failed event handler"); cast->err = true; xdpw_wlr_frame_free(cast); } 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; logprint(TRACE, "wlroots: damage event handler"); cast->simple_frame.damage.x = x; cast->simple_frame.damage.y = y; cast->simple_frame.damage.width = width; cast->simple_frame.damage.height = height; } 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->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); } 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 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, }; static void wlr_xdg_output_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) { struct xdpw_wlr_output *output = data; output->name = strdup(name); }; static void noop() { // This space intentionally left blank } static const struct zxdg_output_v1_listener wlr_xdg_output_listener = { .logical_position = noop, .logical_size = noop, .done = NULL, /* Deprecated */ .description = noop, .name = wlr_xdg_output_name, }; static void wlr_add_xdg_output_listener(struct xdpw_wlr_output *output, struct zxdg_output_v1 *xdg_output) { output->xdg_output = xdg_output; zxdg_output_v1_add_listener(output->xdg_output, &wlr_xdg_output_listener, output); } static void wlr_init_xdg_output(struct xdpw_screencast_context *ctx, struct xdpw_wlr_output *output) { struct zxdg_output_v1 *xdg_output = zxdg_output_manager_v1_get_xdg_output(ctx->xdg_output_manager, output->output); wlr_add_xdg_output_listener(output, xdg_output); } static void wlr_init_xdg_outputs(struct xdpw_screencast_context *ctx) { struct xdpw_wlr_output *output, *tmp; wl_list_for_each_safe(output, tmp, &ctx->output_list, link) { if (output->xdg_output) { continue; } wlr_init_xdg_output(ctx, output); } } 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); } 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; } 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; } 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; } 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 void wlr_remove_output(struct xdpw_wlr_output *out) { free(out->name); free(out->make); free(out->model); zxdg_output_v1_destroy(out->xdg_output); wl_output_destroy(out->output); wl_list_remove(&out->link); free(out); } 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); wl_output_add_listener(output->output, &wlr_output_listener, output); wl_list_insert(&ctx->output_list, &output->link); if (ctx->xdg_output_manager) { wlr_init_xdg_output(ctx, output); } } 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, zxdg_output_manager_v1_interface.name) == 0) { logprint(DEBUG, "wlroots: |-- registered to interface %s (Version %u)", interface, XDG_OUTPUT_MANAGER_VERSION); ctx->xdg_output_manager = wl_registry_bind(reg, id, &zxdg_output_manager_v1_interface, XDG_OUTPUT_MANAGER_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) { 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); // 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"); // make sure our wlroots supports xdg_output_manager if (!ctx->xdg_output_manager) { logprint(ERROR, "Compositor doesn't support %s!", zxdg_output_manager_v1_interface.name); return -1; } wlr_init_xdg_outputs(ctx); wl_display_roundtrip(state->wl_display); logprint(DEBUG, "wayland: xdg output 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; } return 0; } void xdpw_wlr_screencopy_finish(struct xdpw_screencast_context *ctx) { struct xdpw_wlr_output *output, *tmp_o; wl_list_for_each_safe(output, tmp_o, &ctx->output_list, link) { wl_list_remove(&output->link); 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->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.5.0/src/screenshot/000077500000000000000000000000001414124635000210125ustar00rootroot00000000000000xdg-desktop-portal-wlr-0.5.0/src/screenshot/screenshot.c000066400000000000000000000143221414124635000233350ustar00rootroot00000000000000#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_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, NULL); } xdg-desktop-portal-wlr-0.5.0/wlr.portal000066400000000000000000000002651414124635000201000ustar00rootroot00000000000000[portal] DBusName=org.freedesktop.impl.portal.desktop.wlr Interfaces=org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.ScreenCast; UseIn=wlroots;sway;Wayfire;river xdg-desktop-portal-wlr-0.5.0/xdg-desktop-portal-wlr.5.scd000066400000000000000000000060241414124635000232400ustar00rootroot00000000000000xdg-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 seperated 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 _zxdg_output_manager_v1_ 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**. ## 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)