Add support for image capture protocols

53030e410607a8ff7768964aa6ac91a8afbef796
Alexis Sellier committed ago 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