]> git.mdlowis.com Git - proto/labwc.git/commitdiff
osd: add window-switcher custom field (#1670)
authordroc12345 <80716141+droc12345@users.noreply.github.com>
Wed, 10 Apr 2024 22:39:31 +0000 (17:39 -0500)
committerGitHub <noreply@github.com>
Wed, 10 Apr 2024 22:39:31 +0000 (23:39 +0100)
Add custom field with subset of printf style formatting
to replace the original field formats.

Example:

    <windowSwitcher preview="no" outlines="no" allWorkspaces="yes">
      <fields>
        <field content="custom" format="foobar %b %3s %-10o %-20W %-10i%t" width="100%" />
      </fields>
    </windowSwitcher>

Mono space font recommended. May need OSD width adjusted

Co-authored-by: @Consolatis (based on work done by them)
13 files changed:
docs/labwc-config.5.scd
docs/rc.xml.all
include/config/rcxml.h
include/labwc.h
include/osd.h [new file with mode: 0644]
src/action.c
src/config/rcxml.c
src/desktop.c
src/input/keyboard.c
src/meson.build
src/osd.c
src/osd_field.c [new file with mode: 0644]
src/view.c

index 79ec48937140c53ca9a48e6c966210cae1d4565a..e69608828347ecc3699609d59807bfbb94276af9 100644 (file)
@@ -243,6 +243,30 @@ this is for compatibility with Openbox.
 
                - *output* Show output id, if more than one output detected
 
+               - *custom* A printf style config that can replace all the above
+                 fields are:
+                       - 'B' - shell type, values [xwayland|xdg-shell]
+                       - 'b' - shell type (short form), values [X|W]
+                       - 'S' - state of window, values [M|m|F] (3 spaces allocated)
+                               (maximized, minimized, fullscreen)
+                       - 's' - state of window (short form), values [M|m|F] (1 space)
+                       - 'I' - wm-class/app-id
+                       - 'i' - wm-class/app-id trimmed, remove "org." if available
+                       - 'W' - workspace name
+                       - 'w' - workspace name (if more than 1 ws configured)
+                       - 'O' - output name
+                       - 'o' - output name (show if more than 1 monitor active)
+                       - 'T' - title of window
+                       - 't' - title of window (if different than wm-class/app-id)
+                 Recommend using with a mono space font, to keep alignment.
+               - *custom - subset of printf options allowed -- man 3 printf*
+                       - random text may be inserted
+                       - field length, example "%10" use 10 spaces, even if text uses less
+                       - left jusify text, example "%-"
+                       - right justify text, example "%" instead of "%-"
+                       - example, %-10 would left justify and make room for 10 charaters
+               - Only one custom format allowed now. Future enhancements may allow more than one.
+
        *width* defines the width of the field expressed as a percentage of
        the overall window switcher width. The "%" character is required.
 
index 1f56632c3f756f1fc1eee2b76d61b3e9b47b03d0..5fdcd3468e1f4f7c7e376d36500dc9391aa83908 100644 (file)
         <field content="output" width="9%" />
         <field content="identifier" width="30%" />
         <field content="title" width="50%" />
-       </fields>
+      </fields>
+    </windowSwitcher>
+
+    custom format - (introduced in 0.7.2)
+    It allows one to replace all the above "fields" with one line, using a
+    printf style format. For field explanations, "man 5 labwc-config".
+
+    The example below would print "foobar",then type of window (wayland, X),
+    then state of window (M/m/F), then output (shows if more than 1 active),
+    then workspace name, then identifier/app-id, then the window title.
+    Uses 100% of OSD window width.
+
+    <windowSwitcher show="yes" preview="no" outlines="no" allWorkspaces="yes">
+      <fields>
+        <field content="custom" format="foobar %b %3s %-10o %-20W %-10i %t" width="100%" />
+      </fields>
     </windowSwitcher>
   -->
 
index 0a4f10a71a016018587929bd30433833294742f6..ff8b81368b57bcfbe183d2c6c6d514e090f31681 100644 (file)
 #include "resize_indicator.h"
 #include "theme.h"
 
-enum window_switcher_field_content {
-       LAB_FIELD_NONE = 0,
-       LAB_FIELD_TYPE,
-       LAB_FIELD_IDENTIFIER,
-       LAB_FIELD_TRIMMED_IDENTIFIER,
-       LAB_FIELD_TITLE,
-       LAB_FIELD_WORKSPACE,
-       LAB_FIELD_WIN_STATE,
-       LAB_FIELD_TYPE_SHORT,
-       LAB_FIELD_OUTPUT,
-};
-
 enum view_placement_policy {
        LAB_PLACE_CENTER = 0,
        LAB_PLACE_CURSOR,
@@ -53,12 +41,6 @@ struct usable_area_override {
        struct wl_list link; /* struct rcxml.usable_area_overrides */
 };
 
-struct window_switcher_field {
-       enum window_switcher_field_content content;
-       int width;
-       struct wl_list link; /* struct rcxml.window_switcher.fields */
-};
-
 struct rcxml {
        /* from command line */
        char *config_dir;
index b60d7647ac74ca1bf54dfc233bc41099f8bd5290..482ce35fa60c03a786525e440042d5a83557cbc7 100644 (file)
@@ -502,15 +502,6 @@ void server_init(struct server *server);
 void server_start(struct server *server);
 void server_finish(struct server *server);
 
-/* Updates onscreen display 'alt-tab' buffer */
-void osd_update(struct server *server);
-/* Closes the OSD */
-void osd_finish(struct server *server);
-/* Moves preview views back into their original stacking order and state */
-void osd_preview_restore(struct server *server);
-/* Notify OSD about a destroying view */
-void osd_on_view_destroy(struct view *view);
-
 /*
  * wlroots "input inhibitor" extension (required for swaylock) blocks
  * any client other than the requesting client from receiving events
diff --git a/include/osd.h b/include/osd.h
new file mode 100644 (file)
index 0000000..4fa0fa3
--- /dev/null
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef LABWC_OSD_H
+#define LABWC_OSD_H
+
+#include <stdbool.h>
+#include <wayland-server-core.h>
+
+/* TODO: add field with keyboard layout? */
+enum window_switcher_field_content {
+       LAB_FIELD_NONE = 0,
+       LAB_FIELD_TYPE,
+       LAB_FIELD_TYPE_SHORT,
+       LAB_FIELD_IDENTIFIER,
+       LAB_FIELD_TRIMMED_IDENTIFIER,
+       LAB_FIELD_TITLE,
+       LAB_FIELD_TITLE_SHORT,
+       LAB_FIELD_WORKSPACE,
+       LAB_FIELD_WORKSPACE_SHORT,
+       LAB_FIELD_WIN_STATE,
+       LAB_FIELD_WIN_STATE_ALL,
+       LAB_FIELD_OUTPUT,
+       LAB_FIELD_OUTPUT_SHORT,
+       LAB_FIELD_CUSTOM,
+
+       LAB_FIELD_COUNT
+};
+
+struct window_switcher_field {
+       enum window_switcher_field_content content;
+       int width;
+       char *format;
+       struct wl_list link; /* struct rcxml.window_switcher.fields */
+};
+
+struct buf;
+struct view;
+struct server;
+
+/* Updates onscreen display 'alt-tab' buffer */
+void osd_update(struct server *server);
+
+/* Closes the OSD */
+void osd_finish(struct server *server);
+
+/* Moves preview views back into their original stacking order and state */
+void osd_preview_restore(struct server *server);
+
+/* Notify OSD about a destroying view */
+void osd_on_view_destroy(struct view *view);
+
+/* Used by osd.c internally to render window switcher fields */
+void osd_field_get_content(struct window_switcher_field *field,
+       struct buf *buf, struct view *view);
+
+/* Used by rcxml.c when parsing the config */
+struct window_switcher_field *osd_field_create(void);
+void osd_field_arg_from_xml_node(struct window_switcher_field *field,
+       const char *nodename, const char *content);
+bool osd_field_validate(struct window_switcher_field *field);
+void osd_field_free(struct window_switcher_field *field);
+
+#endif // LABWC_OSD_H
index 61e28f27a54131eed73031ccc81daad5b9ad7c9a..e268c3d352ecd7b228e6698554589827b889f81e 100644 (file)
@@ -16,6 +16,7 @@
 #include "debug.h"
 #include "labwc.h"
 #include "menu/menu.h"
+#include "osd.h"
 #include "output-virtual.h"
 #include "placement.h"
 #include "regions.h"
index 5e5ce68b42e84ccbfcca0657dd80d4463000b4cc..2b594a08975fc9ae62d9ebe8fce49f720f6a48d8 100644 (file)
@@ -30,6 +30,7 @@
 #include "config/tablet.h"
 #include "config/rcxml.h"
 #include "labwc.h"
+#include "osd.h"
 #include "regions.h"
 #include "view.h"
 #include "window-rules.h"
@@ -187,7 +188,7 @@ static void
 fill_window_switcher_field(char *nodename, char *content)
 {
        if (!strcasecmp(nodename, "field.fields.windowswitcher")) {
-               current_field = znew(*current_field);
+               current_field = osd_field_create();
                wl_list_append(&rc.window_switcher.fields, &current_field->link);
                return;
        }
@@ -197,39 +198,8 @@ fill_window_switcher_field(char *nodename, char *content)
                /* intentionally left empty */
        } else if (!current_field) {
                wlr_log(WLR_ERROR, "no <field>");
-       } else if (!strcmp(nodename, "content")) {
-               if (!strcmp(content, "type")) {
-                       current_field->content = LAB_FIELD_TYPE;
-               } else if (!strcmp(content, "identifier")) {
-                       current_field->content = LAB_FIELD_IDENTIFIER;
-               } else if (!strcmp(content, "app_id")) {
-                       wlr_log(WLR_ERROR, "window-switcher field 'app_id' is deprecated");
-                       current_field->content = LAB_FIELD_IDENTIFIER;
-               } else if (!strcmp(content, "trimmed_identifier")) {
-                       current_field->content = LAB_FIELD_TRIMMED_IDENTIFIER;
-               } else if (!strcmp(content, "title")) {
-                       current_field->content = LAB_FIELD_TITLE;
-               } else if (!strcmp(content, "workspace")) {
-                       current_field->content = LAB_FIELD_WORKSPACE;
-               } else if (!strcmp(content, "state")) {
-                       current_field->content = LAB_FIELD_WIN_STATE;
-               } else if (!strcmp(content, "type_short")) {
-                       current_field->content = LAB_FIELD_TYPE_SHORT;
-               } else if (!strcmp(content, "output")) {
-                       current_field->content = LAB_FIELD_OUTPUT;
-               } else {
-                       wlr_log(WLR_ERROR, "bad windowSwitcher field '%s'", content);
-               }
-       } else if (!strcmp(nodename, "width") && !strchr(content, '%')) {
-               wlr_log(WLR_ERROR, "Removing invalid field, %s='%s' misses"
-                       " trailing %%", nodename, content);
-               wl_list_remove(&current_field->link);
-               zfree(current_field);
-       } else if (!strcmp(nodename, "width")) {
-               current_field->width = atoi(content);
        } else {
-               wlr_log(WLR_ERROR, "Unexpected data in field parser: %s=\"%s\"",
-                       nodename, content);
+               osd_field_arg_from_xml_node(current_field, nodename, content);
        }
 }
 
@@ -1516,6 +1486,16 @@ validate(void)
        }
 
        validate_actions();
+
+       /* OSD fields */
+       struct window_switcher_field *field, *field_tmp;
+       wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.fields, link) {
+               if (!osd_field_validate(field)) {
+                       wlr_log(WLR_ERROR, "Deleting invalid window switcher field %p", field);
+                       wl_list_remove(&field->link);
+                       osd_field_free(field);
+               }
+       }
 }
 
 void
