diff --git a/.builds/alpine.yml b/.builds/alpine.yml index b6424ec..0308f2e 100644 --- a/.builds/alpine.yml +++ b/.builds/alpine.yml @@ -6,6 +6,7 @@ packages: - pipewire-dev - wayland-dev - wayland-protocols + - iniparser-dev sources: - https://github.com/emersion/xdg-desktop-portal-wlr tasks: diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml index 5e8bff3..94bb601 100644 --- a/.builds/archlinux.yml +++ b/.builds/archlinux.yml @@ -6,6 +6,7 @@ packages: - wayland - wayland-protocols - pipewire + - iniparser sources: - https://github.com/emersion/xdg-desktop-portal-wlr tasks: diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index 9e693cb..0eda101 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -6,6 +6,7 @@ packages: - pkgconf - wayland - wayland-protocols + - iniparser sources: - https://github.com/emersion/xdg-desktop-portal-wlr tasks: diff --git a/contrib/config.sample b/contrib/config.sample new file mode 100644 index 0000000..5daea46 --- /dev/null +++ b/contrib/config.sample @@ -0,0 +1,2 @@ +[screencast] +output= diff --git a/include/config.h b/include/config.h new file mode 100644 index 0000000..9260a70 --- /dev/null +++ b/include/config.h @@ -0,0 +1,18 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include "logger.h" + +struct config_screencast { + char *output_name; +}; + +struct xdpw_config { + struct config_screencast screencast_conf; +}; + +void print_config(enum LOGLEVEL loglevel, struct xdpw_config *config); +void finish_config(struct xdpw_config *config); +void init_config(char ** const configfile, struct xdpw_config *config); + +#endif diff --git a/include/logger.h b/include/logger.h index 3a7e13f..89a2cb3 100644 --- a/include/logger.h +++ b/include/logger.h @@ -3,6 +3,8 @@ #include +#define DEFAULT_LOGLEVEL ERROR + enum LOGLEVEL { QUIET, ERROR, WARN, INFO, DEBUG, TRACE }; struct logger_properties { diff --git a/include/screencast_common.h b/include/screencast_common.h index 0724bd1..4aea195 100644 --- a/include/screencast_common.h +++ b/include/screencast_common.h @@ -57,9 +57,6 @@ struct xdpw_screencast_context { struct zxdg_output_manager_v1* xdg_output_manager; struct wl_shm *shm; - // cli options - const char *output_name; - // sessions struct wl_list screencast_instances; }; diff --git a/include/xdpw.h b/include/xdpw.h index 2d556d7..eeb81e7 100644 --- a/include/xdpw.h +++ b/include/xdpw.h @@ -11,6 +11,7 @@ #endif #include "screencast_common.h" +#include "config.h" struct xdpw_state { struct wl_list xdpw_sessions; @@ -21,6 +22,7 @@ struct xdpw_state { uint32_t screencast_source_types; // bitfield of enum source_types uint32_t screencast_cursor_modes; // bitfield of enum cursor_modes uint32_t screencast_version; + struct xdpw_config *config; }; struct xdpw_request { @@ -41,7 +43,7 @@ enum { }; int xdpw_screenshot_init(struct xdpw_state *state); -int xdpw_screencast_init(struct xdpw_state *state, const char *output_name); +int xdpw_screencast_init(struct xdpw_state *state); struct xdpw_request *xdpw_request_create(sd_bus *bus, const char *object_path); void xdpw_request_destroy(struct xdpw_request *req); diff --git a/meson.build b/meson.build index 8c00f17..fad4ab2 100644 --- a/meson.build +++ b/meson.build @@ -16,12 +16,17 @@ add_project_arguments(cc.get_supported_arguments([ '-D_POSIX_C_SOURCE=200809L', ]), language: 'c') +prefix = get_option('prefix') +sysconfdir = get_option('sysconfdir') +add_project_arguments('-DSYSCONFDIR="@0@"'.format(join_paths(prefix, sysconfdir)), language : 'c') + inc = include_directories('include') rt = cc.find_library('rt') pipewire = dependency('libpipewire-0.3', version: '>= 0.3.2') wayland_client = dependency('wayland-client') wayland_protos = dependency('wayland-protocols', version: '>=1.14') +iniparser = cc.find_library('iniparser', dirs: [join_paths(get_option('prefix'),get_option('libdir'))]) if get_option('sd-bus-provider') == 'auto' assert(get_option('auto_features').auto(), 'sd-bus-provider must not be set to auto since auto_features != auto') @@ -55,6 +60,7 @@ executable( files([ 'src/core/main.c', 'src/core/logger.c', + 'src/core/config.c', 'src/core/request.c', 'src/core/session.c', 'src/screenshot/screenshot.c', @@ -69,6 +75,7 @@ executable( sdbus, pipewire, rt, + iniparser, ], include_directories: [inc], install: true, diff --git a/src/core/config.c b/src/core/config.c new file mode 100644 index 0000000..e29b310 --- /dev/null +++ b/src/core/config.c @@ -0,0 +1,118 @@ +#include "config.h" +#include "xdpw.h" +#include "logger.h" + +#include +#include +#include +#include +#include +#include + +void print_config(enum LOGLEVEL loglevel, struct xdpw_config *config) { + logprint(loglevel, "config: outputname %s", config->screencast_conf.output_name); +} + +// NOTE: calling finish_config won't prepare the config to be read again from config file +// with init_config since to pointers and other values won't be reset to NULL, or 0 +void finish_config(struct xdpw_config *config) { + logprint(DEBUG, "config: destroying config"); + + // screencast + free(&config->screencast_conf.output_name); +} + +static void getstring_from_conffile(dictionary *d, + const char *key, char **dest, const char *fallback) { + if (*dest != NULL) { + return; + } + const char *c = iniparser_getstring(d, key, fallback); + if (c == NULL) { + return; + } + // Allow keys without value as default + if (strcmp(c, "") != 0) { + *dest = strdup(c); + } else { + *dest = fallback ? strdup(fallback) : NULL; + } +} + +static bool file_exists(const char *path) { + return path && access(path, R_OK) != -1; +} + +static char *config_path(char *prefix, 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) { + logprint(INFO, "config: using config file %s", *configfile); + d = iniparser_load(configfile); + } else { + logprint(INFO, "config: no config file found"); + } + if (configfile && !d) { + logprint(ERROR, "config: unable to load config file %s", configfile); + } + + // screencast + getstring_from_conffile(d, "screencast:output_name", &config->screencast_conf.output_name, NULL); + + iniparser_freedict(d); + logprint(DEBUG, "config: config file parsed"); + print_config(DEBUG, config); +} + +static char *get_config_path(void) { + const char *home = getenv("HOME"); + size_t size_fallback = 1 + strlen(home) + strlen("/.config"); + char *config_home_fallback = calloc(size_fallback, sizeof(char)); + snprintf(config_home_fallback, size_fallback, "%s/.config", home); + + char *prefix[4]; + prefix[0] = getenv("XDG_CONFIG_HOME"); + prefix[1] = config_home_fallback; + prefix[2] = SYSCONFDIR "/xdg"; + prefix[3] = SYSCONFDIR; + + char *config[2]; + config[0] = getenv("XDG_CURRENT_DESKTOP"); + config[1] = "config"; + + for (size_t i = 0; i < 4; i++) { + for (size_t j = 0; j < 2; j++) { + char *path = config_path(prefix[i], config[j]); + if (!path) { + continue; + } + logprint(TRACE, "config: trying config file %s", path); + if (file_exists(path)) { + return path; + } + free(path); + } + } + + return NULL; +} + +void init_config(char ** const configfile, struct xdpw_config *config) { + if (*configfile == NULL) { + *configfile = get_config_path(); + } + + config_parse_file(*configfile, config); +} diff --git a/src/core/logger.c b/src/core/logger.c index fa0d7b1..e49e210 100644 --- a/src/core/logger.c +++ b/src/core/logger.c @@ -5,17 +5,6 @@ #include #include -static int NUM_LEVELS = 6; - -static const char *loglevels[] = { - "QUIET", - "ERROR", - "WARN", - "INFO", - "DEBUG", - "TRACE" -}; - static struct logger_properties logprops; void init_logger(FILE *dst, enum LOGLEVEL level) { @@ -24,16 +13,43 @@ void init_logger(FILE *dst, enum LOGLEVEL level) { } enum LOGLEVEL get_loglevel(const char *level) { - int i; - for (i = 0; i < NUM_LEVELS; i++) { - if (!strcmp(level, loglevels[i])) { - return (enum LOGLEVEL) i; - } + if (strcmp(level, "QUIET") == 0) { + return QUIET; + } else if (strcmp(level, "ERROR") == 0) { + return ERROR; + } else if (strcmp(level, "WARN") == 0) { + return WARN; + } else if (strcmp(level, "INFO") == 0) { + return INFO; + } else if (strcmp(level, "DEBUG") == 0) { + return DEBUG; + } else if (strcmp(level, "TRACE") == 0) { + return TRACE; } + fprintf(stderr, "Could not understand log level %s\n", level); abort(); } +static const char *print_loglevel(enum LOGLEVEL loglevel) { + switch (loglevel) { + case QUIET: + return "QUIET"; + case ERROR: + return "ERROR"; + case WARN: + return "WARN"; + case INFO: + return "INFO"; + case DEBUG: + return "DEBUG"; + case TRACE: + return "TRACE"; + } + fprintf(stderr, "Could not find log level %d\n", loglevel); + abort(); +} + void logprint(enum LOGLEVEL level, char *msg, ...) { if (!logprops.dst) { fprintf(stderr, "Logger has been called, but was not initialized\n"); @@ -56,7 +72,7 @@ void logprint(enum LOGLEVEL level, char *msg, ...) { fprintf(logprops.dst, "%s", timestr); fprintf(logprops.dst, " "); - fprintf(logprops.dst, "[%s]", loglevels[level]); + fprintf(logprops.dst, "[%s]", print_loglevel(level)); fprintf(logprops.dst, " - "); va_start(args, msg); diff --git a/src/core/main.c b/src/core/main.c index 5d16449..3ec8e1a 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -24,6 +24,8 @@ static int xdpw_usage(FILE* stream, int rc) { " QUIET, ERROR, WARN, INFO, DEBUG, TRACE\n" " -o, --output= Select output to capture.\n" " metadata (performs no conversion).\n" + " -c, --config= Select config file.\n" + " (default is $XDG_CONFIG_HOME/xdg-desktop-portal-wlr/config)\n" " -r, --replace Replace a running instance.\n" " -h, --help Get help (this text).\n" "\n"; @@ -39,14 +41,16 @@ static int handle_name_lost(sd_bus_message *m, void *userdata, sd_bus_error *ret } int main(int argc, char *argv[]) { - const char* output_name = NULL; - enum LOGLEVEL loglevel = ERROR; + struct xdpw_config config = {0}; + char *configfile = NULL; + enum LOGLEVEL loglevel = DEFAULT_LOGLEVEL; bool replace = false; - static const char* shortopts = "l:o:rh"; + static const char* shortopts = "l:o:c:rh"; static const struct option longopts[] = { { "loglevel", required_argument, NULL, 'l' }, { "output", required_argument, NULL, 'o' }, + { "config", required_argument, NULL, 'c' }, { "replace", no_argument, NULL, 'r' }, { "help", no_argument, NULL, 'h' }, { NULL, 0, NULL, 0 } @@ -62,7 +66,10 @@ int main(int argc, char *argv[]) { loglevel = get_loglevel(optarg); break; case 'o': - output_name = optarg; + config.screencast_conf.output_name = strdup(optarg); + break; + case 'c': + configfile = strdup(optarg); break; case 'r': replace = true; @@ -75,6 +82,7 @@ int main(int argc, char *argv[]) { } init_logger(stderr, loglevel); + init_config(&configfile, &config); int ret = 0; @@ -111,12 +119,13 @@ int main(int argc, char *argv[]) { .screencast_source_types = MONITOR, .screencast_cursor_modes = HIDDEN | EMBEDDED, .screencast_version = XDP_CAST_PROTO_VER, + .config = &config, }; wl_list_init(&state.xdpw_sessions); xdpw_screenshot_init(&state); - ret = xdpw_screencast_init(&state, output_name); + ret = xdpw_screencast_init(&state); if (ret < 0) { logprint(ERROR, "xdpw: failed to initialize screencast"); goto error; @@ -217,6 +226,8 @@ int main(int argc, char *argv[]) { } // TODO: cleanup + finish_config(&config); + free(configfile); return EXIT_SUCCESS; diff --git a/src/screencast/screencast.c b/src/screencast/screencast.c index 05ffd3f..e4be866 100644 --- a/src/screencast/screencast.c +++ b/src/screencast/screencast.c @@ -48,8 +48,9 @@ int setup_outputs(struct xdpw_screencast_context *ctx, struct xdpw_session *sess } struct xdpw_wlr_output *out; - if (ctx->output_name) { - out = xdpw_wlr_output_find_by_name(&ctx->output_list, ctx->output_name); + 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(); @@ -434,12 +435,11 @@ static const sd_bus_vtable screencast_vtable[] = { SD_BUS_VTABLE_END }; -int xdpw_screencast_init(struct xdpw_state *state, const char *output_name) { +int xdpw_screencast_init(struct xdpw_state *state) { sd_bus_slot *slot = NULL; state->screencast = (struct xdpw_screencast_context) { 0 }; state->screencast.state = state; - state->screencast.output_name = output_name; int err; err = xdpw_pwr_core_connect(state);