From cc26ac29b92e2248c0beec1ce23efc56604de1fc Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Mon, 23 Sep 2024 19:17:12 +0100 Subject: [PATCH] theme: finish titleLayout implementation (#2150) Change Openbox style WLIMC syntax to `menu:iconify,max,close` as was agreed when PR #2088 was merged. menu:iconify,max,close yes|on Ref: - https://github.com/labwc/labwc/pull/2088#issuecomment-2295730704 --- docs/labwc-config.5.scd | 27 ++++--- docs/rc.xml.all | 5 +- src/config/rcxml.c | 160 ++++++++++++++++++++++++++++++---------- 3 files changed, 140 insertions(+), 52 deletions(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index e2c84116..18a3efea 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -437,19 +437,24 @@ extending outward from the snapped edge. ** The name of the Openbox theme to use. It is not set by default. -** +** Selection and order of buttons in a window's titlebar. - The following letters can be used, each only once: - - L: window label (aka. title) - - |: empty space (can be used instead of a title) - - W: window menu - - I: iconify - - M: maximize toggle - - C: close - - S: shade toggle - - D: all-desktops toggle + The following identifiers can be used, each only once: + - 'menu': window menu + - 'iconify': iconify + - 'max': maximize toggle + - 'close': close + - 'shade': shade toggle + - 'desk': all-desktops toggle - Example: WLIMC + A colon deliminator is used to separate buttons on the left and right, + whereas commas are used to separate items within a section. It is + mandatory to use one colon. + + Default: menu:iconify,max,close + +** [yes|no] + Show the window title in the titlebar. Default is yes. ** The radius of server side decoration top corners. Default is 8. diff --git a/docs/rc.xml.all b/docs/rc.xml.all index b76db661..b14ab6b0 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -28,7 +28,10 @@ - WLIMC + + menu:iconify,max,close + yes + 8 yes no diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 007ae160..e71c14a8 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -119,61 +119,139 @@ parse_window_type(const char *type) } } +/* + * Openbox/labwc comparison + * + * Instead of openbox's WLIMC we use + * + * + * menu:iconfiy,max,close + * yes|no + * + * + * ...using the icon names (like iconify.xbm) without the file extension for the + * identifier. + * + * labwc openbox description + * ----- ------- ----------- + * menu W Open window menu (client-menu) + * iconfiy I Iconify (aka minimize) + * max M Maximize toggle + * close C Close + * shade S Shade toggle + * desk D All-desktops toggle (aka omnipresent) + */ static void -fill_title_layout(char *nodename, char *content) +fill_section(const char *content, struct wl_list *list) { - char *c, *c2; - enum ssd_part_type type; - struct title_button *item; - struct wl_list *list = &rc.title_buttons_left; - - for (c = content; *c != '\0'; c++) { - for (c2 = content; c2 < c; c2++) { - if (*c2 == *c) { - break; - } - } - if (c2 != c) { + gchar **identifiers = g_strsplit(content, ",", -1); + for (size_t i = 0; identifiers[i]; ++i) { + char *identifier = identifiers[i]; + if (string_null_or_empty(identifier)) { continue; } - - switch (*c) { - /* case 'N': icon */ - case 'L': - list = &rc.title_buttons_right; - rc.show_title = true; - continue; - case '|': - list = &rc.title_buttons_right; - continue; - case 'W': + enum ssd_part_type type = LAB_SSD_NONE; + if (!strcmp(identifier, "menu")) { type = LAB_SSD_BUTTON_WINDOW_MENU; - break; - case 'I': + } else if (!strcmp(identifier, "iconify")) { type = LAB_SSD_BUTTON_ICONIFY; - break; - case 'M': + } else if (!strcmp(identifier, "max")) { type = LAB_SSD_BUTTON_MAXIMIZE; - break; - case 'C': + } else if (!strcmp(identifier, "close")) { type = LAB_SSD_BUTTON_CLOSE; - break; - case 'S': + } else if (!strcmp(identifier, "shade")) { type = LAB_SSD_BUTTON_SHADE; - break; - case 'D': + } else if (!strcmp(identifier, "desk")) { type = LAB_SSD_BUTTON_OMNIPRESENT; - break; - default: + } else { + wlr_log(WLR_ERROR, "invalid titleLayout identifier '%s'", + identifier); continue; } - item = znew(*item); + assert(type != LAB_SSD_NONE); + + struct title_button *item = znew(*item); item->type = type; wl_list_append(list, &item->link); } + g_strfreev(identifiers); +} + +static int +compare_strings(const void *a, const void *b) +{ + char * const *str1 = a; + char * const *str2 = b; + return strcmp(*str1, *str2); +} + +static bool +contains_duplicates(char *content) +{ + bool ret = false; + + /* + * The string typically looks like: 'menu:iconfiy,max,close' so we have + * to split on both ':' and ','. + */ + gchar **idents = g_strsplit_set(content, ",:", -1); + + /* + * We've got to have at least two for duplicates to exist. Bailing out + * early here also enables the below algorithm which just iterates and + * checks if previous item is the same. + */ + if (g_strv_length(idents) <= 1) { + goto out; + } + + qsort(idents, g_strv_length(idents), sizeof(gchar *), compare_strings); + for (size_t i = 1; idents[i]; ++i) { + if (string_null_or_empty(idents[i])) { + continue; + } + if (!strcmp(idents[i], idents[i-1])) { + ret = true; + wlr_log(WLR_ERROR, + "titleLayout identifier '%s' is a duplicate", + idents[i]); + break; + } + } + +out: + g_strfreev(idents); + return ret; +} + +static void +fill_title_layout(char *content) +{ + if (contains_duplicates(content)) { + wlr_log(WLR_ERROR, "titleLayout contains duplicates"); + return; + } + + struct wl_list *sections[] = { + &rc.title_buttons_left, + &rc.title_buttons_right, + }; + + gchar **parts = g_strsplit(content, ":", -1); + + if (g_strv_length(parts) != 2) { + wlr_log(WLR_ERROR, " must contain one colon"); + goto err; + } + + for (size_t i = 0; parts[i]; ++i) { + fill_section(parts[i], sections[i]); + } rc.title_layout_loaded = true; +err: + g_strfreev(parts); } static void @@ -989,8 +1067,10 @@ entry(xmlNode *node, char *nodename, char *content) rc.placement_cascade_offset_y = atoi(content); } else if (!strcmp(nodename, "name.theme")) { rc.theme_name = xstrdup(content); - } else if (!strcmp(nodename, "titlelayout.theme")) { - fill_title_layout(nodename, content); + } else if (!strcasecmp(nodename, "layout.titlebar.theme")) { + fill_title_layout(content); + } else if (!strcasecmp(nodename, "showTitle.titlebar.theme")) { + rc.show_title = parse_bool(content, true); } else if (!strcmp(nodename, "cornerradius.theme")) { rc.corner_radius = atoi(content); } else if (!strcasecmp(nodename, "keepBorder.theme")) { @@ -1578,7 +1658,7 @@ post_processing(void) } if (!rc.title_layout_loaded) { - fill_title_layout("titlelayout.theme", "WLIMC"); + fill_title_layout("menu:iconify,max,close"); } /*