mirror of
https://github.com/labwc/labwc.git
synced 2025-10-29 05:40:24 -04:00
osd: add window-switcher custom field (#1670)
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)
This commit is contained in:
parent
2bf285a2c6
commit
d672765ea7
13 changed files with 490 additions and 191 deletions
|
|
@ -243,6 +243,30 @@ this is for compatibility with Openbox.
|
||||||
|
|
||||||
- *output* Show output id, if more than one output detected
|
- *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
|
*width* defines the width of the field expressed as a percentage of
|
||||||
the overall window switcher width. The "%" character is required.
|
the overall window switcher width. The "%" character is required.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,22 @@
|
||||||
<field content="output" width="9%" />
|
<field content="output" width="9%" />
|
||||||
<field content="identifier" width="30%" />
|
<field content="identifier" width="30%" />
|
||||||
<field content="title" width="50%" />
|
<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>
|
</windowSwitcher>
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,6 @@
|
||||||
#include "resize_indicator.h"
|
#include "resize_indicator.h"
|
||||||
#include "theme.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 {
|
enum view_placement_policy {
|
||||||
LAB_PLACE_CENTER = 0,
|
LAB_PLACE_CENTER = 0,
|
||||||
LAB_PLACE_CURSOR,
|
LAB_PLACE_CURSOR,
|
||||||
|
|
@ -53,12 +41,6 @@ struct usable_area_override {
|
||||||
struct wl_list link; /* struct rcxml.usable_area_overrides */
|
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 {
|
struct rcxml {
|
||||||
/* from command line */
|
/* from command line */
|
||||||
char *config_dir;
|
char *config_dir;
|
||||||
|
|
|
||||||
|
|
@ -502,15 +502,6 @@ void server_init(struct server *server);
|
||||||
void server_start(struct server *server);
|
void server_start(struct server *server);
|
||||||
void server_finish(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
|
* wlroots "input inhibitor" extension (required for swaylock) blocks
|
||||||
* any client other than the requesting client from receiving events
|
* any client other than the requesting client from receiving events
|
||||||
|
|
|
||||||
62
include/osd.h
Normal file
62
include/osd.h
Normal file
|
|
@ -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
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "labwc.h"
|
#include "labwc.h"
|
||||||
#include "menu/menu.h"
|
#include "menu/menu.h"
|
||||||
|
#include "osd.h"
|
||||||
#include "output-virtual.h"
|
#include "output-virtual.h"
|
||||||
#include "placement.h"
|
#include "placement.h"
|
||||||
#include "regions.h"
|
#include "regions.h"
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
#include "config/tablet.h"
|
#include "config/tablet.h"
|
||||||
#include "config/rcxml.h"
|
#include "config/rcxml.h"
|
||||||
#include "labwc.h"
|
#include "labwc.h"
|
||||||
|
#include "osd.h"
|
||||||
#include "regions.h"
|
#include "regions.h"
|
||||||
#include "view.h"
|
#include "view.h"
|
||||||
#include "window-rules.h"
|
#include "window-rules.h"
|
||||||
|
|
@ -187,7 +188,7 @@ static void
|
||||||
fill_window_switcher_field(char *nodename, char *content)
|
fill_window_switcher_field(char *nodename, char *content)
|
||||||
{
|
{
|
||||||
if (!strcasecmp(nodename, "field.fields.windowswitcher")) {
|
if (!strcasecmp(nodename, "field.fields.windowswitcher")) {
|
||||||
current_field = znew(*current_field);
|
current_field = osd_field_create();
|
||||||
wl_list_append(&rc.window_switcher.fields, ¤t_field->link);
|
wl_list_append(&rc.window_switcher.fields, ¤t_field->link);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -197,39 +198,8 @@ fill_window_switcher_field(char *nodename, char *content)
|
||||||
/* intentionally left empty */
|
/* intentionally left empty */
|
||||||
} else if (!current_field) {
|
} else if (!current_field) {
|
||||||
wlr_log(WLR_ERROR, "no <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(¤t_field->link);
|
|
||||||
zfree(current_field);
|
|
||||||
} else if (!strcmp(nodename, "width")) {
|
|
||||||
current_field->width = atoi(content);
|
|
||||||
} else {
|
} else {
|
||||||
wlr_log(WLR_ERROR, "Unexpected data in field parser: %s=\"%s\"",
|
osd_field_arg_from_xml_node(current_field, nodename, content);
|
||||||
nodename, content);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1516,6 +1486,16 @@ validate(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
validate_actions();
|
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
|
void
|
||||||
|
|
@ -1645,7 +1625,7 @@ rcxml_finish(void)
|
||||||
struct window_switcher_field *field, *field_tmp;
|
struct window_switcher_field *field, *field_tmp;
|
||||||
wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.fields, link) {
|
wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.fields, link) {
|
||||||
wl_list_remove(&field->link);
|
wl_list_remove(&field->link);
|
||||||
zfree(field);
|
osd_field_free(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct window_rule *rule, *rule_tmp;
|
struct window_rule *rule, *rule_tmp;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
#include "labwc.h"
|
#include "labwc.h"
|
||||||
#include "layers.h"
|
#include "layers.h"
|
||||||
#include "node.h"
|
#include "node.h"
|
||||||
|
#include "osd.h"
|
||||||
#include "ssd.h"
|
#include "ssd.h"
|
||||||
#include "view.h"
|
#include "view.h"
|
||||||
#include "window-rules.h"
|
#include "window-rules.h"
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include "input/key-state.h"
|
#include "input/key-state.h"
|
||||||
#include "labwc.h"
|
#include "labwc.h"
|
||||||
#include "menu/menu.h"
|
#include "menu/menu.h"
|
||||||
|
#include "osd.h"
|
||||||
#include "regions.h"
|
#include "regions.h"
|
||||||
#include "view.h"
|
#include "view.h"
|
||||||
#include "workspaces.h"
|
#include "workspaces.h"
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ labwc_sources = files(
|
||||||
'main.c',
|
'main.c',
|
||||||
'node.c',
|
'node.c',
|
||||||
'osd.c',
|
'osd.c',
|
||||||
|
'osd_field.c',
|
||||||
'output.c',
|
'output.c',
|
||||||
'output-virtual.c',
|
'output-virtual.c',
|
||||||
'overlay.c',
|
'overlay.c',
|
||||||
|
|
|
||||||
133
src/osd.c
133
src/osd.c
|
|
@ -14,39 +14,13 @@
|
||||||
#include "common/scene-helpers.h"
|
#include "common/scene-helpers.h"
|
||||||
#include "config/rcxml.h"
|
#include "config/rcxml.h"
|
||||||
#include "labwc.h"
|
#include "labwc.h"
|
||||||
#include "theme.h"
|
|
||||||
#include "node.h"
|
#include "node.h"
|
||||||
|
#include "osd.h"
|
||||||
|
#include "theme.h"
|
||||||
#include "view.h"
|
#include "view.h"
|
||||||
#include "window-rules.h"
|
#include "window-rules.h"
|
||||||
#include "workspaces.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
|
static void
|
||||||
destroy_osd_nodes(struct output *output)
|
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);
|
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
|
static void
|
||||||
render_osd(struct server *server, cairo_t *cairo, int w, int h,
|
render_osd(struct server *server, cairo_t *cairo, int w, int h,
|
||||||
bool show_workspace, const char *workspace_name,
|
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_padding_y
|
||||||
+ theme->osd_window_switcher_item_active_border_width);
|
+ theme->osd_window_switcher_item_active_border_width);
|
||||||
|
|
||||||
switch (field->content) {
|
osd_field_get_content(field, &buf, *view);
|
||||||
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;
|
|
||||||
}
|
|
||||||
int field_width = (available_width - (nr_fields + 1)
|
int field_width = (available_width - (nr_fields + 1)
|
||||||
* theme->osd_window_switcher_item_padding_x)
|
* theme->osd_window_switcher_item_padding_x)
|
||||||
* field->width / 100.0;
|
* field->width / 100.0;
|
||||||
|
|
|
||||||
365
src/osd_field.c
Normal file
365
src/osd_field.c
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include "input/keyboard.h"
|
#include "input/keyboard.h"
|
||||||
#include "labwc.h"
|
#include "labwc.h"
|
||||||
#include "menu/menu.h"
|
#include "menu/menu.h"
|
||||||
|
#include "osd.h"
|
||||||
#include "placement.h"
|
#include "placement.h"
|
||||||
#include "regions.h"
|
#include "regions.h"
|
||||||
#include "resize_indicator.h"
|
#include "resize_indicator.h"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue