From c84882c0d00bcf908225058d1549bfed66fb8004 Mon Sep 17 00:00:00 2001 From: columbarius Date: Fri, 2 Apr 2021 13:35:44 +0200 Subject: [PATCH 1/3] screencast: add chooser_type json --- contrib/dmenu-chooser-json.sh | 30 ++++ include/screencast_common.h | 12 ++ include/wlr_screencast.h | 3 +- meson.build | 2 + src/screencast/screencast.c | 28 ++-- src/screencast/screencast_common.c | 4 + src/screencast/wlr_screencast.c | 212 +++++++++++++++++++++++++---- xdg-desktop-portal-wlr.5.scd | 4 +- 8 files changed, 252 insertions(+), 43 deletions(-) create mode 100755 contrib/dmenu-chooser-json.sh diff --git a/contrib/dmenu-chooser-json.sh b/contrib/dmenu-chooser-json.sh new file mode 100755 index 00000000..ab7b2741 --- /dev/null +++ b/contrib/dmenu-chooser-json.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +chooser=$1 + +read -r json +echo "$json" >&2 +output=$(echo $json | jq '.monitors | .[] | (.id|tostring) + ":" + .name + ":" + .make + ":" + .model' | tr -d '"' | ${chooser:="wofi -d"}) +ret=$! +version=$(echo $json | jq '.version|tostring') +revision=$(echo $json | jq '.revision|tostring') +target_type=monitor +IFS=":" read -r id name make model <<<"$output" +echo "version:$version" >&2 +echo "revision:$revision" >&2 +echo "target_type:$target_type" >&2 +echo "name:$name" >&2 +echo "model:$model" >&2 +echo "make:$make" >&2 +echo "id $id" >&2 + +jq -c --null-input \ + --argjson version $version \ + --argjson revision $revision \ + --arg target_type "$target_type" \ + --arg name "$name" \ + --arg model "$model" \ + --arg make "$make" \ + --argjson id $id \ + '{"version": $version, "revision": $revision, "target_type": $target_type, "target": { "name": $name, "model": $model, "make": $make, "id": $id }}' +exit $ret diff --git a/include/screencast_common.h b/include/screencast_common.h index cae85052..d5d0b0d8 100644 --- a/include/screencast_common.h +++ b/include/screencast_common.h @@ -41,6 +41,7 @@ enum xdpw_chooser_types { XDPW_CHOOSER_NONE, XDPW_CHOOSER_SIMPLE, XDPW_CHOOSER_DMENU, + XDPW_CHOOSER_JSON, }; enum xdpw_frame_state { @@ -56,6 +57,17 @@ struct xdpw_output_chooser { char *cmd; }; +struct xdpw_chooser_opts { + struct wl_list *output_list; + + // XDPW_CHOOSER_JSON + uint32_t target_mask; + enum persist_modes persist_mode; + + // XDPW_CHOOSER_NONE + char *outputname; +}; + struct xdpw_frame_damage { uint32_t x; uint32_t y; diff --git a/include/wlr_screencast.h b/include/wlr_screencast.h index ee784139..350512e9 100644 --- a/include/wlr_screencast.h +++ b/include/wlr_screencast.h @@ -18,7 +18,8 @@ struct xdpw_state; int xdpw_wlr_screencopy_init(struct xdpw_state *state); void xdpw_wlr_screencopy_finish(struct xdpw_screencast_context *ctx); -bool xdpw_wlr_target_chooser(struct xdpw_screencast_context *ctx, struct xdpw_screencast_target *target); +bool xdpw_wlr_target_chooser(struct xdpw_output_chooser *chooser, struct xdpw_chooser_opts *opts, + struct xdpw_screencast_target *target); bool xdpw_wlr_target_from_data(struct xdpw_screencast_context *ctx, struct xdpw_screencast_target *target, struct xdpw_screencast_restore_data *data); diff --git a/meson.build b/meson.build index e6e93468..cfcf1d89 100644 --- a/meson.build +++ b/meson.build @@ -29,6 +29,7 @@ wayland_protos = dependency('wayland-protocols', version: '>=1.24') iniparser = dependency('inih') gbm = dependency('gbm') drm = dependency('libdrm') +jsonc = dependency('json-c') epoll = dependency('', required: false) if (not cc.has_function('timerfd_create', prefix: '#include ') or @@ -90,6 +91,7 @@ executable( iniparser, gbm, drm, + jsonc, epoll, ], include_directories: [inc], diff --git a/src/screencast/screencast.c b/src/screencast/screencast.c index 627bb584..38191f7c 100644 --- a/src/screencast/screencast.c +++ b/src/screencast/screencast.c @@ -127,23 +127,17 @@ bool setup_target(struct xdpw_screencast_context *ctx, struct xdpw_session *sess target_initialized = xdpw_wlr_target_from_data(ctx, target, data); } if (!target_initialized) { - target_initialized = xdpw_wlr_target_chooser(ctx, target); - //TODO: Chooser option to confirm the persist mode - const char *env_persist_str = getenv("XDPW_PERSIST_MODE"); - if (env_persist_str) { - if (strcmp(env_persist_str, "transient") == 0) { - sess->screencast_data.persist_mode = sess->screencast_data.persist_mode > PERSIST_TRANSIENT - ? PERSIST_TRANSIENT : sess->screencast_data.persist_mode; - } else if (strcmp(env_persist_str, "permanent") == 0) { - sess->screencast_data.persist_mode = sess->screencast_data.persist_mode > PERSIST_PERMANENT - ? PERSIST_PERMANENT : sess->screencast_data.persist_mode; - } else { - sess->screencast_data.persist_mode = PERSIST_NONE; - } - - } else { - sess->screencast_data.persist_mode = PERSIST_NONE; - } + struct xdpw_output_chooser chooser = { + .cmd = ctx->state->config->screencast_conf.chooser_cmd, + .type = ctx->state->config->screencast_conf.chooser_type, + }; + struct xdpw_chooser_opts chooser_opts = { + .output_list = &ctx->output_list, + .target_mask = (1<screencast_data.persist_mode, + .outputname = ctx->state->config->screencast_conf.output_name, + }; + target_initialized = xdpw_wlr_target_chooser(&chooser, &chooser_opts, target); } if (!target_initialized) { logprint(ERROR, "wlroots: no output found"); diff --git a/src/screencast/screencast_common.c b/src/screencast/screencast_common.c index d6d13db7..533ef2d6 100644 --- a/src/screencast/screencast_common.c +++ b/src/screencast/screencast_common.c @@ -391,6 +391,8 @@ enum xdpw_chooser_types get_chooser_type(const char *chooser_type) { return XDPW_CHOOSER_SIMPLE; } else if (strcmp(chooser_type, "dmenu") == 0) { return XDPW_CHOOSER_DMENU; + } else if (strcmp(chooser_type, "json") == 0) { + return XDPW_CHOOSER_JSON; } fprintf(stderr, "Could not understand chooser type %s\n", chooser_type); exit(1); @@ -406,6 +408,8 @@ const char *chooser_type_str(enum xdpw_chooser_types chooser_type) { return "simple"; case XDPW_CHOOSER_DMENU: return "dmenu"; + case XDPW_CHOOSER_JSON: + return "json"; } fprintf(stderr, "Could not find chooser type %d\n", chooser_type); abort(); diff --git a/src/screencast/wlr_screencast.c b/src/screencast/wlr_screencast.c index 6dc595d5..8765fc04 100644 --- a/src/screencast/wlr_screencast.c +++ b/src/screencast/wlr_screencast.c @@ -3,6 +3,7 @@ #include "linux-dmabuf-unstable-v1-client-protocol.h" #include "wlr-screencopy-unstable-v1-client-protocol.h" #include +#include #include #include #include @@ -377,9 +378,10 @@ static bool wait_chooser(pid_t pid) { 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"); + assert(chooser->type != XDPW_CHOOSER_JSON); struct xdpw_wlr_output *out; - size_t name_size = 0; - char *name = NULL; + size_t chooser_msg_size = 0; + char *chooser_msg = NULL; *output = NULL; int chooser_in[2]; //p -> c @@ -402,9 +404,10 @@ static bool wlr_output_chooser(struct xdpw_output_chooser *chooser, goto error_fork; } + FILE *f; switch (chooser->type) { case XDPW_CHOOSER_DMENU:; - FILE *f = fdopen(chooser_in[1], "w"); + 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"); @@ -424,7 +427,7 @@ static bool wlr_output_chooser(struct xdpw_output_chooser *chooser, return false; } - FILE *f = fdopen(chooser_out[0], "r"); + 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"); @@ -432,7 +435,7 @@ static bool wlr_output_chooser(struct xdpw_output_chooser *chooser, goto end; } - ssize_t nread = getline(&name, &name_size, f); + ssize_t nread = getline(&chooser_msg, &chooser_msg_size, f); fclose(f); if (nread < 0) { perror("getline failed"); @@ -440,11 +443,12 @@ static bool wlr_output_chooser(struct xdpw_output_chooser *chooser, } //Strip newline - char *p = strchr(name, '\n'); + char *p = strchr(chooser_msg, '\n'); if (p != NULL) { *p = '\0'; } + char *name = chooser_msg; 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) { @@ -452,7 +456,8 @@ static bool wlr_output_chooser(struct xdpw_output_chooser *chooser, break; } } - free(name); + + free(chooser_msg); end: return true; @@ -468,6 +473,164 @@ static bool wlr_output_chooser(struct xdpw_output_chooser *chooser, return false; } +static bool wlr_target_chooser(struct xdpw_output_chooser *chooser, struct xdpw_chooser_opts *opts, + struct xdpw_screencast_target *target) { + logprint(DEBUG, "wlroots: target chooser called"); + assert(chooser->type == XDPW_CHOOSER_JSON); + target->output = NULL; + + size_t chooser_msg_size = 0; + char *chooser_msg = 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; + } + + 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; + } + + struct json_object *obj = json_object_new_object(); + json_object_object_add(obj, "version", json_object_new_int(1)); + json_object_object_add(obj, "revision", json_object_new_int(0)); + if (opts->target_mask & (1<output_list) { + struct json_object *monitor_arr = json_object_new_array(); + struct xdpw_wlr_output *out; + wl_list_for_each(out, opts->output_list, link) { + struct json_object *monitor_obj = json_object_new_object(); + json_object_object_add(monitor_obj, "name", json_object_new_string(out->name)); + json_object_object_add(monitor_obj, "make", json_object_new_string(out->make)); + json_object_object_add(monitor_obj, "model", json_object_new_string(out->model)); + json_object_object_add(monitor_obj, "id", json_object_new_int(out->id)); + json_object_array_add(monitor_arr, monitor_obj); + } + json_object_object_add(obj, "monitors", monitor_arr); + } + struct json_object *options_obj = json_object_new_object(); + struct json_object *persist_arr = json_object_new_array(); + if (0 <= opts->persist_mode) { + json_object_object_add(persist_arr, "none", json_object_new_int(0)); + } + if (1 <= opts->persist_mode) { + json_object_object_add(persist_arr, "transient", json_object_new_int(1)); + } + if (2 <= opts->persist_mode) { + json_object_object_add(persist_arr, "persistent", json_object_new_int(2)); + } + json_object_object_add(options_obj, "persist_mode", persist_arr); + json_object_object_add(obj, "options", options_obj); + size_t json_msg_size; + const char *json_msg = json_object_to_json_string_length(obj, JSON_C_TO_STRING_PLAIN, &json_msg_size); + if (json_msg_size == 0 || !json_msg) { + fclose(f); + logprint(ERROR, "Failed to create json_msg"); + goto error_chooser_out; + } + fwrite(json_msg, 1, json_msg_size, f); + logprint(TRACE, "chooser: json_msg %s", json_msg); + fclose(f); + json_object_put(obj); + + if (!wait_chooser(pid)) { + close(chooser_out[0]); + return false; + } + + 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(&chooser_msg, &chooser_msg_size, f); + fclose(f); + if (nread < 0) { + perror("getline failed"); + goto end; + } + + obj = json_tokener_parse(chooser_msg); + struct json_object *value; + json_object_object_get_ex(obj, "version", &value); + if (json_object_get_int(value) != 1) { + logprint(ERROR, "Chooser returned msg with wrong version %d", json_object_get_int(value)); + goto end; + } + json_object_object_get_ex(obj, "revision", &value); + if (json_object_get_int(value) > 0) { + logprint(ERROR, "Chooser returned msg with wrong revision %d", json_object_get_int(value)); + goto end; + } + json_object_object_get_ex(obj, "target_type", &value); + const char *target_type = json_object_get_string(value); + if (strcmp(target_type, "monitor") == 0) { + struct json_object *target_obj; + json_object_object_get_ex(obj, "target", &target_obj); + struct xdpw_wlr_output *out; + wl_list_for_each(out, opts->output_list, link) { + json_object_object_get_ex(target_obj, "name", &value); + if (strcmp(out->name, json_object_get_string(value)) != 0) { + logprint(ERROR, "chooser: wrong name %s:%s", out->name, json_object_get_string(value)); + continue; + } + json_object_object_get_ex(target_obj, "model", &value); + if (strcmp(out->model, json_object_get_string(value)) != 0) { + logprint(ERROR, "chooser: wrong model %s:%s", out->model, json_object_get_string(value)); + continue; + } + json_object_object_get_ex(target_obj, "make", &value); + if (strcmp(out->make, json_object_get_string(value)) != 0) { + logprint(ERROR, "chooser: wrong make %s:%s", out->make, json_object_get_string(value)); + continue; + } + json_object_object_get_ex(target_obj, "id", &value); + if (out->id != (unsigned int)json_object_get_int(value)) { + logprint(ERROR, "chooser: wrong name %u:%d", out->id, json_object_get_int(value)); + continue; + } + target->output = out; + break; + } + } else { + logprint(ERROR, "Chooser returned msg with wrong target_type %s", target_type); + goto end; + } + free(chooser_msg); + +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: + 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[] = { @@ -496,31 +659,27 @@ static struct xdpw_wlr_output *wlr_output_chooser_default(struct wl_list *output return xdpw_wlr_output_first(output_list); } -static struct xdpw_wlr_output *xdpw_wlr_output_chooser(struct xdpw_screencast_context *ctx) { - switch (ctx->state->config->screencast_conf.chooser_type) { +static struct xdpw_wlr_output *xdpw_wlr_output_chooser(struct xdpw_output_chooser *chooser, struct xdpw_chooser_opts *opts) { + switch (chooser->type) { case XDPW_CHOOSER_DEFAULT: - return wlr_output_chooser_default(&ctx->output_list); + return wlr_output_chooser_default(opts->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); + if (opts->outputname) { + return xdpw_wlr_output_find_by_name(opts->output_list, opts->outputname); } else { - return xdpw_wlr_output_first(&ctx->output_list); + return xdpw_wlr_output_first(opts->output_list); } case XDPW_CHOOSER_DMENU: case XDPW_CHOOSER_SIMPLE:; struct xdpw_wlr_output *output = NULL; - if (!ctx->state->config->screencast_conf.chooser_cmd) { + if (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); + logprint(DEBUG, "wlroots: output chooser %s (%d)", chooser->cmd, chooser->type); + bool ret = wlr_output_chooser(chooser, opts->output_list, &output); if (!ret) { - logprint(ERROR, "wlroots: output chooser %s failed", chooser.cmd); + logprint(ERROR, "wlroots: output chooser %s failed", chooser->cmd); goto end; } if (output) { @@ -529,14 +688,19 @@ static struct xdpw_wlr_output *xdpw_wlr_output_chooser(struct xdpw_screencast_co logprint(DEBUG, "wlroots: output chooser canceled"); } return output; + default: + abort(); } end: return NULL; } -bool xdpw_wlr_target_chooser(struct xdpw_screencast_context *ctx, struct xdpw_screencast_target *target) { - target->output = xdpw_wlr_output_chooser(ctx); - return target->output != NULL; +bool xdpw_wlr_target_chooser(struct xdpw_output_chooser *chooser, struct xdpw_chooser_opts *opts, struct xdpw_screencast_target *target) { + if (chooser->type != XDPW_CHOOSER_JSON) { + target->output = xdpw_wlr_output_chooser(chooser, opts); + return target->output != NULL; + } + return wlr_target_chooser(chooser, opts, target); } bool xdpw_wlr_target_from_data(struct xdpw_screencast_context *ctx, struct xdpw_screencast_target *target, diff --git a/xdg-desktop-portal-wlr.5.scd b/xdg-desktop-portal-wlr.5.scd index 6cec9686..6a91b0c6 100644 --- a/xdg-desktop-portal-wlr.5.scd +++ b/xdg-desktop-portal-wlr.5.scd @@ -68,7 +68,7 @@ These options need to be placed under the **[screencast]** section. (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 + - simple, dmenu, json: xdpw will launch the chooser given by **chooser_cmd**. For more details see **OUTPUT CHOOSER**. **force_mod_linear** = _bool_ @@ -93,6 +93,8 @@ The chooser can be any program or script with the following behaviour: 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. +- json: the chooser receives a list of outputs formated as a json stdin ++ + {"monitor":[{"name": "eDP-1","make": "","model": "","id": "42"}]} # SEE ALSO From 234dfdd125be68599f3454aefc0a5a1bea5ed08a Mon Sep 17 00:00:00 2001 From: columbarius Date: Fri, 5 Aug 2022 02:02:57 +0200 Subject: [PATCH 2/3] DNM: screencast: chooser-protocoll example Just a scratch how request and response could look like. --- chooser-protocol.json | 53 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 chooser-protocol.json diff --git a/chooser-protocol.json b/chooser-protocol.json new file mode 100644 index 00000000..08b3bd5f --- /dev/null +++ b/chooser-protocol.json @@ -0,0 +1,53 @@ +{ + "request": { + "version": 1, + "revision": 0, + "monitors": [ + { + "name": "Name1", + "make": "Make1", + "model": "Model1", + "id": 1 + }, + { + "name": "Name2", + "make": "Make2", + "model": "Model2", + "id": 2 + } + ], + "windows": [ + { + "title": "Title1", + "app_id": "App_Id1", + "id": 1 + }, + { + "title": "Title2", + "app_id": "App_Id2", + "id": 2 + } + ], + "options": { + "persist_mode": [ + {"none": 0}, + {"transient": 1}, + {"permanent": 2} + ] + } + }, + "response": { + "version": 1, + "revision": 0, + "target_type": "output", + "target": { + "name": "Name1", + "make": "Make1", + "model": "Model1", + "id": 1 + }, + "options": { + "persist_mode": 0 + } + } +} From 71b50679030bf04e0efae29ced0de575a7cec4e8 Mon Sep 17 00:00:00 2001 From: columbarius Date: Fri, 5 Aug 2022 02:25:20 +0200 Subject: [PATCH 3/3] fix build --- .builds/alpine.yml | 1 + .builds/archlinux.yml | 1 + .builds/freebsd.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.builds/alpine.yml b/.builds/alpine.yml index 9092c812..30624568 100644 --- a/.builds/alpine.yml +++ b/.builds/alpine.yml @@ -10,6 +10,7 @@ packages: - scdoc - libdrm - mesa-dev + - json-c-dev sources: - https://github.com/emersion/xdg-desktop-portal-wlr tasks: diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml index e9b6d8fd..8d44794c 100644 --- a/.builds/archlinux.yml +++ b/.builds/archlinux.yml @@ -9,6 +9,7 @@ packages: - libinih - scdoc - mesa + - json-c sources: - https://github.com/emersion/xdg-desktop-portal-wlr tasks: diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index fd012d84..1f4dc74e 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -11,6 +11,7 @@ packages: - scdoc - graphics/libdrm - graphics/mesa-libs + - json-c sources: - https://github.com/emersion/xdg-desktop-portal-wlr tasks: