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 | ||||
| 
 | ||||
| 		- *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. | ||||
| 
 | ||||
|  |  | |||
|  | @ -77,7 +77,22 @@ | |||
|         <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> | ||||
|   --> | ||||
| 
 | ||||
|  |  | |||
|  | @ -15,18 +15,6 @@ | |||
| #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; | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										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 "labwc.h" | ||||
| #include "menu/menu.h" | ||||
| #include "osd.h" | ||||
| #include "output-virtual.h" | ||||
| #include "placement.h" | ||||
| #include "regions.h" | ||||
|  |  | |||
|  | @ -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, ¤t_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(¤t_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; | ||||
|  |  | |||
|  | @ -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" | ||||
|  |  | |||
|  | @ -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" | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ labwc_sources = files( | |||
|   'main.c', | ||||
|   'node.c', | ||||
|   'osd.c', | ||||
|   'osd_field.c', | ||||
|   'output.c', | ||||
|   'output-virtual.c', | ||||
|   'overlay.c', | ||||
|  |  | |||
							
								
								
									
										133
									
								
								src/osd.c
									
										
									
									
									
								
							
							
						
						
									
										133
									
								
								src/osd.c
									
										
									
									
									
								
							|  | @ -14,39 +14,13 @@ | |||
| #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; | ||||
|  |  | |||
							
								
								
									
										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 "labwc.h" | ||||
| #include "menu/menu.h" | ||||
| #include "osd.h" | ||||
| #include "placement.h" | ||||
| #include "regions.h" | ||||
| #include "resize_indicator.h" | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 droc12345
						droc12345