From 9d78b21695a5f47ddd350c2f0b27dabfb10af25e Mon Sep 17 00:00:00 2001 From: columbarius Date: Sat, 10 Oct 2020 16:20:06 +0200 Subject: [PATCH] screencast: add outputchooser with config option Supports "dmenu" chooser type, which is called with a dmenu type list piped to stdin, "simple" type, which recieves nothing on stdin and default, which tries the hardcoded choosers. Choosers are required to return the name of the choosen output as given by the xdg-output protocol. Thanks to piater for closing overlooked pipes. Thanks to ericonr for suggestions regarding fork and pipes. --- contrib/config.sample | 2 + include/config.h | 3 + include/screencast_common.h | 14 ++ include/wlr_screencast.h | 1 + src/core/config.c | 37 ++++-- src/core/logger.c | 2 +- src/core/main.c | 1 + src/screencast/screencast.c | 34 ++--- src/screencast/screencast_common.c | 29 +++++ src/screencast/wlr_screencast.c | 200 ++++++++++++++++++++++++++++- 10 files changed, 287 insertions(+), 36 deletions(-) diff --git a/contrib/config.sample b/contrib/config.sample index 57c8021..bf53515 100644 --- a/contrib/config.sample +++ b/contrib/config.sample @@ -1,3 +1,5 @@ [screencast] output_name= max_fps=30 +chooser_cmd="slurp -f %o -o" +chooser_type=simple diff --git a/include/config.h b/include/config.h index 7961bf6..13700c8 100644 --- a/include/config.h +++ b/include/config.h @@ -2,12 +2,15 @@ #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 { diff --git a/include/screencast_common.h b/include/screencast_common.h index 4200800..7cfa149 100644 --- a/include/screencast_common.h +++ b/include/screencast_common.h @@ -22,6 +22,18 @@ enum source_types { 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; @@ -113,4 +125,6 @@ enum spa_video_format xdpw_format_pw_from_wl_shm( struct xdpw_screencast_instance *cast); 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 */ diff --git a/include/wlr_screencast.h b/include/wlr_screencast.h index 09cbe84..a1585bc 100644 --- a/include/wlr_screencast.h +++ b/include/wlr_screencast.h @@ -22,6 +22,7 @@ struct xdpw_wlr_output *xdpw_wlr_output_find_by_name(struct wl_list *output_list 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); diff --git a/src/core/config.c b/src/core/config.c index 3510af2..3e27fad 100644 --- a/src/core/config.c +++ b/src/core/config.c @@ -1,6 +1,7 @@ #include "config.h" #include "xdpw.h" #include "logger.h" +#include "screencast_common.h" #include #include @@ -11,6 +12,8 @@ void print_config(enum LOGLEVEL loglevel, struct xdpw_config *config) { logprint(loglevel, "config: outputname %s", config->screencast_conf.output_name); + logprint(loglevel, "config: chooser_cmd: %s\n", config->screencast_conf.chooser_cmd); + logprint(loglevel, "config: chooser_type: %s\n", chooser_type_str(config->screencast_conf.chooser_type)); } // NOTE: calling finish_config won't prepare the config to be read again from config file @@ -22,6 +25,7 @@ void finish_config(struct xdpw_config *config) { 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 getstring_from_conffile(dictionary *d, @@ -53,19 +57,6 @@ 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 void config_parse_file(const char *configfile, struct xdpw_config *config) { dictionary *d = NULL; if (configfile) { @@ -83,12 +74,32 @@ static void config_parse_file(const char *configfile, struct xdpw_config *config getdouble_from_conffile(d, "screencast:max_fps", &config->screencast_conf.max_fps, 0); getstring_from_conffile(d, "screencast:exec_before", &config->screencast_conf.exec_before, NULL); getstring_from_conffile(d, "screencast:exec_after", &config->screencast_conf.exec_after, NULL); + getstring_from_conffile(d, "screencast:chooser_cmd", &config->screencast_conf.chooser_cmd, NULL); + if (!config->screencast_conf.chooser_type) { + char *chooser_type = NULL; + getstring_from_conffile(d, "screencast:chooser_type", &chooser_type, "default"); + config->screencast_conf.chooser_type = get_chooser_type(chooser_type); + free(chooser_type); + } iniparser_freedict(d); logprint(DEBUG, "config: config file parsed"); print_config(DEBUG, config); } +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"); size_t size_fallback = 1 + strlen(home) + strlen("/.config"); diff --git a/src/core/logger.c b/src/core/logger.c index e49e210..a966992 100644 --- a/src/core/logger.c +++ b/src/core/logger.c @@ -28,7 +28,7 @@ enum LOGLEVEL get_loglevel(const char *level) { } fprintf(stderr, "Could not understand log level %s\n", level); - abort(); + exit(1); } static const char *print_loglevel(enum LOGLEVEL loglevel) { diff --git a/src/core/main.c b/src/core/main.c index e1173d0..5fccab2 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -74,6 +74,7 @@ int main(int argc, char *argv[]) { break; case 'o': config.screencast_conf.output_name = strdup(optarg); + config.screencast_conf.chooser_type = XDPW_CHOOSER_NONE; break; case 'c': configfile = strdup(optarg); diff --git a/src/screencast/screencast.c b/src/screencast/screencast.c index 24fdbcc..de116b8 100644 --- a/src/screencast/screencast.c +++ b/src/screencast/screencast.c @@ -77,7 +77,7 @@ void xdpw_screencast_instance_destroy(struct xdpw_screencast_instance *cast) { free(cast); } -int setup_outputs(struct xdpw_screencast_context *ctx, struct xdpw_session *sess, bool with_cursor) { +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) { @@ -86,19 +86,10 @@ int setup_outputs(struct xdpw_screencast_context *ctx, struct xdpw_session *sess } struct xdpw_wlr_output *out; - if (ctx->state->config->screencast_conf.output_name) { - out = xdpw_wlr_output_find_by_name(&ctx->output_list, - ctx->state->config->screencast_conf.output_name); - if (!out) { - logprint(ERROR, "wlroots: no such output"); - abort(); - } - } else { - out = xdpw_wlr_output_first(&ctx->output_list); - if (!out) { - logprint(ERROR, "wlroots: no output found"); - abort(); - } + out = xdpw_wlr_output_chooser(ctx); + if (!out) { + logprint(ERROR, "wlroots: no output found"); + return false; } struct xdpw_screencast_instance *cast, *tmp_c; @@ -130,7 +121,7 @@ int setup_outputs(struct xdpw_screencast_context *ctx, struct xdpw_session *sess logprint(INFO, "wlroots: output: %s", sess->screencast_instance->target_output->name); - return 0; + return true; } @@ -310,22 +301,23 @@ static int method_screencast_select_sources(sd_bus_message *msg, void *data, return ret; } - ret = -1; + bool output_selection_canceled = 1; wl_list_for_each_reverse_safe(sess, tmp_s, &state->xdpw_sessions, link) { if (strcmp(sess->session_handle, session_handle) == 0) { logprint(DEBUG, "dbus: select sources: found matching session %s", sess->session_handle); - ret = setup_outputs(ctx, sess, cursor_embedded); + output_selection_canceled = !setup_outputs(ctx, sess, cursor_embedded); } } - if (ret < 0) { - return ret; - } 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 (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; } diff --git a/src/screencast/screencast_common.c b/src/screencast/screencast_common.c index f185ad7..3059cc6 100644 --- a/src/screencast/screencast_common.c +++ b/src/screencast/screencast_common.c @@ -52,3 +52,32 @@ enum spa_video_format xdpw_format_pw_strip_alpha(enum spa_video_format format) { 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(); +} diff --git a/src/screencast/wlr_screencast.c b/src/screencast/wlr_screencast.c index 29f89a5..77da095 100644 --- a/src/screencast/wlr_screencast.c +++ b/src/screencast/wlr_screencast.c @@ -140,7 +140,7 @@ static void wlr_frame_buffer_done(void *data, 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"); @@ -313,6 +313,204 @@ static void wlr_init_xdg_outputs(struct xdpw_screencast_context *ctx) { } } +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]); + goto end; + } + + 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 -o"}, + {XDPW_CHOOSER_DMENU, "wofi -d -n"}, + {XDPW_CHOOSER_DMENU, "bemenu"}, + }; + + 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) {