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) {