diff --git a/cagebreak.c b/cagebreak.c index 4a71480..1ba61fe 100644 --- a/cagebreak.c +++ b/cagebreak.c @@ -307,6 +307,8 @@ main(int argc, char *argv[]) { goto end; } + wl_list_init(&server.output_config); + renderer = wlr_backend_get_renderer(backend); wlr_renderer_init_wl_display(renderer, server.wl_display); @@ -506,6 +508,13 @@ main(int argc, char *argv[]) { free(config_file); } + { + struct cg_output *output; + wl_list_for_each(output, &server.outputs, link) { + output_configure(&server, output); + } + } + /* Place the cursor to the topl left of the output layout. */ wlr_cursor_warp(server.seat->cursor, NULL, 0, 0); @@ -523,6 +532,13 @@ end: } free(server.modes); + struct cg_output_config *output_config, *output_config_tmp; + wl_list_for_each_safe(output_config, output_config_tmp, &server.output_config, link) { + wl_list_remove(&output_config->link); + free(output_config->output_name); + free(output_config); + } + keybinding_list_free(server.keybindings); wl_event_source_remove(sigint_source); wl_event_source_remove(sigterm_source); diff --git a/examples/config b/examples/config index cd7ee3c..0f0a5b0 100644 --- a/examples/config +++ b/examples/config @@ -70,3 +70,8 @@ definekey top XF86AudioLowerVolume exec pactl set-sink-mute 0 off&&amixer set Ma definekey top XF86AudioRaiseVolume exec pactl set-sink-mute 0 off&&amixer set Master 1%+ definekey top XF86MonBrightnessDown exec xbacklight -dec 1 definekey top XF86MonBrightnessUp exec xbacklight -inc 1 + +###################### +#Output configuration +###################### +#output eDP-1 pos 0 0 res 1366x768 rate 60 diff --git a/fuzz/fuzz-parse.c b/fuzz/fuzz-parse.c index 257f289..e159293 100644 --- a/fuzz/fuzz-parse.c +++ b/fuzz/fuzz-parse.c @@ -5,6 +5,7 @@ #include "../parse.h" #include "../seat.h" #include "../server.h" +#include "../output.h" #include #include #include @@ -58,6 +59,7 @@ LLVMFuzzerInitialize(int *argc, char ***argv) { /* new_xwayland_surface */ server.keybindings = keybinding_list_init(); server.output_transform = 0; + wl_list_init(&server.output_config); server.running = true; server.modes = malloc(sizeof(char *)); @@ -82,5 +84,13 @@ LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { free(str); keybinding_list_free(server.keybindings); server.keybindings = keybinding_list_init(); + + struct cg_output_config *output_config, *output_config_tmp; + wl_list_for_each_safe(output_config, output_config_tmp, &server.output_config, link) { + wl_list_remove(&output_config->link); + free(output_config->output_name); + free(output_config); + } + return 0; } diff --git a/man/cagebreak-config.5.md b/man/cagebreak-config.5.md index bbf3096..6be7c60 100644 --- a/man/cagebreak-config.5.md +++ b/man/cagebreak-config.5.md @@ -182,6 +182,13 @@ by prepending a line with the # symbol. > Enter mode "``". After a keybinding is processed, return to default mode +**output pos res x rate ** + +> Configure the output "". and are the position of the monitor +> in pixels. The top-left monitor should have the coordinates 0 0. and +> specify the resolution in pixels and sets the refresh rate of +> the monitor (often this is 50 or 60). + **setmode ** > Set the default mode to `` diff --git a/output.c b/output.c index 4fd6960..7983983 100644 --- a/output.c +++ b/output.c @@ -480,6 +480,72 @@ handle_output_destroy(struct wl_listener *listener, void *data) { output_destroy(output); } +struct cg_output_config* +output_find_config(struct cg_server* server, struct wlr_output* output) { + struct cg_output_config *config; + wl_list_for_each(config, &server->output_config, link) { + if(strcmp(config->output_name, output->name) == 0) { + return config; + } + } + return NULL; +} + +static void output_set_mode(struct wlr_output *output, int width, int height, + float refresh_rate) { + int mhz = (int)(refresh_rate * 1000); + + if (wl_list_empty(&output->modes)) { + wlr_log(WLR_DEBUG, "Assigning custom mode to %s", output->name); + wlr_output_set_custom_mode(output, width, height, + refresh_rate > 0 ? mhz : 0); + return; + } + + struct wlr_output_mode *mode, *best = NULL; + wl_list_for_each(mode, &output->modes, link) { + if (mode->width == width && mode->height == height) { + if (mode->refresh == mhz) { + best = mode; + break; + } + if (best == NULL || mode->refresh > best->refresh) { + best = mode; + } + } + } + if (!best) { + wlr_log(WLR_ERROR, "Configured mode for %s not available", output->name); + wlr_log(WLR_INFO, "Picking preferred mode instead"); + best = wlr_output_preferred_mode(output); + } else { + wlr_log(WLR_DEBUG, "Assigning configured mode to %s", output->name); + } + wlr_output_set_mode(output, best); +} + +void +output_configure(struct cg_server* server, struct cg_output *output) { + struct wlr_output *wlr_output = output->wlr_output; + struct cg_output_config *config = output_find_config(server, wlr_output); + if(output->wlr_output->enabled) { + wlr_output_layout_remove(server->output_layout, wlr_output); + } + + if(config == NULL) { + wlr_output_layout_add_auto(server->output_layout, wlr_output); + + struct wlr_output_mode *preferred_mode = + wlr_output_preferred_mode(wlr_output); + if(preferred_mode) { + wlr_output_set_mode(wlr_output, preferred_mode); + } + } else { + output_set_mode(wlr_output, config->pos.width, config->pos.height, config->refresh_rate); + wlr_output_layout_add(server->output_layout, wlr_output, config->pos.x, config->pos.y); + } +} + void handle_new_output(struct wl_listener *listener, void *data) { struct cg_server *server = wl_container_of(listener, server, new_output); @@ -508,7 +574,8 @@ handle_new_output(struct wl_listener *listener, void *data) { wl_signal_add(&output->damage->events.destroy, &output->damage_destroy); wlr_output_set_transform(wlr_output, server->output_transform); - wlr_output_layout_add_auto(server->output_layout, wlr_output); + + output_configure(server, output); output->workspaces = malloc(server->nws * sizeof(struct cg_workspace *)); for(unsigned int i = 0; i < server->nws; ++i) { @@ -520,12 +587,6 @@ handle_new_output(struct wl_listener *listener, void *data) { output->curr_workspace = 0; wl_list_init(&output->messages); - struct wlr_output_mode *preferred_mode = - wlr_output_preferred_mode(wlr_output); - if(preferred_mode) { - wlr_output_set_mode(wlr_output, preferred_mode); - } - if(wlr_xcursor_manager_load(server->seat->xcursor_manager, wlr_output->scale)) { wlr_log(WLR_ERROR, diff --git a/output.h b/output.h index ffca3e7..ba12ea6 100644 --- a/output.h +++ b/output.h @@ -2,13 +2,13 @@ #define CG_OUTPUT_H #include +#include struct cg_server; struct cg_view; struct wlr_output; struct wlr_output_damage; struct wlr_surface; -struct wlr_box; struct cg_output { struct cg_server *server; @@ -27,6 +27,13 @@ struct cg_output { struct wl_list link; // cg_server::outputs }; +struct cg_output_config { + struct wlr_box pos; + char* output_name; + float refresh_rate; + struct wl_list link;// cg_server::output_config +}; + typedef void (*cg_surface_iterator_func_t)(struct cg_output *output, struct wlr_surface *surface, struct wlr_box *box, @@ -35,6 +42,8 @@ typedef void (*cg_surface_iterator_func_t)(struct cg_output *output, void handle_new_output(struct wl_listener *listener, void *data); void +output_configure(struct cg_server* server, struct cg_output* output); +void output_surface_for_each_surface(struct cg_output *output, struct wlr_surface *surface, double ox, double oy, cg_surface_iterator_func_t iterator, diff --git a/parse.c b/parse.c index e8c7b15..0b6e864 100644 --- a/parse.c +++ b/parse.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "keybinding.h" #include "output.h" @@ -343,6 +344,104 @@ parse_definemode(char ***modes, char **saveptr) { return 0; } +int +parse_uint(char **saveptr, const char* delim) { + char *uint_str = strtok_r(NULL, delim, saveptr); + if(uint_str == NULL) { + wlr_log(WLR_ERROR, "Expected a non-negative integer, got nothing"); + return -1; + } + long uint = strtol(uint_str, NULL, 10); + if(uint >= 0 && uint <= INT_MAX) { + return uint; + } else { + wlr_log(WLR_ERROR, "Error parsing non-negative integer. Must be a number larger or equal to 0 and less or equal to %d",INT_MAX); + return -1; + } +} + +float +parse_float(char **saveptr, const char* delim) { + char *uint_str = strtok_r(NULL, delim, saveptr); + if(uint_str == NULL) { + wlr_log(WLR_ERROR, "Expected a non-negative float, got nothing"); + return -1; + } + float ufloat = strtof(uint_str, NULL); + if(ufloat >= 0) { + return ufloat; + } else { + wlr_log(WLR_ERROR, "Error parsing non-negative float. Must be a number larger or equal to 0"); + return -1; + } +} + +int +parse_output_config(struct wl_list *config_list, char **saveptr) { + struct cg_output_config* cfg = malloc(sizeof(struct cg_output_config)); + char *name = strtok_r(NULL, " ", saveptr); + if(name == NULL) { + wlr_log(WLR_ERROR, "Expected name of output to be configured, got none"); + goto error; + } + char *pos_str = strtok_r(NULL, " ", saveptr); + if(pos_str == NULL || strcmp(pos_str, "pos") != 0) { + wlr_log(WLR_ERROR, "Expected keyword \"pos\" in output configuration for output %s", name); + goto error; + } + + cfg->pos.x = parse_uint(saveptr, " "); + if(cfg->pos.x < 0) { + wlr_log(WLR_ERROR, "Error parsing x coordinate of output configuration for output %s", name); + goto error; + } + + cfg->pos.y = parse_uint(saveptr, " "); + if(cfg->pos.y < 0) { + wlr_log(WLR_ERROR, "Error parsing y coordinate of output configuration for output %s", name); + goto error; + } + + char *res_str = strtok_r(NULL, " ", saveptr); + if(res_str == NULL || strcmp(res_str, "res") != 0) { + wlr_log(WLR_ERROR, "Expected keyword \"res\" in output configuration for output %s", name); + goto error; + } + + cfg->pos.width = parse_uint(saveptr, "x"); + if(cfg->pos.width <= 0) { + wlr_log(WLR_ERROR, "Error parsing width of output configuration for output %s (hint: width must be larger than 0)", name); + goto error; + } + + cfg->pos.height = parse_uint(saveptr, " "); + if(cfg->pos.height <= 0) { + wlr_log(WLR_ERROR, "Error parsing height of output configuration for output %s (hint: height must e larger than 0)", name); + goto error; + } + + char *rate_str = strtok_r(NULL, " ", saveptr); + if(rate_str == NULL || strcmp(rate_str, "rate") != 0) { + wlr_log(WLR_ERROR, "Expected keyword \"rate\" in output configuration for output %s", name); + goto error; + } + + cfg->refresh_rate = parse_float(saveptr, " "); + if(cfg->refresh_rate <= 0.0) { + wlr_log(WLR_ERROR, "Error parsing refresh rate of output configuration for output %s", name); + goto error; + } + + cfg->output_name = strdup(name); + wl_list_insert(config_list, &cfg->link); + return 0; + +error: + free(cfg); + wlr_log(WLR_ERROR, "Output configuration must be of the form \"output pos res x rate "); + return -1; +} + int parse_rc_line(struct cg_server *server, char *line) { char *saveptr = NULL; // Used internally by strtok_r @@ -374,6 +473,10 @@ parse_rc_line(struct cg_server *server, char *line) { if(parse_and_run_exec(&saveptr) != 0) { return -1; } + } else if(strcmp(command, "output") == 0) { + if(parse_output_config(&server->output_config, &saveptr) != 0) { + return -1; + } } else if(strcmp(command, "workspaces") == 0) { char *nws_str = strtok_r(NULL, " ", &saveptr); if(nws_str == NULL) { diff --git a/server.h b/server.h index 92a9172..930cb56 100644 --- a/server.h +++ b/server.h @@ -11,6 +11,7 @@ struct cg_output; struct keybinding_list; struct wlr_output_layout; struct wlr_idle_inhibit_manager_v1; +struct cg_output_config; struct cg_server { struct wl_display *wl_display; @@ -35,6 +36,7 @@ struct cg_server { #endif struct keybinding_list *keybindings; + struct wl_list output_config; enum wl_output_transform output_transform;