@@ -1645,7 +1625,7 @@ rcxml_finish(void)
        struct window_switcher_field *field, *field_tmp;
        wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.fields, link) {
                wl_list_remove(&field->link);
-               zfree(field);
+               osd_field_free(field);
        }
 
        struct window_rule *rule, *rule_tmp;
index 4141e974135b52ce7625576469b37f2d204897b8..e3feaa3793486d997a3ab46cf5ec345bb1e0bf9f 100644 (file)
@@ -7,6 +7,7 @@
 #include "labwc.h"
 #include "layers.h"
 #include "node.h"
+#include "osd.h"
 #include "ssd.h"
 #include "view.h"
 #include "window-rules.h"
index 415edffde1e5baa39c7632fef9d660d8ce31897e..d0db6cb139c3646fc8853dff988971b6657fe882 100644 (file)
@@ -11,6 +11,7 @@
 #include "input/key-state.h"
 #include "labwc.h"
 #include "menu/menu.h"
+#include "osd.h"
 #include "regions.h"
 #include "view.h"
 #include "workspaces.h"
index f3ff141237b91a8035b5c333628c39d37695a2dd..a7450d9169c691b67038a77639a2e39d285dc50e 100644 (file)
@@ -12,6 +12,7 @@ labwc_sources = files(
   'main.c',
   'node.c',
   'osd.c',
+  'osd_field.c',
   'output.c',
   'output-virtual.c',
   'overlay.c',
index b41b1bf1d91e4822c691d0167bd86dedc98a488e..caefe66d57802e3045a78cec85a16e06e86e1e3a 100644 (file)
--- a/src/osd.c
+++ b/src/osd.c
 #include "common/scene-helpers.h"
 #include "config/rcxml.h"
 #include "labwc.h"
-#include "theme.h"
 #include "node.h"
+#include "osd.h"
+#include "theme.h"
 #include "view.h"
 #include "window-rules.h"
 #include "workspaces.h"
 
-static const char *
-get_formatted_app_id(struct view *view)
-{
-       char *s = (char *)view_get_string_prop(view, "app_id");
-       if (!s) {
-               return NULL;
-       }
-       return s;
-}
-
-static const char *
-get_trimmed_app_id(char *s)
-{
-       if (!s) {
-               return NULL;
-       }
-       /* remove the first two nodes of 'org.' strings */
-       if (!strncmp(s, "org.", 4)) {
-               char *p = s + 4;
-               p = strchr(p, '.');
-               if (p) {
-                       return ++p;
-               }
-       }
-       return s;
-}
-
 static void
 destroy_osd_nodes(struct output *output)
 {
@@ -225,63 +199,6 @@ preview_cycled_view(struct view *view)
        wlr_scene_node_raise_to_top(osd_state->preview_node);
 }
 
-static const char *
-get_type(struct view *view)
-{
-       switch (view->type) {
-       case LAB_XDG_SHELL_VIEW:
-               return "[xdg-shell]";
-#if HAVE_XWAYLAND
-       case LAB_XWAYLAND_VIEW:
-               return "[xwayland]";
-#endif
-       }
-       return "";
-}
-
-static const char *
-get_type_short(struct view *view)
-{
-       switch (view->type) {
-       case LAB_XDG_SHELL_VIEW:
-               return "[W]";
-#if HAVE_XWAYLAND
-       case LAB_XWAYLAND_VIEW:
-               return "[X]";
-#endif
-       }
-       return "";
-}
-
-static const char *
-get_app_id(struct view *view)
-{
-       switch (view->type) {
-       case LAB_XDG_SHELL_VIEW:
-               return get_formatted_app_id(view);
-#if HAVE_XWAYLAND
-       case LAB_XWAYLAND_VIEW:
-               return view_get_string_prop(view, "class");
-#endif
-       }
-       return "";
-}
-
-static const char *
-get_title_if_different(struct view *view)
-{
-       /*
-        * XWayland clients return WM_CLASS for 'app_id' so we don't need a
-        * special case for that here.
-        */
-       const char *identifier = view_get_string_prop(view, "app_id");
-       const char *title = view_get_string_prop(view, "title");
-       if (!identifier) {
-               return title;
-       }
-       return (!title || !strcmp(identifier, title)) ? NULL : title;
-}
-
 static void
 render_osd(struct server *server, cairo_t *cairo, int w, int h,
                bool show_workspace, const char *workspace_name,
@@ -374,50 +291,8 @@ render_osd(struct server *server, cairo_t *cairo, int w, int h,
                                + theme->osd_window_switcher_item_padding_y
                                + theme->osd_window_switcher_item_active_border_width);
 
-                       switch (field->content) {
-                       case LAB_FIELD_TYPE:
-                               buf_add(&buf, get_type(*view));
-                               break;
-                       case LAB_FIELD_TYPE_SHORT:
-                               buf_add(&buf, get_type_short(*view));
-                               break;
-                       case LAB_FIELD_WORKSPACE:
-                               buf_add(&buf, (*view)->workspace->name);
-                               break;
-                       case LAB_FIELD_WIN_STATE:
-                               if ((*view)->maximized) {
-                                       buf_add(&buf, "M");
-                               } else if ((*view)->minimized) {
-                                       buf_add(&buf, "m");
-                               } else if ((*view)->fullscreen) {
-                                       buf_add(&buf, "F");
-                               } else {
-                                       buf_add(&buf, " ");
-                               }
-                               break;
-                       case LAB_FIELD_OUTPUT:
-                               if (wl_list_length(&server->outputs) > 1 &&
-                                               output_is_usable((*view)->output)) {
-                                       buf_add(&buf, (*view)->output->wlr_output->name);
-                               } else {
-                                       buf_add(&buf, " ");
-                               }
-                               break;
-                       case LAB_FIELD_IDENTIFIER:
-                               buf_add(&buf, get_app_id(*view));
-                               break;
-                       case LAB_FIELD_TRIMMED_IDENTIFIER:
-                               {
-                                       char *s = (char *)get_app_id(*view);
-                                       buf_add(&buf, get_trimmed_app_id(s));
-                                       break;
-                               }
-                       case LAB_FIELD_TITLE:
-                               buf_add(&buf, get_title_if_different(*view));
-                               break;
-                       default:
-                               break;
-                       }
+                       osd_field_get_content(field, &buf, *view);
+
                        int field_width = (available_width - (nr_fields + 1)
                                * theme->osd_window_switcher_item_padding_x)
                                * field->width / 100.0;
diff --git a/src/osd_field.c b/src/osd_field.c
new file mode 100644 (file)
index 0000000..a804a18
--- /dev/null
@@ -0,0 +1,365 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <assert.h>
+#include <ctype.h>
+#include <wlr/util/log.h>
+#include "common/mem.h"
+#include "config/rcxml.h"
+#include "view.h"
+#include "workspaces.h"
+#include "labwc.h"
+#include "osd.h"
+
+/* includes '%', terminating 's' and NULL byte, 8 is enough for %-9999s */
+#define LAB_FIELD_SINGLE_FMT_MAX_LEN 8
+static_assert(LAB_FIELD_SINGLE_FMT_MAX_LEN <= 255, "fmt_position is a unsigned char");
+
+/* forward declares */
+typedef void field_conversion_type(struct buf *buf, struct view *view, const char *format);
+struct field_converter {
+       const char fmt_char;
+       field_conversion_type *fn;
+};
+
+static const struct field_converter field_converter[];
+
+/* Internal helpers */
+
+static const char *
+get_app_id_or_class(struct view *view, bool trim)
+{
+       /*
+        * XWayland clients return WM_CLASS for 'app_id' so we don't need a
+        * special case for that here.
+        */
+       const char *identifier = view_get_string_prop(view, "app_id");
+
+       /* remove the first two nodes of 'org.' strings */
+       if (trim && identifier && !strncmp(identifier, "org.", 4)) {
+               char *p = (char *)identifier + 4;
+               p = strchr(p, '.');
+               if (p) {
+                       return ++p;
+               }
+       }
+       return identifier;
+}
+
+static const char *
+get_type(struct view *view, bool short_form)
+{
+       switch (view->type) {
+       case LAB_XDG_SHELL_VIEW:
+               return short_form ? "[W]" : "[xdg-shell]";
+#if HAVE_XWAYLAND
+       case LAB_XWAYLAND_VIEW:
+               return short_form ? "[X]" : "[xwayland]";
+#endif
+       }
+       return "???";
+}
+
+static const char *
+get_title(struct view *view)
+{
+       return view_get_string_prop(view, "title");
+}
+
+static const char *
+get_title_if_different(struct view *view)
+{
+       const char *identifier = get_app_id_or_class(view, /*trim*/ false);
+       const char *title = get_title(view);
+       if (!identifier) {
+               return title;
+       }
+       return (!title || !strcmp(identifier, title)) ? NULL : title;
+}
+
+/* Field handlers */
+static void
+field_set_type(struct buf *buf, struct view *view, const char *format)
+{
+       /* custom type conversion-specifier: B (backend) */
+       buf_add(buf, get_type(view, /*short_form*/ false));
+}
+
+static void
+field_set_type_short(struct buf *buf, struct view *view, const char *format)
+{
+       /* custom type conversion-specifier: b (backend) */
+       buf_add(buf, get_type(view, /*short_form*/ true));
+}
+
+static void
+field_set_workspace(struct buf *buf, struct view *view, const char *format)
+{
+       /* custom type conversion-specifier: W */
+       buf_add(buf, view->workspace->name);
+}
+
+static void
+field_set_workspace_short(struct buf *buf, struct view *view, const char *format)
+{
+       /* custom type conversion-specifier: w */
+       if (wl_list_length(&rc.workspace_config.workspaces) > 1) {
+               buf_add(buf, view->workspace->name);
+       }
+}
+
+static void
+field_set_win_state(struct buf *buf, struct view *view, const char *format)
+{
+       /* custom type conversion-specifier: s */
+       if (view->maximized) {
+               buf_add(buf, "M");
+       } else if (view->minimized) {
+               buf_add(buf, "m");
+       } else if (view->fullscreen) {
+               buf_add(buf, "F");
+       } else {
+               buf_add(buf, " ");
+       }
+}
+
+static void
+field_set_win_state_all(struct buf *buf, struct view *view, const char *format)
+{
+       /* custom type conversion-specifier: S */
+       buf_add(buf, view->minimized ? "m" : " ");
+       buf_add(buf, view->maximized ? "M" : " ");
+       buf_add(buf, view->fullscreen ? "F" : " ");
+       /* TODO: add always-on-top and omnipresent ? */
+}
+
+static void
+field_set_output(struct buf *buf, struct view *view, const char *format)
+{
+       /* custom type conversion-specifier: O */
+       if (output_is_usable(view->output)) {
+               buf_add(buf, view->output->wlr_output->name);
+       }
+}
+
+static void
+field_set_output_short(struct buf *buf, struct view *view, const char *format)
+{
+       /* custom type conversion-specifier: o */
+       if (wl_list_length(&view->server->outputs) > 1 &&
+                       output_is_usable(view->output)) {
+               buf_add(buf, view->output->wlr_output->name);
+       }
+}
+
+static void
+field_set_identifier(struct buf *buf, struct view *view, const char *format)
+{
+       /* custom type conversion-specifier: I */
+       buf_add(buf, get_app_id_or_class(view, /*trim*/ false));
+}
+
+static void
+field_set_identifier_trimmed(struct buf *buf, struct view *view, const char *format)
+{
+       /* custom type conversion-specifier: i */
+       buf_add(buf, get_app_id_or_class(view, /*trim*/ true));
+}
+
+static void
+field_set_title(struct buf *buf, struct view *view, const char *format)
+{
+       /* custom type conversion-specifier: T */
+       buf_add(buf, get_title(view));
+}
+
+static void
+field_set_title_short(struct buf *buf, struct view *view, const char *format)
+{
+       /* custom type conversion-specifier: t */
+       buf_add(buf, get_title_if_different(view));
+}
+
+static void
+field_set_custom(struct buf *buf, struct view *view, const char *format)
+{
+       if (!format) {
+               wlr_log(WLR_ERROR, "Missing format for custom window switcher field");
+               return;
+       }
+
+       char fmt[LAB_FIELD_SINGLE_FMT_MAX_LEN];
+       unsigned char fmt_position = 0;
+
+       struct buf field_result;
+       buf_init(&field_result);
+
+       char converted_field[4096];
+
+       for (const char *p = format; *p; p++) {
+               if (!fmt_position) {
+                       if (*p == '%') {
+                               fmt[fmt_position++] = *p;
+                       } else {
+                               /*
+                                * Just relay anything not part of a
+                                * format string to the output buffer.
+                                */
+                               buf_add_char(buf, *p);
+                       }
+                       continue;
+               }
+
+               /* Allow string formatting */
+               /* TODO: add . for manual truncating? */
+               /*
+                * Remove the || *p == '#' section as not used/needed
+                * change (*p >= '0' && *p <= '9') to isdigit(*p)
+                * changes by droc12345
+                */
+               if (*p == '-' || isdigit(*p)) {
+                       if (fmt_position >= LAB_FIELD_SINGLE_FMT_MAX_LEN - 2) {
+                               /* Leave space for terminating 's' and NULL byte */
+                               wlr_log(WLR_ERROR,
+                                       "single format string length exceeded: '%s'", p);
+                       } else {
+                               fmt[fmt_position++] = *p;
+                       }
+                       continue;
+               }
+
+               /* Handlers */
+               for (unsigned char i = 0; i < LAB_FIELD_COUNT; i++) {
+                       if (*p != field_converter[i].fmt_char) {
+                               continue;
+                       }
+
+                       /* Generate the actual content*/
+                       field_converter[i].fn(&field_result, view, /*format*/ NULL);
+
+                       /* Throw it at snprintf to allow formatting / padding */
+                       fmt[fmt_position++] = 's';
+                       fmt[fmt_position++] = '\0';
+                       snprintf(converted_field, sizeof(converted_field),
+                               fmt, field_result.buf);
+
+                       /* And finally write it to the output buffer */
+                       buf_add(buf, converted_field);
+                       goto reset_format;
+               }
+
+               wlr_log(WLR_ERROR,
+                       "invalid format character found for osd %s: '%c'",
+                       format, *p);
+
+reset_format:
+               /* Reset format string and tmp field result buffer */
+               buf_clear(&field_result);
+               fmt_position = 0;
+       }
+       free(field_result.buf);
+}
+
+static const struct field_converter field_converter[LAB_FIELD_COUNT] = {
+       [LAB_FIELD_TYPE]               = { 'B', field_set_type },
+       [LAB_FIELD_TYPE_SHORT]         = { 'b', field_set_type_short },
+       [LAB_FIELD_WIN_STATE_ALL]      = { 'S', field_set_win_state_all },
+       [LAB_FIELD_WIN_STATE]          = { 's', field_set_win_state },
+       [LAB_FIELD_IDENTIFIER]         = { 'I', field_set_identifier },
+       [LAB_FIELD_TRIMMED_IDENTIFIER] = { 'i', field_set_identifier_trimmed },
+       [LAB_FIELD_WORKSPACE]          = { 'W', field_set_workspace },
+       [LAB_FIELD_WORKSPACE_SHORT]    = { 'w', field_set_workspace_short },
+       [LAB_FIELD_OUTPUT]             = { 'O', field_set_output },
+       [LAB_FIELD_OUTPUT_SHORT]       = { 'o', field_set_output_short },
+       [LAB_FIELD_TITLE]              = { 'T', field_set_title },
+       [LAB_FIELD_TITLE_SHORT]        = { 't', field_set_title_short },
+       /* fmt_char can never be matched so prevents LAB_FIELD_CUSTOM recursion */
+       [LAB_FIELD_CUSTOM]             = { '\0', field_set_custom },
+};
+
+struct window_switcher_field *
+osd_field_create(void)
+{
+       struct window_switcher_field *field = znew(*field);
+       return field;
+}
+
+void
+osd_field_arg_from_xml_node(struct window_switcher_field *field,
+               const char *nodename, const char *content)
+{
+       if (!strcmp(nodename, "content")) {
+               if (!strcmp(content, "type")) {
+                       field->content = LAB_FIELD_TYPE;
+               } else if (!strcmp(content, "type_short")) {
+                       field->content = LAB_FIELD_TYPE_SHORT;
+               } else if (!strcmp(content, "app_id")) {
+                       wlr_log(WLR_ERROR, "window-switcher field 'app_id' is deprecated");
+                       field->content = LAB_FIELD_IDENTIFIER;
+               } else if (!strcmp(content, "identifier")) {
+                       field->content = LAB_FIELD_IDENTIFIER;
+               } else if (!strcmp(content, "trimmed_identifier")) {
+                       field->content = LAB_FIELD_TRIMMED_IDENTIFIER;
+               } else if (!strcmp(content, "title")) {
+                       /* Keep old defaults */
+                       field->content = LAB_FIELD_TITLE_SHORT;
+               } else if (!strcmp(content, "workspace")) {
+                       field->content = LAB_FIELD_WORKSPACE;
+               } else if (!strcmp(content, "state")) {
+                       field->content = LAB_FIELD_WIN_STATE;
+               } else if (!strcmp(content, "output")) {
+                       /* Keep old defaults */
+                       field->content = LAB_FIELD_OUTPUT_SHORT;
+               } else if (!strcmp(content, "custom")) {
+                       field->content = LAB_FIELD_CUSTOM;
+               } else {
+                       wlr_log(WLR_ERROR, "bad windowSwitcher field '%s'", content);
+               }
+       } else if (!strcmp(nodename, "format")) {
+               zfree(field->format);
+               field->format = xstrdup(content);
+       } else if (!strcmp(nodename, "width") && !strchr(content, '%')) {
+               wlr_log(WLR_ERROR, "Invalid osd field width: %s, misses trailing %%", content);
+       } else if (!strcmp(nodename, "width")) {
+               field->width = atoi(content);
+       } else {
+               wlr_log(WLR_ERROR, "Unexpected data in field parser: %s=\"%s\"",
+                       nodename, content);
+       }
+}
+
+bool
+osd_field_validate(struct window_switcher_field *field)
+{
+       if (field->content == LAB_FIELD_NONE) {
+               wlr_log(WLR_ERROR, "Invalid OSD field: no content set");
+               return false;
+       }
+       if (field->content == LAB_FIELD_CUSTOM && !field->format) {
+               wlr_log(WLR_ERROR, "Invalid OSD field: custom without format");
+               return false;
+       }
+       if (!field->width) {
+               wlr_log(WLR_ERROR, "Invalid OSD field: no width");
+               return false;
+       }
+       return true;
+}
+
+void
+osd_field_get_content(struct window_switcher_field *field,
+               struct buf *buf, struct view *view)
+{
+       if (field->content == LAB_FIELD_NONE) {
+               wlr_log(WLR_ERROR, "Invalid window switcher field type");
+               return;
+       }
+       assert(field->content < LAB_FIELD_COUNT && field_converter[field->content].fn);
+
+       field_converter[field->content].fn(buf, view, field->format);
+}
+
+void
+osd_field_free(struct window_switcher_field *field)
+{
+       zfree(field->format);
+       zfree(field);
+}
index 98ecf4de9c30844f906c19ea201689cd15f7a99c..9e8ab7032784e4c9bc0a3101592a6028a123023c 100644 (file)
@@ -10,6 +10,7 @@
 #include "input/keyboard.h"
 #include "labwc.h"
 #include "menu/menu.h"
+#include "osd.h"
 #include "placement.h"
 #include "regions.h"
 #include "resize_indicator.h"