rofi  1.7.5
combi.c
Go to the documentation of this file.
1 /*
2  * rofi
3  *
4  * MIT/X11 License
5  * Copyright © 2013-2022 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 
46 typedef struct {
48  gboolean disable;
49 } CombiMode;
50 
51 typedef 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 
62 static void combi_mode_parse_switchers(Mode *sw) {
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 }
100 static 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 
113 static 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 }
131 static 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++) {
138  mode_destroy(pd->switchers[i].mode);
139  }
140  g_free(pd->switchers);
141  g_free(pd);
142  mode_set_private_data(sw, NULL);
143  }
144 }
145 static 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 = eob + 1;
171  return mode_result(pd->switchers[switcher].mode, mretv, &n,
172  selected_line - pd->starts[switcher]);
173  }
174  return MODE_EXIT;
175  }
176  } else if ((mretv & MENU_COMPLETE)) {
177  return RELOAD_DIALOG;
178  }
179 
180  for (unsigned i = 0; i < pd->num_switchers; i++) {
181  if (selected_line >= pd->starts[i] &&
182  selected_line < (pd->starts[i] + pd->lengths[i])) {
183  return mode_result(pd->switchers[i].mode, mretv, input,
184  selected_line - pd->starts[i]);
185  }
186  }
187  if ((mretv & MENU_CUSTOM_INPUT)) {
188  return mode_result(pd->switchers[0].mode, mretv, input, selected_line);
189  }
190  return MODE_EXIT;
191 }
192 static int combi_mode_match(const Mode *sw, rofi_int_matcher **tokens,
193  unsigned int index) {
195  for (unsigned i = 0; i < pd->num_switchers; i++) {
196  if (pd->switchers[i].disable) {
197  continue;
198  }
199  if (index >= pd->starts[i] && index < (pd->starts[i] + pd->lengths[i])) {
200  return mode_token_match(pd->switchers[i].mode, tokens,
201  index - pd->starts[i]);
202  }
203  }
204  return 0;
205 }
206 static char *combi_mgrv(const Mode *sw, unsigned int selected_line, int *state,
207  GList **attr_list, int get_entry) {
209  if (!get_entry) {
210  for (unsigned i = 0; i < pd->num_switchers; i++) {
211  if (selected_line >= pd->starts[i] &&
212  selected_line < (pd->starts[i] + pd->lengths[i])) {
214  selected_line - pd->starts[i], state, attr_list,
215  FALSE);
216  return NULL;
217  }
218  }
219  return NULL;
220  }
221  for (unsigned i = 0; i < pd->num_switchers; i++) {
222  if (selected_line >= pd->starts[i] &&
223  selected_line < (pd->starts[i] + pd->lengths[i])) {
224  char *retv;
225  char *str = retv = mode_get_display_value(pd->switchers[i].mode,
226  selected_line - pd->starts[i],
227  state, attr_list, TRUE);
228  const char *dname = mode_get_display_name(pd->switchers[i].mode);
229 
231  if (!(*state & MARKUP)) {
232  char *tmp = str;
233  str = g_markup_escape_text(tmp, -1);
234  g_free(tmp);
235  *state |= MARKUP;
236  }
237 
239  config.combi_display_format, "{mode}", dname, "{text}", str, NULL);
240  g_free(str);
241 
242  if (attr_list != NULL) {
243  ThemeWidget *wid = rofi_config_find_widget(sw->name, NULL, TRUE);
245  wid, P_COLOR, pd->switchers[i].mode->name, TRUE);
246  if (p != NULL) {
247  PangoAttribute *pa = pango_attr_foreground_new(
248  p->value.color.red * 65535, p->value.color.green * 65535,
249  p->value.color.blue * 65535);
250  pa->start_index = PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING;
251  pa->end_index = strlen(dname);
252  *attr_list = g_list_append(*attr_list, pa);
253  }
254  }
255  }
256  return retv;
257  }
258  }
259 
260  return NULL;
261 }
262 static char *combi_get_completion(const Mode *sw, unsigned int index) {
264  for (unsigned i = 0; i < pd->num_switchers; i++) {
265  if (index >= pd->starts[i] && index < (pd->starts[i] + pd->lengths[i])) {
266  char *comp =
267  mode_get_completion(pd->switchers[i].mode, index - pd->starts[i]);
268  char *mcomp =
269  g_strdup_printf("!%s %s", mode_get_name(pd->switchers[i].mode), comp);
270  g_free(comp);
271  return mcomp;
272  }
273  }
274  // Should never get here.
275  g_assert_not_reached();
276  return NULL;
277 }
278 
279 static cairo_surface_t *combi_get_icon(const Mode *sw, unsigned int index,
280  unsigned int height) {
282  for (unsigned i = 0; i < pd->num_switchers; i++) {
283  if (index >= pd->starts[i] && index < (pd->starts[i] + pd->lengths[i])) {
284  cairo_surface_t *icon =
285  mode_get_icon(pd->switchers[i].mode, index - pd->starts[i], height);
286  return icon;
287  }
288  }
289  return NULL;
290 }
291 
292 static char *combi_preprocess_input(Mode *sw, const char *input) {
294  for (unsigned i = 0; i < pd->num_switchers; i++) {
295  pd->switchers[i].disable = FALSE;
296  }
297  if (input != NULL && input[0] == '!') {
298  // Implement strchrnul behaviour.
299  const char *eob = g_utf8_strchr(input, -1, ' ');
300  if (eob == NULL) {
301  // Set it to end.
302  eob = &(input[strlen(input)]);
303  }
304  ssize_t bang_len = g_utf8_pointer_to_offset(input, eob) - 1;
305  if (bang_len > 0) {
306  for (unsigned i = 0; i < pd->num_switchers; i++) {
307  const char *mode_name = mode_get_name(pd->switchers[i].mode);
308  size_t mode_name_len = g_utf8_strlen(mode_name, -1);
309  if (!((size_t)bang_len <= mode_name_len &&
310  utf8_strncmp(&input[1], mode_name, bang_len) == 0)) {
311  // No match.
312  pd->switchers[i].disable = TRUE;
313  }
314  }
315  if (eob[0] == '\0' || eob[1] == '\0') {
316  return NULL;
317  }
318  return g_strdup(eob + 1);
319  }
320  }
321  return g_strdup(input);
322 }
323 
324 Mode combi_mode = {.name = "combi",
325  .cfg_name_key = "display-combi",
326  ._init = combi_mode_init,
327  ._get_num_entries = combi_mode_get_num_entries,
328  ._result = combi_mode_result,
329  ._destroy = combi_mode_destroy,
330  ._token_match = combi_mode_match,
331  ._get_completion = combi_get_completion,
332  ._get_display_value = combi_mgrv,
333  ._get_icon = combi_get_icon,
334  ._preprocess_input = combi_preprocess_input,
335  .private_data = NULL,
336  .free = NULL};
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 char * combi_get_completion(const Mode *sw, unsigned int index)
Definition: combi.c:262
static int combi_mode_init(Mode *sw)
Definition: combi.c:113
static char * combi_preprocess_input(Mode *sw, const char *input)
Definition: combi.c:292
static void combi_mode_parse_switchers(Mode *sw)
Definition: combi.c:62
static void combi_mode_destroy(Mode *sw)
Definition: combi.c:131
static int combi_mode_match(const Mode *sw, rofi_int_matcher **tokens, unsigned int index)
Definition: combi.c:192
static cairo_surface_t * combi_get_icon(const Mode *sw, unsigned int index, unsigned int height)
Definition: combi.c:279
static char * combi_mgrv(const Mode *sw, unsigned int selected_line, int *state, GList **attr_list, int get_entry)
Definition: combi.c:206
Mode combi_mode
Definition: combi.c:324
char * helper_string_replace_if_exists(char *string,...)
Definition: helper.c:1285
char * mode_get_completion(const Mode *mode, unsigned int selected_line)
Definition: mode.c:109
void mode_destroy(Mode *mode)
Definition: mode.c:52
int mode_init(Mode *mode)
Definition: mode.c:43
unsigned int mode_get_num_entries(const Mode *mode)
Definition: mode.c:58
const char * mode_get_display_name(const Mode *mode)
Definition: mode.c:172
ModeMode mode_result(Mode *mode, int menu_retv, char **input, unsigned int selected_line)
Definition: mode.c:119
char * mode_get_display_value(const Mode *mode, unsigned int selected_line, int *state, GList **attribute_list, int get_entry)
Definition: mode.c:64
void mode_set_private_data(Mode *mode, void *pd)
Definition: mode.c:164
cairo_surface_t * mode_get_icon(Mode *mode, unsigned int selected_line, unsigned int height)
Definition: mode.c:75
int mode_token_match(const Mode *mode, rofi_int_matcher **tokens, unsigned int selected_line)
Definition: mode.c:138
void * mode_get_private_data(const Mode *mode)
Definition: mode.c:159
ModeMode
Definition: mode.h:49
const char * mode_get_name(const Mode *mode)
Definition: mode.c:145
@ 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:516
Mode * script_mode_parse_setup(const char *str)
Definition: script.c:519
@ MARKUP
Definition: textbox.h:110
struct _icon icon
Definition: icon.h:44
int utf8_strncmp(const char *a, const char *b, size_t n)
Definition: helper.c:991
@ P_COLOR
Definition: rofi-types.h:22
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:297
char * combi_modes
Definition: settings.h:132
gboolean combi_hide_mode_prefix
Definition: settings.h:156
char * combi_display_format
Definition: settings.h:158
double blue
Definition: rofi-types.h:164
double green
Definition: rofi-types.h:162
double red
Definition: rofi-types.h:160
Definition: icon.c:39
char * name
Definition: mode-private.h:163
ThemeWidget * rofi_config_find_widget(const char *name, const char *state, gboolean exact)
Definition: theme.c:778
Property * rofi_theme_find_property(ThemeWidget *widget, PropertyType type, const char *property, gboolean exact)
Definition: theme.c:740
ThemeColor color
Definition: rofi-types.h:268