rofi 1.7.7
combi.c
Go to the documentation of this file.
1/*
2 * rofi
3 *
4 * MIT/X11 License
5 * Copyright © 2013-2023 Qball Cow <qball@gmpclient.org>
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining
8 * a copy of this software and associated documentation files (the
9 * "Software"), to deal in the Software without restriction, including
10 * without limitation the rights to use, copy, modify, merge, publish,
11 * distribute, sublicense, and/or sell copies of the Software, and to
12 * permit persons to whom the Software is furnished to do so, subject to
13 * the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be
16 * included in all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 *
26 */
27
29#define G_LOG_DOMAIN "Modes.Combi"
30
31#include "helper.h"
32#include "settings.h"
33#include <rofi.h>
34#include <stdio.h>
35#include <stdlib.h>
36
37#include "mode-private.h"
38#include "widgets/textbox.h"
39#include <modes/modes.h>
40#include <pango/pango.h>
41#include <theme.h>
42
46typedef struct {
48 gboolean disable;
49} CombiMode;
50
51typedef struct {
52 // List of (combined) entries.
53 unsigned int cmd_list_length;
54 // List to validate where each switcher starts.
55 unsigned int *starts;
56 unsigned int *lengths;
57 // List of switchers to combine.
58 unsigned int num_switchers;
61
64 char *savept = NULL;
65 // Make a copy, as strtok will modify it.
66 char *switcher_str = g_strdup(config.combi_modes);
67 const char *const sep = ",#";
68 // Split token on ','. This modifies switcher_str.
69 for (char *token = strtok_r(switcher_str, sep, &savept); token != NULL;
70 token = strtok_r(NULL, sep, &savept)) {
71 /* Check against recursion. */
72 if (g_strcmp0(token, sw->name) == 0) {
73 g_warning("You cannot add '%s' to the list of combined modes.", sw->name);
74 continue;
75 }
76 // Resize and add entry.
77 pd->switchers = (CombiMode *)g_realloc(
78 pd->switchers, sizeof(CombiMode) * (pd->num_switchers + 1));
79
80 Mode *mode = rofi_collect_modes_search(token);
81 if (mode != NULL) {
82 pd->switchers[pd->num_switchers].disable = FALSE;
83 pd->switchers[pd->num_switchers++].mode = mode;
84 continue;
85 }
86 // If not build in, use custom switchers.
87 mode = script_mode_parse_setup(token);
88 if (mode != NULL) {
89 pd->switchers[pd->num_switchers].disable = FALSE;
90 pd->switchers[pd->num_switchers++].mode = mode;
91 continue;
92 }
93 // Report error, don't continue.
94 g_warning("Invalid script switcher: %s", token);
95 token = NULL;
96 }
97 // Free string that was modified by strtok_r
98 g_free(switcher_str);
99}
100static unsigned int combi_mode_get_num_entries(const Mode *sw) {
101 const CombiModePrivateData *pd =
103 unsigned int length = 0;
104 for (unsigned int i = 0; i < pd->num_switchers; i++) {
105 unsigned int entries = mode_get_num_entries(pd->switchers[i].mode);
106 pd->starts[i] = length;
107 pd->lengths[i] = entries;
108 length += entries;
109 }
110 return length;
111}
112
113static int combi_mode_init(Mode *sw) {
114 if (mode_get_private_data(sw) == NULL) {
115 CombiModePrivateData *pd = g_malloc0(sizeof(*pd));
116 mode_set_private_data(sw, (void *)pd);
118 pd->starts = g_malloc0(sizeof(int) * pd->num_switchers);
119 pd->lengths = g_malloc0(sizeof(int) * pd->num_switchers);
120 for (unsigned int i = 0; i < pd->num_switchers; i++) {
121 if (!mode_init(pd->switchers[i].mode)) {
122 return FALSE;
123 }
124 }
125 if (pd->cmd_list_length == 0) {
127 }
128 }
129 return TRUE;
130}
131static void combi_mode_destroy(Mode *sw) {
133 if (pd != NULL) {
134 g_free(pd->starts);
135 g_free(pd->lengths);
136 // Cleanup switchers.
137 for (unsigned int i = 0; i < pd->num_switchers; i++) {
139 }
140 g_free(pd->switchers);
141 g_free(pd);
142 mode_set_private_data(sw, NULL);
143 }
144}
145static ModeMode combi_mode_result(Mode *sw, int mretv, char **input,
146 unsigned int selected_line) {
148
149 if (input[0][0] == '!') {
150 int switcher = -1;
151 // Implement strchrnul behaviour.
152 char *eob = g_utf8_strchr(input[0], -1, ' ');
153 if (eob == NULL) {
154 eob = &(input[0][strlen(input[0])]);
155 }
156 ssize_t bang_len = g_utf8_pointer_to_offset(input[0], eob) - 1;
157 if (bang_len > 0) {
158 for (unsigned i = 0; i < pd->num_switchers; i++) {
159 const char *mode_name = mode_get_name(pd->switchers[i].mode);
160 size_t mode_name_len = g_utf8_strlen(mode_name, -1);
161 if ((size_t)bang_len <= mode_name_len &&
162 utf8_strncmp(&input[0][1], mode_name, bang_len) == 0) {
163 switcher = i;
164 break;
165 }
166 }
167 }
168 if (switcher >= 0) {
169 if (eob[0] == ' ') {
170 char *n = g_strdup(eob + 1);
171 ModeMode retv = mode_result(pd->switchers[switcher].mode, mretv, &n,
172 selected_line - pd->starts[switcher]);
173 g_free(n);
174 return retv;
175 } else if (eob[0] == '\0') {
176 char *str = NULL;
177 ModeMode retv = mode_result(pd->switchers[switcher].mode, mretv, &str,
178 selected_line - pd->starts[switcher]);
179 g_free(str);
180 return retv;
181 }
182 return MODE_EXIT;
183 }
184 } else if ((mretv & MENU_COMPLETE)) {
185 return RELOAD_DIALOG;
186 }
187
188 for (unsigned i = 0; i < pd->num_switchers; i++) {
189 if (selected_line >= pd->starts[i] &&
190 selected_line < (pd->starts[i] + pd->lengths[i])) {
191 return mode_result(pd->switchers[i].mode, mretv, input,
192 selected_line - pd->starts[i]);
193 }
194 }
195 if ((mretv & MENU_CUSTOM_INPUT)) {
196 return mode_result(pd->switchers[0].mode, mretv, input, selected_line);
197 }
198 return MODE_EXIT;
199}
200static int combi_mode_match(const Mode *sw, rofi_int_matcher **tokens,
201 unsigned int index) {
203 for (unsigned i = 0; i < pd->num_switchers; i++) {
204 if (pd->switchers[i].disable) {
205 continue;
206 }
207 if (index >= pd->starts[i] && index < (pd->starts[i] + pd->lengths[i])) {
208 return mode_token_match(pd->switchers[i].mode, tokens,
209 index - pd->starts[i]);
210 }
211 }
212 return 0;
213}
214static char *combi_mgrv(const Mode *sw, unsigned int selected_line, int *state,
215 GList **attr_list, int get_entry) {
217 if (!get_entry) {
218 for (unsigned i = 0; i < pd->num_switchers; i++) {
219 if (selected_line >= pd->starts[i] &&
220 selected_line < (pd->starts[i] + pd->lengths[i])) {
222 selected_line - pd->starts[i], state, attr_list,
223 FALSE);
224 return NULL;
225 }
226 }
227 return NULL;
228 }
229 for (unsigned i = 0; i < pd->num_switchers; i++) {
230 if (selected_line >= pd->starts[i] &&
231 selected_line < (pd->starts[i] + pd->lengths[i])) {
232 char *retv;
233 char *str = retv = mode_get_display_value(pd->switchers[i].mode,
234 selected_line - pd->starts[i],
235 state, attr_list, TRUE);
236 const char *dname = mode_get_display_name(pd->switchers[i].mode);
237
238 if (!config.combi_hide_mode_prefix) {
239 if (!(*state & MARKUP)) {
240 char *tmp = str;
241 str = g_markup_escape_text(tmp, -1);
242 g_free(tmp);
243 *state |= MARKUP;
244 }
245
246 retv = helper_string_replace_if_exists(config.combi_display_format,
247 "{mode}", dname, "{text}", str,
248 (char *)0);
249 g_free(str);
250
251 if (attr_list != NULL) {
252 ThemeWidget *wid = rofi_config_find_widget(sw->name, NULL, TRUE);
254 wid, P_COLOR, pd->switchers[i].mode->name, TRUE);
255 if (p != NULL) {
256 PangoAttribute *pa = pango_attr_foreground_new(
257 p->value.color.red * 65535, p->value.color.green * 65535,
258 p->value.color.blue * 65535);
259 pa->start_index = PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING;
260 pa->end_index = strlen(dname);
261 *attr_list = g_list_append(*attr_list, pa);
262 }
263 }
264 }
265 return retv;
266 }
267 }
268
269 return NULL;
270}
271static char *combi_get_completion(const Mode *sw, unsigned int index) {
273 for (unsigned i = 0; i < pd->num_switchers; i++) {
274 if (index >= pd->starts[i] && index < (pd->starts[i] + pd->lengths[i])) {
275 char *comp =
276 mode_get_completion(pd->switchers[i].mode, index - pd->starts[i]);
277 char *mcomp =
278 g_strdup_printf("!%s %s", mode_get_name(pd->switchers[i].mode), comp);
279 g_free(comp);
280 return mcomp;
281 }
282 }
283 // Should never get here.
284 g_assert_not_reached();
285 return NULL;
286}
287
288static cairo_surface_t *combi_get_icon(const Mode *sw, unsigned int index,
289 unsigned int height) {
291 for (unsigned i = 0; i < pd->num_switchers; i++) {
292 if (index >= pd->starts[i] && index < (pd->starts[i] + pd->lengths[i])) {
293 cairo_surface_t *icon =
294 mode_get_icon(pd->switchers[i].mode, index - pd->starts[i], height);
295 return icon;
296 }
297 }
298 return NULL;
299}
300
301static char *combi_preprocess_input(Mode *sw, const char *input) {
303 for (unsigned i = 0; i < pd->num_switchers; i++) {
304 pd->switchers[i].disable = FALSE;
305 }
306 if (input != NULL && input[0] == '!') {
307 // Implement strchrnul behaviour.
308 const char *eob = g_utf8_strchr(input, -1, ' ');
309 if (eob == NULL) {
310 // Set it to end.
311 eob = &(input[strlen(input)]);
312 }
313 ssize_t bang_len = g_utf8_pointer_to_offset(input, eob) - 1;
314 if (bang_len > 0) {
315 for (unsigned i = 0; i < pd->num_switchers; i++) {
316 const char *mode_name = mode_get_name(pd->switchers[i].mode);
317 size_t mode_name_len = g_utf8_strlen(mode_name, -1);
318 if (!((size_t)bang_len <= mode_name_len &&
319 utf8_strncmp(&input[1], mode_name, bang_len) == 0)) {
320 // No match.
321 pd->switchers[i].disable = TRUE;
322 }
323 }
324 if (eob[0] == '\0' || eob[1] == '\0') {
325 return NULL;
326 }
327 return g_strdup(eob + 1);
328 }
329 }
330 return g_strdup(input);
331}
332
333Mode combi_mode = {.name = "combi",
334 .cfg_name_key = "display-combi",
335 ._init = combi_mode_init,
336 ._get_num_entries = combi_mode_get_num_entries,
337 ._result = combi_mode_result,
338 ._destroy = combi_mode_destroy,
339 ._token_match = combi_mode_match,
340 ._get_completion = combi_get_completion,
341 ._get_display_value = combi_mgrv,
342 ._get_icon = combi_get_icon,
343 ._preprocess_input = combi_preprocess_input,
344 .private_data = NULL,
345 .free = NULL,
346 .type = MODE_TYPE_SWITCHER };
static char * combi_mgrv(const Mode *sw, unsigned int selected_line, int *state, GList **attr_list, int get_entry)
Definition combi.c:214
static ModeMode combi_mode_result(Mode *sw, int mretv, char **input, unsigned int selected_line)
Definition combi.c:145
static unsigned int combi_mode_get_num_entries(const Mode *sw)
Definition combi.c:100
static cairo_surface_t * combi_get_icon(const Mode *sw, unsigned int index, unsigned int height)
Definition combi.c:288
static int combi_mode_init(Mode *sw)
Definition combi.c:113
static char * combi_get_completion(const Mode *sw, unsigned int index)
Definition combi.c:271
static void combi_mode_parse_switchers(Mode *sw)
Definition combi.c:62
static void combi_mode_destroy(Mode *sw)
Definition combi.c:131
static char * combi_preprocess_input(Mode *sw, const char *input)
Definition combi.c:301
static int combi_mode_match(const Mode *sw, rofi_int_matcher **tokens, unsigned int index)
Definition combi.c:200
Mode combi_mode
Definition combi.c:333
Property * rofi_theme_find_property(ThemeWidget *wid, PropertyType type, const char *property, gboolean exact)
Definition theme.c:743
char * helper_string_replace_if_exists(char *string,...)
Definition helper.c:1336
ThemeWidget * rofi_config_find_widget(const char *name, const char *state, gboolean exact)
Definition theme.c:780
void mode_destroy(Mode *mode)
Definition mode.c:64
const char * mode_get_name(const Mode *mode)
Definition mode.c:157
int mode_init(Mode *mode)
Definition mode.c:44
cairo_surface_t * mode_get_icon(Mode *mode, unsigned int selected_line, unsigned int height)
Definition mode.c:87
const char * mode_get_display_name(const Mode *mode)
Definition mode.c:184
struct rofi_mode Mode
Definition mode.h:44
unsigned int mode_get_num_entries(const Mode *mode)
Definition mode.c:70
ModeMode mode_result(Mode *mode, int menu_retv, char **input, unsigned int selected_line)
Definition mode.c:131
void * mode_get_private_data(const Mode *mode)
Definition mode.c:171
void mode_set_private_data(Mode *mode, void *pd)
Definition mode.c:176
int mode_token_match(const Mode *mode, rofi_int_matcher **tokens, unsigned int selected_line)
Definition mode.c:150
ModeMode
Definition mode.h:49
char * mode_get_display_value(const Mode *mode, unsigned int selected_line, int *state, GList **attribute_list, int get_entry)
Definition mode.c:76
char * mode_get_completion(const Mode *mode, unsigned int selected_line)
Definition mode.c:121
@ MENU_COMPLETE
Definition mode.h:83
@ MENU_CUSTOM_INPUT
Definition mode.h:73
@ MODE_EXIT
Definition mode.h:51
@ RELOAD_DIALOG
Definition mode.h:55
Mode * rofi_collect_modes_search(const char *name)
Definition rofi.c:564
Mode * script_mode_parse_setup(const char *str)
Definition script.c:591
@ MARKUP
Definition textbox.h:112
struct _icon icon
Definition icon.h:44
int utf8_strncmp(const char *a, const char *b, size_t n)
Definition helper.c:989
@ MODE_TYPE_SWITCHER
@ P_COLOR
Definition rofi-types.h:20
struct rofi_int_matcher_t rofi_int_matcher
Settings config
unsigned int cmd_list_length
Definition combi.c:53
unsigned int num_switchers
Definition combi.c:58
unsigned int * lengths
Definition combi.c:56
CombiMode * switchers
Definition combi.c:59
unsigned int * starts
Definition combi.c:55
Mode * mode
Definition combi.c:47
gboolean disable
Definition combi.c:48
PropertyValue value
Definition rofi-types.h:293
double blue
Definition rofi-types.h:162
double green
Definition rofi-types.h:160
double red
Definition rofi-types.h:158
char * name
ThemeColor color
Definition rofi-types.h:264