From 7e85df21f6300f383f6453dd2f6e5c54fc8a9b21 Mon Sep 17 00:00:00 2001 From: Euwiiwueir Date: Sat, 22 Jun 2024 19:22:58 -0400 Subject: [PATCH 1/2] Factor out a function to parse color from string Also add some minimum effort error checking/correction as this will soon be processing a user-entered value. The extant code path should not be affected as input color values are correctly specified. Color values can be words or hex values. Accepted: - 'black' - 'white' - 'blue' - 'dark blue' - '0x0000ff' Rejected with logged error + exit: - 'nonsense color' - '0xZZZZZZ' Related to: QubesOS/qubes-issues#9304 --- gui-daemon/xside.c | 62 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/gui-daemon/xside.c b/gui-daemon/xside.c index 6fe3471..0a4d8bb 100644 --- a/gui-daemon/xside.c +++ b/gui-daemon/xside.c @@ -283,27 +283,59 @@ static int x11_io_error_handler(Display * dpy) exit(1); } +/* + * Infer a color value from the provided string using strtoul for + * hex-like input or for a name Xlib by way of /etc/X11/rgb.txt. + */ +static XColor parse_color(const char *str, Display *dpy, int screen) +{ + XColor xcolor; + Status status; + char *trimmed = (char *) str; + while (trimmed[0] == ' ') { /* skip any leading spaces */ + trimmed++; + } + if (trimmed[0] == '0' && (trimmed[1] == 'x' || trimmed[1] == 'X')) { + char *endptr; + unsigned int rgb; + errno = 0; + rgb = strtoul(trimmed, &endptr, 16); + if (errno) { + perror("strtoul"); + fprintf(stderr, "Failed to parse color '%s'\n", trimmed); + exit(1); + } else if (endptr == trimmed || + /* Note: this check incorrectly rejects the + specific case of trailing space on hex black, + i.e. '0x000000 ' */ + (endptr[0] != '\0' && rgb == 0)) { + fprintf(stderr, "Failed to parse color '%s'\n", trimmed); + exit(1); + } + xcolor.blue = (rgb & 0xff) * 257; + rgb >>= 8; + xcolor.green = (rgb & 0xff) * 257; + rgb >>= 8; + xcolor.red = (rgb & 0xff) * 257; + status = XAllocColor(dpy, XDefaultColormap(dpy, screen), &xcolor); + } else { + XColor dummy; + status = XAllocNamedColor(dpy, XDefaultColormap(dpy, screen), trimmed, + &xcolor, &dummy); + } + if (status == 0) { + fprintf(stderr, "Failed to allocate color when parsing '%s'\n", trimmed); + exit(1); + } + return xcolor; +} /* prepare graphic context for painting colorful frame and set RGB value of the * color */ static void get_frame_gc(Ghandles * g, const char *name) { XGCValues values; - XColor fcolor, dummy; - if (name[0] == '0' && (name[1] == 'x' || name[1] == 'X')) { - unsigned int rgb = strtoul(name, 0, 16); - fcolor.blue = (rgb & 0xff) * 257; - rgb >>= 8; - fcolor.green = (rgb & 0xff) * 257; - rgb >>= 8; - fcolor.red = (rgb & 0xff) * 257; - XAllocColor(g->display, - XDefaultColormap(g->display, g->screen), - &fcolor); - } else - XAllocNamedColor(g->display, - XDefaultColormap(g->display, g->screen), - name, &fcolor, &dummy); + XColor fcolor = parse_color(name, g->display, g->screen); g->label_color_rgb = (fcolor.red >> 8) << 16 | (fcolor.green >> 8) << 8 | From 51399c372c40d2b00052871105197be64e7c01d7 Mon Sep 17 00:00:00 2001 From: Euwiiwueir Date: Sat, 22 Jun 2024 19:24:04 -0400 Subject: [PATCH 2/2] qubes-guid: add window_background_color setting This configuration setting allows changing the background pixel for local windows that display content of VM windows. Generally the color is only visible rarely and briefly. Previous to this commit the color was hardcoded to white which is suboptimal for users running dark desktop styling. This is the downstream translation of these VM features: - gui-window-background-color - gui-default-window-background-color Testing process: [user@dom0 ~]$ qvm-features dom0 gui-default-window-background-color [user@dom0 ~]$ qvm-run --dispvm=default-dvm --service qubes.StartApp+debian-xterm # (Then play with the terminal's window to expose the color) Related to: QubesOS/qubes-issues#9304 --- gui-daemon/guid.conf | 7 +++++++ gui-daemon/xside.c | 13 +++++++++++-- gui-daemon/xside.h | 2 ++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/gui-daemon/guid.conf b/gui-daemon/guid.conf index 7f2dff0..3f1530a 100644 --- a/gui-daemon/guid.conf +++ b/gui-daemon/guid.conf @@ -70,6 +70,13 @@ global: { # # trayicon_mode = "border1"; + # Set the fill color to be shown in a window when content is pending or + # unavailable. This is rarely visible except very briefly. Possible values are + # a color name (see: /etc/X11/rgb.txt) or a specification in format 0xRRGGBB. + # When running a dark-styled desktop theme, "black" is recommended. + # + # window_background_color = "white"; + # Timeout when waiting for qubes-gui-agent # # startup_timeout = 45; diff --git a/gui-daemon/xside.c b/gui-daemon/xside.c index 0a4d8bb..947596c 100644 --- a/gui-daemon/xside.c +++ b/gui-daemon/xside.c @@ -361,7 +361,7 @@ static Window mkwindow(Ghandles * g, struct windowdata *vm_window) my_size_hints.height = vm_window->height; attr.override_redirect = vm_window->override_redirect; - attr.background_pixel = WhitePixel(g->display, DefaultScreen(g->display)); + attr.background_pixel = g->window_background_pixel; child_win = XCreateWindow(g->display, g->root_win, vm_window->x, vm_window->y, vm_window->width, @@ -690,6 +690,9 @@ static void mkghandles(Ghandles * g) else if (g->trayicon_mode == TRAY_TINT) init_tray_tint(g); /* nothing extra needed for TRAY_BORDER */ + /* parse window background color */ + g->window_background_pixel = parse_color(g->window_background_color_pre_parse, + g->display, g->screen).pixel; /* parse -p arguments now, as we have X server connection */ parse_cmdline_prop(g); /* init window lists */ @@ -3791,7 +3794,7 @@ static void usage(FILE *stream) fprintf(stream, " --domid=ID, -d ID\tdomain ID running GUI agent\n"); fprintf(stream, " --target-domid=ID, -t ID\tdomain ID of actual VM (may be different from --domid in case of stubdomain)\n"); fprintf(stream, " --name=NAME, -N NAME\tVM name\n"); - fprintf(stream, " --color=COLOR, -c COLOR\tVM color (in format 0xRRGGBB)\n"); + fprintf(stream, " --color=COLOR, -c COLOR\tVM color (format 0xRRGGBB or color name)\n"); fprintf(stream, " --label=LABEL_INDEX, -l LABEL_INDEX\tVM label index\n"); fprintf(stream, " --icon=ICON, -i ICON\tIcon name (without suffix), or full icon path\n"); fprintf(stream, " --qrexec-for-clipboard, -Q\tforce usage of Qrexec for clipboard operations\n"); @@ -4123,6 +4126,7 @@ static void load_default_config_values(Ghandles * g) g->trayicon_border = 0; g->trayicon_tint_reduce_saturation = 0; g->trayicon_tint_whitehack = 0; + g->window_background_color_pre_parse = "white"; } // parse string describing key sequence like Ctrl-Alt-c @@ -4211,6 +4215,11 @@ static void parse_vm_config(Ghandles * g, config_setting_t * group) parse_trayicon_mode(g, config_setting_get_string(setting)); } + if ((setting = + config_setting_get_member(group, "window_background_color"))) { + g->window_background_color_pre_parse = config_setting_get_string(setting); + } + if ((setting = config_setting_get_member(group, "startup_timeout"))) { g->startup_timeout = config_setting_get_int(setting); diff --git a/gui-daemon/xside.h b/gui-daemon/xside.h index 8ca478e..f799c86 100644 --- a/gui-daemon/xside.h +++ b/gui-daemon/xside.h @@ -225,6 +225,8 @@ struct _global_handles { int trayicon_border; /* position of trayicon border - 0 - no border, 1 - at the edges, 2 - 1px from the edges */ bool trayicon_tint_reduce_saturation; /* reduce trayicon saturation by 50% (available only for "tint" mode) */ bool trayicon_tint_whitehack; /* replace white pixels with almost-white 0xfefefe (available only for "tint" mode) */ + const char *window_background_color_pre_parse; /* user-provided description of window background pixel */ + unsigned long window_background_pixel; /* parsed version of the above */ bool disable_override_redirect; /* Disable “override redirect” windows */ char *screensaver_names[MAX_SCREENSAVER_NAMES]; /* WM_CLASS names for windows detected as screensavers */ Cursor *cursors; /* preloaded cursors (using XCreateFontCursor) */