Add support for image capture protocols
53030e410607a8ff7768964aa6ac91a8afbef796
1 parent
f1699f66
README
+4 -2
| 16 | 16 | ||
| 17 | 17 | swm is configured in C and aims to remain small enough to understand, modify, |
|
| 18 | 18 | and extend. It supports native Wayland clients, X11 clients through Xwayland, |
|
| 19 | 19 | multiple outputs, global workspaces, server-side decorations, layer-shell |
|
| 20 | 20 | panels and launchers, session locking, idle inhibition, input methods, and |
|
| 21 | - | the standard wlroots output-management, screencopy, data-control, and |
|
| 22 | - | foreign-toplevel protocols. |
|
| 21 | + | the standard wlroots output-management, screencopy, data-control, |
|
| 22 | + | foreign-toplevel, and toplevel image-capture protocols. |
|
| 23 | 23 | ||
| 24 | 24 | ||
| 25 | 25 | # REQUIREMENTS |
|
| 26 | 26 | ||
| 27 | 27 | Building swm requires: |
| 109 | 109 | ||
| 110 | 110 | On each state change swm also writes line-oriented status records to standard |
|
| 111 | 111 | output. When `-s` is used, that output is connected to the startup command's |
|
| 112 | 112 | standard input. The active window title is published atomically to |
|
| 113 | 113 | `$XDG_RUNTIME_DIR/swm-title`, which can be consumed by a custom bar module. |
|
| 114 | + | Visible window rectangles are published atomically to |
|
| 115 | + | `$XDG_RUNTIME_DIR/swm-windows` in the format accepted by `slurp -r`. |
|
| 114 | 116 | ||
| 115 | 117 | ||
| 116 | 118 | # TESTING AND DEVELOPMENT |
|
| 117 | 119 | ||
| 118 | 120 | Run the complete test suite with: |
swm.c
+58 -1
| 29 | 29 | #include <wlr/types/wlr_data_device.h> |
|
| 30 | 30 | #include <wlr/types/wlr_drm.h> |
|
| 31 | 31 | #include <wlr/types/wlr_export_dmabuf_v1.h> |
|
| 32 | 32 | #include <wlr/types/wlr_ext_data_control_v1.h> |
|
| 33 | 33 | #include <wlr/types/wlr_ext_foreign_toplevel_list_v1.h> |
|
| 34 | + | #include <wlr/types/wlr_ext_image_capture_source_v1.h> |
|
| 35 | + | #include <wlr/types/wlr_ext_image_copy_capture_v1.h> |
|
| 34 | 36 | #include <wlr/types/wlr_foreign_toplevel_management_v1.h> |
|
| 35 | 37 | #include <wlr/types/wlr_fractional_scale_v1.h> |
|
| 36 | 38 | #include <wlr/types/wlr_gamma_control_v1.h> |
|
| 37 | 39 | #include <wlr/types/wlr_idle_inhibit_v1.h> |
|
| 38 | 40 | #include <wlr/types/wlr_idle_notify_v1.h> |
| 459 | 461 | static void focus_client(client_t *c, int lift); |
|
| 460 | 462 | static client_t *focus_close(client_t *c); |
|
| 461 | 463 | static void focus_main(const arg_t *arg); |
|
| 462 | 464 | static void focus_urgent(const arg_t *arg); |
|
| 463 | 465 | static void ftl_activate_notify(struct wl_listener *listener, void *data); |
|
| 466 | + | static void ftl_capture_request_notify(struct wl_listener *listener, void *data); |
|
| 464 | 467 | static void ftl_close_notify(struct wl_listener *listener, void *data); |
|
| 465 | 468 | static void ftl_fullscreen_notify(struct wl_listener *listener, void *data); |
|
| 466 | 469 | static void ftl_sync(client_t *c); |
|
| 467 | 470 | static void input_method_commit_notify(struct wl_listener *listener, void *data); |
|
| 468 | 471 | static void input_method_create_notify(struct wl_listener *listener, void *data); |
| 525 | 528 | static void output_manager_test(struct wl_listener *listener, void *data); |
|
| 526 | 529 | static void pointer_focus( |
|
| 527 | 530 | client_t *c, struct wlr_surface *surface, double sx, double sy, uint32_t time, bool refocus |
|
| 528 | 531 | ); |
|
| 529 | 532 | static void print_status(void); |
|
| 533 | + | static void publish_windows(const char *runtime); |
|
| 530 | 534 | static void prepare_child(void); |
|
| 531 | 535 | static void power_manager_set_mode(struct wl_listener *listener, void *data); |
|
| 532 | 536 | static void quit(const arg_t *arg); |
|
| 533 | 537 | static void raise_client(const arg_t *arg); |
|
| 534 | 538 | static void render_monitor(struct wl_listener *listener, void *data); |
| 637 | 641 | static struct wlr_input_method_v2 *input_method; /* at most one */ |
|
| 638 | 642 | static struct wl_listener im_commit = { .notify = input_method_commit_notify }; |
|
| 639 | 643 | static struct wl_listener im_destroy = { .notify = input_method_destroy_notify }; |
|
| 640 | 644 | static struct wl_listener im_grab_kb = { .notify = input_method_grab_keyboard }; |
|
| 641 | 645 | static struct wl_listener im_new_popup = { .notify = input_method_new_popup }; |
|
| 642 | - | static struct wlr_ext_foreign_toplevel_list_v1 *ext_ftl_list; |
|
| 646 | + | static struct wlr_ext_foreign_toplevel_list_v1 *ext_ftl_list; |
|
| 647 | + | static struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 *ext_ftl_capture_mgr; |
|
| 648 | + | static struct wl_listener ext_ftl_capture_request = { .notify = ftl_capture_request_notify }; |
|
| 643 | 649 | ||
| 644 | 650 | static struct wlr_pointer_constraints_v1 *pointer_constraints; |
|
| 645 | 651 | static struct wlr_relative_pointer_manager_v1 *relative_pointer_mgr; |
|
| 646 | 652 | static struct wlr_pointer_constraint_v1 *active_constraint; |
|
| 647 | 653 |
| 1980 | 1986 | wl_list_remove(&new_xdg_toplevel.link); |
|
| 1981 | 1987 | wl_list_remove(&new_text_input.link); |
|
| 1982 | 1988 | wl_list_remove(&new_xdg_decoration.link); |
|
| 1983 | 1989 | wl_list_remove(&new_xdg_popup.link); |
|
| 1984 | 1990 | wl_list_remove(&new_layer_surface.link); |
|
| 1991 | + | wl_list_remove(&ext_ftl_capture_request.link); |
|
| 1985 | 1992 | wl_list_remove(&output_mgr_apply.link); |
|
| 1986 | 1993 | wl_list_remove(&output_mgr_test.link); |
|
| 1987 | 1994 | wl_list_remove(&output_power_mgr_set_mode.link); |
|
| 1988 | 1995 | wl_list_remove(&request_activate.link); |
|
| 1989 | 1996 | wl_list_remove(&request_cursor.link); |
| 2988 | 2995 | if (c->ws && c->ws->mon != selmon) |
|
| 2989 | 2996 | view_workspace(c->ws, selmon); |
|
| 2990 | 2997 | focus_client(c, 1); |
|
| 2991 | 2998 | } |
|
| 2992 | 2999 | ||
| 3000 | + | /* Create a scene-backed capture source for a published window. */ |
|
| 3001 | + | void ftl_capture_request_notify(struct wl_listener *listener, void *data) { |
|
| 3002 | + | struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request *request = data; |
|
| 3003 | + | struct wlr_ext_image_capture_source_v1 *source; |
|
| 3004 | + | client_t *c = request->toplevel_handle->data; |
|
| 3005 | + | ||
| 3006 | + | if (!c || !c->scene || !client_surface(c)->mapped) |
|
| 3007 | + | return; |
|
| 3008 | + | source = wlr_ext_image_capture_source_v1_create_with_scene_node( |
|
| 3009 | + | &c->scene->node, event_loop, alloc, drw |
|
| 3010 | + | ); |
|
| 3011 | + | ||
| 3012 | + | if (source) |
|
| 3013 | + | wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_accept(request, source); |
|
| 3014 | + | } |
|
| 3015 | + | ||
| 2993 | 3016 | /* Forward a taskbar's close request to the selected window. */ |
|
| 2994 | 3017 | void ftl_close_notify(struct wl_listener *listener, void *data) { |
|
| 2995 | 3018 | client_t *c = wl_container_of(listener, c, ftl_close); |
|
| 2996 | 3019 | ||
| 2997 | 3020 | client_send_close(c); |
| 3839 | 3862 | &(struct wlr_ext_foreign_toplevel_handle_v1_state){ |
|
| 3840 | 3863 | .app_id = client_get_appid(c), |
|
| 3841 | 3864 | .title = client_get_title(c), |
|
| 3842 | 3865 | } |
|
| 3843 | 3866 | ); |
|
| 3867 | + | if (c->extftl) |
|
| 3868 | + | c->extftl->data = c; |
|
| 3844 | 3869 | ftl_sync(c); |
|
| 3845 | 3870 | /* A managed window can receive keyboard focus only after it is visible. */ |
|
| 3846 | 3871 | if (c->ws && c->ws->mon) |
|
| 3847 | 3872 | focus_client(c, 1); |
|
| 3848 | 3873 | c->pending_map = 0; |
| 4197 | 4222 | /* Replace line-breaking characters before writing a status field. */ |
|
| 4198 | 4223 | static void status_field(char *dst, size_t size, const char *src) { |
|
| 4199 | 4224 | swm_sanitize_field(dst, size, src, nullptr); |
|
| 4200 | 4225 | } |
|
| 4201 | 4226 | ||
| 4227 | + | /* Publish visible window rectangles in slurp's predefined-region format. */ |
|
| 4228 | + | void publish_windows(const char *runtime) { |
|
| 4229 | + | client_t *c; |
|
| 4230 | + | FILE *file; |
|
| 4231 | + | char path[MAX_STATE_PATH], tmppath[MAX_STATE_PATH], title[MAX_WINDOW_STATE_FIELD]; |
|
| 4232 | + | int ok = 1; |
|
| 4233 | + | ||
| 4234 | + | if (!runtime || snprintf(path, sizeof(path), "%s/swm-windows", runtime) >= (int)sizeof(path) || |
|
| 4235 | + | snprintf(tmppath, sizeof(tmppath), "%s.tmp.%ld", path, (long)getpid()) >= |
|
| 4236 | + | (int)sizeof(tmppath) || |
|
| 4237 | + | !(file = fopen(tmppath, "w"))) |
|
| 4238 | + | return; |
|
| 4239 | + | ||
| 4240 | + | wl_list_for_each(c, &clients, link) { |
|
| 4241 | + | if (!c->mon || !client_surface(c)->mapped || !c->scene->node.enabled) |
|
| 4242 | + | continue; |
|
| 4243 | + | status_field(title, sizeof(title), client_get_title(c)); |
|
| 4244 | + | if (fprintf( |
|
| 4245 | + | file, "%d,%d %dx%d %s\n", c->geom.x, c->geom.y, c->geom.width, c->geom.height, title |
|
| 4246 | + | ) < 0) |
|
| 4247 | + | ok = 0; |
|
| 4248 | + | } |
|
| 4249 | + | if (fflush(file) < 0 || fclose(file) < 0) |
|
| 4250 | + | ok = 0; |
|
| 4251 | + | if (!ok || rename(tmppath, path) < 0) |
|
| 4252 | + | unlink(tmppath); |
|
| 4253 | + | } |
|
| 4254 | + | ||
| 4202 | 4255 | /* Publish current display, workspace, and focus state on standard output. */ |
|
| 4203 | 4256 | void print_status(void) { |
|
| 4204 | 4257 | monitor_t *m = nullptr; |
|
| 4205 | 4258 | client_t *c; |
|
| 4206 | 4259 | FILE *titlefile; |
| 4243 | 4296 | m->ws ? 1u << m->ws->idx : 0, |
|
| 4244 | 4297 | urg |
|
| 4245 | 4298 | ); |
|
| 4246 | 4299 | } |
|
| 4247 | 4300 | runtime = getenv("XDG_RUNTIME_DIR"); |
|
| 4301 | + | publish_windows(runtime); |
|
| 4248 | 4302 | ||
| 4249 | 4303 | if (runtime && selmon) { |
|
| 4250 | 4304 | src = (c = focus_top(selmon)) ? client_get_title(c) : ""; |
|
| 4251 | 4305 | status_field(title, sizeof(title), src); |
|
| 4252 | 4306 | snprintf(titlepath, sizeof(titlepath), "%s/swm-title", runtime); |
| 5187 | 5241 | wl_global_create(dpy, &ext_workspace_manager_v1_interface, 1, nullptr, workspace_manager_bind); |
|
| 5188 | 5242 | ||
| 5189 | 5243 | /* Publish window lists for taskbars and switchers. */ |
|
| 5190 | 5244 | ftl_mgr = wlr_foreign_toplevel_manager_v1_create(dpy); |
|
| 5191 | 5245 | ext_ftl_list = wlr_ext_foreign_toplevel_list_v1_create(dpy, 1); |
|
| 5246 | + | wlr_ext_image_copy_capture_manager_v1_create(dpy, 1); |
|
| 5247 | + | ext_ftl_capture_mgr = wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create(dpy, 1); |
|
| 5248 | + | wl_signal_add(&ext_ftl_capture_mgr->events.new_request, &ext_ftl_capture_request); |
|
| 5192 | 5249 | ||
| 5193 | 5250 | /* Let virtual machines and remote desktops capture window-manager shortcuts. */ |
|
| 5194 | 5251 | kb_inhibit_mgr = wlr_keyboard_shortcuts_inhibit_v1_create(dpy); |
|
| 5195 | 5252 | wl_signal_add(&kb_inhibit_mgr->events.new_inhibitor, &new_shortcuts_inhibitor_listener); |
|
| 5196 | 5253 |