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.
This commit is contained in:
columbarius 2020-10-10 16:20:06 +02:00 committed by Simon Ser
parent 4c2d8fc808
commit 9d78b21695
10 changed files with 287 additions and 36 deletions

View file

@ -1,3 +1,5 @@
[screencast] [screencast]
output_name= output_name=
max_fps=30 max_fps=30
chooser_cmd="slurp -f %o -o"
chooser_type=simple

View file

@ -2,12 +2,15 @@
#define CONFIG_H #define CONFIG_H
#include "logger.h" #include "logger.h"
#include "screencast_common.h"
struct config_screencast { struct config_screencast {
char *output_name; char *output_name;
double max_fps; double max_fps;
char *exec_before; char *exec_before;
char *exec_after; char *exec_after;
char *chooser_cmd;
enum xdpw_chooser_types chooser_type;
}; };
struct xdpw_config { struct xdpw_config {

View file

@ -22,6 +22,18 @@ enum source_types {
WINDOW = 2, 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 { struct xdpw_frame_damage {
uint32_t x; uint32_t x;
uint32_t y; uint32_t y;
@ -113,4 +125,6 @@ enum spa_video_format xdpw_format_pw_from_wl_shm(
struct xdpw_screencast_instance *cast); struct xdpw_screencast_instance *cast);
enum spa_video_format xdpw_format_pw_strip_alpha(enum spa_video_format format); 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 */ #endif /* SCREENCAST_COMMON_H */

View file

@ -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_first(struct wl_list *output_list);
struct xdpw_wlr_output *xdpw_wlr_output_find(struct xdpw_screencast_context *ctx, struct xdpw_wlr_output *xdpw_wlr_output_find(struct xdpw_screencast_context *ctx,
struct wl_output *out, uint32_t id); 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_frame_free(struct xdpw_screencast_instance *cast);
void xdpw_wlr_register_cb(struct xdpw_screencast_instance *cast); void xdpw_wlr_register_cb(struct xdpw_screencast_instance *cast);

View file

@ -1,6 +1,7 @@
#include "config.h" #include "config.h"
#include "xdpw.h" #include "xdpw.h"
#include "logger.h" #include "logger.h"
#include "screencast_common.h"
#include <dictionary.h> #include <dictionary.h>
#include <stdio.h> #include <stdio.h>
@ -11,6 +12,8 @@
void print_config(enum LOGLEVEL loglevel, struct xdpw_config *config) { void print_config(enum LOGLEVEL loglevel, struct xdpw_config *config) {
logprint(loglevel, "config: outputname %s", config->screencast_conf.output_name); 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 // 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.output_name);
free(&config->screencast_conf.exec_before); free(&config->screencast_conf.exec_before);
free(&config->screencast_conf.exec_after); free(&config->screencast_conf.exec_after);
free(&config->screencast_conf.chooser_cmd);
} }
static void getstring_from_conffile(dictionary *d, 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; 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) { static void config_parse_file(const char *configfile, struct xdpw_config *config) {
dictionary *d = NULL; dictionary *d = NULL;
if (configfile) { 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); 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_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: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); iniparser_freedict(d);
logprint(DEBUG, "config: config file parsed"); logprint(DEBUG, "config: config file parsed");
print_config(DEBUG, config); 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) { static char *get_config_path(void) {
const char *home = getenv("HOME"); const char *home = getenv("HOME");
size_t size_fallback = 1 + strlen(home) + strlen("/.config"); size_t size_fallback = 1 + strlen(home) + strlen("/.config");

View file

@ -28,7 +28,7 @@ enum LOGLEVEL get_loglevel(const char *level) {
} }
fprintf(stderr, "Could not understand log level %s\n", level); fprintf(stderr, "Could not understand log level %s\n", level);
abort(); exit(1);
} }
static const char *print_loglevel(enum LOGLEVEL loglevel) { static const char *print_loglevel(enum LOGLEVEL loglevel) {

View file

@ -74,6 +74,7 @@ int main(int argc, char *argv[]) {
break; break;
case 'o': case 'o':
config.screencast_conf.output_name = strdup(optarg); config.screencast_conf.output_name = strdup(optarg);
config.screencast_conf.chooser_type = XDPW_CHOOSER_NONE;
break; break;
case 'c': case 'c':
configfile = strdup(optarg); configfile = strdup(optarg);

View file

@ -77,7 +77,7 @@ void xdpw_screencast_instance_destroy(struct xdpw_screencast_instance *cast) {
free(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; struct xdpw_wlr_output *output, *tmp_o;
wl_list_for_each_reverse_safe(output, tmp_o, &ctx->output_list, link) { 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; struct xdpw_wlr_output *out;
if (ctx->state->config->screencast_conf.output_name) { out = xdpw_wlr_output_chooser(ctx);
out = xdpw_wlr_output_find_by_name(&ctx->output_list, if (!out) {
ctx->state->config->screencast_conf.output_name); logprint(ERROR, "wlroots: no output found");
if (!out) { return false;
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();
}
} }
struct xdpw_screencast_instance *cast, *tmp_c; 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", logprint(INFO, "wlroots: output: %s",
sess->screencast_instance->target_output->name); 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; return ret;
} }
ret = -1; bool output_selection_canceled = 1;
wl_list_for_each_reverse_safe(sess, tmp_s, &state->xdpw_sessions, link) { wl_list_for_each_reverse_safe(sess, tmp_s, &state->xdpw_sessions, link) {
if (strcmp(sess->session_handle, session_handle) == 0) { if (strcmp(sess->session_handle, session_handle) == 0) {
logprint(DEBUG, "dbus: select sources: found matching session %s", sess->session_handle); 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); ret = sd_bus_message_new_method_return(msg, &reply);
if (ret < 0) { if (ret < 0) {
return ret; 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) { if (ret < 0) {
return ret; return ret;
} }

View file

@ -52,3 +52,32 @@ enum spa_video_format xdpw_format_pw_strip_alpha(enum spa_video_format format) {
return SPA_VIDEO_FORMAT_UNKNOWN; 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();
}

View file

@ -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; i<N; i++) {
ret = wlr_output_chooser(&default_chooser[i], output_list, &output);
if (!ret) {
logprint(DEBUG, "wlroots: output chooser %s not found. Trying next one.",
default_chooser[i].cmd);
continue;
}
if (output != NULL) {
logprint(DEBUG, "wlroots: output chooser selects %s", output->name);
} 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 *xdpw_wlr_output_first(struct wl_list *output_list) {
struct xdpw_wlr_output *output, *tmp; struct xdpw_wlr_output *output, *tmp;
wl_list_for_each_safe(output, tmp, output_list, link) { wl_list_for_each_safe(output, tmp, output_list, link) {