rofi  1.7.3
script.c
Go to the documentation of this file.
1 /*
2  * rofi
3  *
4  * MIT/X11 License
5  * Copyright © 2013-2021 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 "Dialogs.Script"
30 
31 #include "dialogs/script.h"
32 #include "config.h"
33 #include "helper.h"
34 #include "rofi.h"
35 #include <assert.h>
36 #include <ctype.h>
37 #include <errno.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <strings.h>
42 #include <unistd.h>
43 
44 #include "widgets/textbox.h"
45 
46 #include "mode-private.h"
47 
48 #include "rofi-icon-fetcher.h"
49 
51 
52 typedef struct {
54  unsigned int id;
58  unsigned int cmd_list_length;
59 
62  unsigned int num_urgent_list;
65  unsigned int num_active_list;
67  char *message;
68  char *prompt;
69  gboolean do_markup;
70  char delim;
72  gboolean no_custom;
73 
74  gboolean use_hot_keys;
76 
80 void dmenuscript_parse_entry_extras(G_GNUC_UNUSED Mode *sw,
81  DmenuScriptEntry *entry, char *buffer,
82  G_GNUC_UNUSED size_t length) {
83  gchar **extras = g_strsplit(buffer, "\x1f", -1);
84  gchar **extra;
85  for (extra = extras; *extra != NULL && *(extra + 1) != NULL; extra += 2) {
86  gchar *key = *extra;
87  gchar *value = *(extra + 1);
88  if (strcasecmp(key, "icon") == 0) {
89  entry->icon_name = value;
90  } else if (strcasecmp(key, "meta") == 0) {
91  entry->meta = value;
92  } else if (strcasecmp(key, "info") == 0) {
93  entry->info = value;
94  } else if (strcasecmp(key, "nonselectable") == 0) {
95  entry->nonselectable = strcasecmp(value, "true") == 0;
96  g_free(value);
97  } else {
98  g_free(value);
99  }
100  g_free(key);
101  }
102  g_free(extras);
103 }
104 
109 static void parse_header_entry(Mode *sw, char *line, ssize_t length) {
111  ssize_t length_key = 0; // strlen ( line );
112  while (length_key < length && line[length_key] != '\x1f') {
113  length_key++;
114  }
115 
116  if ((length_key + 1) < length) {
117  line[length_key] = '\0';
118  char *value = line + length_key + 1;
119  if (strcasecmp(line, "message") == 0) {
120  g_free(pd->message);
121  pd->message = strlen(value) ? g_strdup(value) : NULL;
122  } else if (strcasecmp(line, "prompt") == 0) {
123  g_free(pd->prompt);
124  pd->prompt = g_strdup(value);
125  sw->display_name = pd->prompt;
126  } else if (strcasecmp(line, "markup-rows") == 0) {
127  pd->do_markup = (strcasecmp(value, "true") == 0);
128  } else if (strcasecmp(line, "urgent") == 0) {
129  parse_ranges(value, &(pd->urgent_list), &(pd->num_urgent_list));
130  } else if (strcasecmp(line, "active") == 0) {
131  parse_ranges(value, &(pd->active_list), &(pd->num_active_list));
132  } else if (strcasecmp(line, "delim") == 0) {
133  pd->delim = helper_parse_char(value);
134  } else if (strcasecmp(line, "no-custom") == 0) {
135  pd->no_custom = (strcasecmp(value, "true") == 0);
136  } else if (strcasecmp(line, "use-hot-keys") == 0) {
137  pd->use_hot_keys = (strcasecmp(value, "true") == 0);
138  }
139  }
140 }
141 
142 static DmenuScriptEntry *execute_executor(Mode *sw, char *arg,
143  unsigned int *length, int value,
144  DmenuScriptEntry *entry) {
146  int fd = -1;
147  GError *error = NULL;
148  DmenuScriptEntry *retv = NULL;
149  char **argv = NULL;
150  int argc = 0;
151  *length = 0;
152 
153  // Environment
154  char **env = g_get_environ();
155 
156  char *str_value = g_strdup_printf("%d", value);
157  env = g_environ_setenv(env, "ROFI_RETV", str_value, TRUE);
158  g_free(str_value);
159 
160  str_value = g_strdup_printf("%d", (int)getpid());
161  env = g_environ_setenv(env, "ROFI_OUTSIDE", str_value, TRUE);
162  g_free(str_value);
163 
164  if (entry && entry->info) {
165  env = g_environ_setenv(env, "ROFI_INFO", entry->info, TRUE);
166  }
167 
168  if (g_shell_parse_argv(sw->ed, &argc, &argv, &error)) {
169  argv = g_realloc(argv, (argc + 2) * sizeof(char *));
170  argv[argc] = g_strdup(arg);
171  argv[argc + 1] = NULL;
172  g_spawn_async_with_pipes(NULL, argv, env, G_SPAWN_SEARCH_PATH, NULL, NULL,
173  NULL, NULL, &fd, NULL, &error);
174  }
175  g_strfreev(env);
176  if (error != NULL) {
177  char *msg = g_strdup_printf("Failed to execute: '%s'\nError: '%s'",
178  (char *)sw->ed, error->message);
179  rofi_view_error_dialog(msg, FALSE);
180  g_free(msg);
181  // print error.
182  g_error_free(error);
183  fd = -1;
184  }
185  if (fd >= 0) {
186  FILE *inp = fdopen(fd, "r");
187  if (inp) {
188  char *buffer = NULL;
189  size_t buffer_length = 0;
190  ssize_t read_length = 0;
191  size_t actual_size = 0;
192  while ((read_length = getdelim(&buffer, &buffer_length, pd->delim, inp)) >
193  0) {
194  // Filter out line-end.
195  if (buffer[read_length - 1] == pd->delim) {
196  buffer[read_length - 1] = '\0';
197  }
198  if (buffer[0] == '\0') {
199  parse_header_entry(sw, &buffer[1], read_length - 1);
200  } else {
201  if (actual_size < ((*length) + 2)) {
202  actual_size += 256;
203  retv = g_realloc(retv, (actual_size) * sizeof(DmenuScriptEntry));
204  }
205  if (retv) {
206  size_t buf_length = strlen(buffer) + 1;
207  retv[(*length)].entry = g_memdup(buffer, buf_length);
208  retv[(*length)].icon_name = NULL;
209  retv[(*length)].meta = NULL;
210  retv[(*length)].info = NULL;
211  retv[(*length)].icon_fetch_uid = 0;
212  retv[(*length)].nonselectable = FALSE;
213  if (buf_length > 0 && (read_length > (ssize_t)buf_length)) {
214  dmenuscript_parse_entry_extras(sw, &(retv[(*length)]),
215  buffer + buf_length,
216  read_length - buf_length);
217  }
218  retv[(*length) + 1].entry = NULL;
219  (*length)++;
220  }
221  }
222  }
223  if (buffer) {
224  free(buffer);
225  }
226  if (fclose(inp) != 0) {
227  g_warning("Failed to close stdout off executor script: '%s'",
228  g_strerror(errno));
229  }
230  }
231  }
232  g_strfreev(argv);
233  return retv;
234 }
235 
236 static void script_switcher_free(Mode *sw) {
237  if (sw == NULL) {
238  return;
239  }
240  g_free(sw->name);
241  g_free(sw->ed);
242  g_free(sw);
243 }
244 
245 static int script_mode_init(Mode *sw) {
246  if (sw->private_data == NULL) {
247  ScriptModePrivateData *pd = g_malloc0(sizeof(*pd));
248  pd->delim = '\n';
249  sw->private_data = (void *)pd;
250  pd->cmd_list = execute_executor(sw, NULL, &(pd->cmd_list_length), 0, NULL);
251  }
252  return TRUE;
253 }
254 static unsigned int script_mode_get_num_entries(const Mode *sw) {
255  const ScriptModePrivateData *rmpd =
256  (const ScriptModePrivateData *)sw->private_data;
257  return rmpd->cmd_list_length;
258 }
259 
262 
263  rmpd->num_urgent_list = 0;
264  g_free(rmpd->urgent_list);
265  rmpd->urgent_list = NULL;
266  rmpd->num_active_list = 0;
267  g_free(rmpd->active_list);
268  rmpd->active_list = NULL;
269 }
270 
271 static ModeMode script_mode_result(Mode *sw, int mretv, char **input,
272  unsigned int selected_line) {
274  ModeMode retv = MODE_EXIT;
275  DmenuScriptEntry *new_list = NULL;
276  unsigned int new_length = 0;
277 
278  if ((mretv & MENU_CUSTOM_COMMAND)) {
279  if (rmpd->use_hot_keys) {
281  if (selected_line != UINT32_MAX) {
282  new_list = execute_executor(sw, rmpd->cmd_list[selected_line].entry,
283  &new_length, 10 + (mretv & MENU_LOWER_MASK),
284  &(rmpd->cmd_list[selected_line]));
285  } else {
286  if (rmpd->no_custom == FALSE) {
287  new_list = execute_executor(sw, *input, &new_length,
288  10 + (mretv & MENU_LOWER_MASK), NULL);
289  } else {
290  return RELOAD_DIALOG;
291  }
292  }
293  } else {
294  retv = (mretv & MENU_LOWER_MASK);
295  return retv;
296  }
297  } else if ((mretv & MENU_OK) && rmpd->cmd_list[selected_line].entry != NULL) {
298  if (rmpd->cmd_list[selected_line].nonselectable) {
299  return RELOAD_DIALOG;
300  }
302  new_list =
303  execute_executor(sw, rmpd->cmd_list[selected_line].entry, &new_length,
304  1, &(rmpd->cmd_list[selected_line]));
305  } else if ((mretv & MENU_CUSTOM_INPUT) && *input != NULL &&
306  *input[0] != '\0') {
307  if (rmpd->no_custom == FALSE) {
309  new_list = execute_executor(sw, *input, &new_length, 2, NULL);
310  } else {
311  return RELOAD_DIALOG;
312  }
313  }
314 
315  // If a new list was generated, use that an loop around.
316  if (new_list != NULL) {
317  for (unsigned int i = 0; i < rmpd->cmd_list_length; i++) {
318  g_free(rmpd->cmd_list[i].entry);
319  g_free(rmpd->cmd_list[i].icon_name);
320  g_free(rmpd->cmd_list[i].meta);
321  }
322  g_free(rmpd->cmd_list);
323 
324  rmpd->cmd_list = new_list;
325  rmpd->cmd_list_length = new_length;
326  retv = RESET_DIALOG;
327  }
328  return retv;
329 }
330 
331 static void script_mode_destroy(Mode *sw) {
333  if (rmpd != NULL) {
334  for (unsigned int i = 0; i < rmpd->cmd_list_length; i++) {
335  g_free(rmpd->cmd_list[i].entry);
336  g_free(rmpd->cmd_list[i].icon_name);
337  g_free(rmpd->cmd_list[i].meta);
338  }
339  g_free(rmpd->cmd_list);
340  g_free(rmpd->message);
341  g_free(rmpd->prompt);
342  g_free(rmpd->urgent_list);
343  g_free(rmpd->active_list);
344  g_free(rmpd);
345  sw->private_data = NULL;
346  }
347 }
348 static inline unsigned int get_index(unsigned int length, int index) {
349  if (index >= 0) {
350  return index;
351  }
352  if (((unsigned int)-index) <= length) {
353  return length + index;
354  }
355  // Out of range.
356  return UINT_MAX;
357 }
358 static char *_get_display_value(const Mode *sw, unsigned int selected_line,
359  G_GNUC_UNUSED int *state,
360  G_GNUC_UNUSED GList **list, int get_entry) {
362  for (unsigned int i = 0; i < pd->num_active_list; i++) {
363  unsigned int start =
365  unsigned int stop = get_index(pd->cmd_list_length, pd->active_list[i].stop);
366  if (selected_line >= start && selected_line <= stop) {
367  *state |= ACTIVE;
368  }
369  }
370  for (unsigned int i = 0; i < pd->num_urgent_list; i++) {
371  unsigned int start =
373  unsigned int stop = get_index(pd->cmd_list_length, pd->urgent_list[i].stop);
374  if (selected_line >= start && selected_line <= stop) {
375  *state |= URGENT;
376  }
377  }
378  if (pd->do_markup) {
379  *state |= MARKUP;
380  }
381  return get_entry ? g_strdup(pd->cmd_list[selected_line].entry) : NULL;
382 }
383 
384 static int script_token_match(const Mode *sw, rofi_int_matcher **tokens,
385  unsigned int index) {
387  int match = 1;
388  if (tokens) {
389  for (int j = 0; match && tokens != NULL && tokens[j] != NULL; j++) {
390  rofi_int_matcher *ftokens[2] = {tokens[j], NULL};
391  int test = 0;
392  test = helper_token_match(ftokens, rmpd->cmd_list[index].entry);
393  if (test == tokens[j]->invert && rmpd->cmd_list[index].meta) {
394  test = helper_token_match(ftokens, rmpd->cmd_list[index].meta);
395  }
396 
397  if (test == 0) {
398  match = 0;
399  }
400  }
401  }
402  return match;
403 }
404 static char *script_get_message(const Mode *sw) {
406  return g_strdup(pd->message);
407 }
408 static cairo_surface_t *
409 script_get_icon(const Mode *sw, unsigned int selected_line, int height) {
412  g_return_val_if_fail(pd->cmd_list != NULL, NULL);
413  DmenuScriptEntry *dr = &(pd->cmd_list[selected_line]);
414  if (dr->icon_name == NULL) {
415  return NULL;
416  }
417  if (dr->icon_fetch_uid > 0) {
419  }
422 }
423 
424 #include "mode-private.h"
425 Mode *script_mode_parse_setup(const char *str) {
426  Mode *sw = g_malloc0(sizeof(*sw));
427  char *endp = NULL;
428  char *parse = g_strdup(str);
429  unsigned int index = 0;
430  const char *const sep = ":";
431  for (char *token = strtok_r(parse, sep, &endp); token != NULL;
432  token = strtok_r(NULL, sep, &endp)) {
433  if (index == 0) {
434  sw->name = g_strdup(token);
435  } else if (index == 1) {
436  sw->ed = (void *)rofi_expand_path(token);
437  }
438  index++;
439  }
440  g_free(parse);
441  if (index == 2) {
443  sw->_init = script_mode_init;
450  sw->_get_completion = NULL, sw->_preprocess_input = NULL,
452 
453  return sw;
454  }
455  fprintf(
456  stderr,
457  "The script command '%s' has %u options, but needs 2: <name>:<script>.",
458  str, index);
460  return NULL;
461 }
462 
463 gboolean script_mode_is_valid(const char *token) {
464  return strchr(token, ':') != NULL;
465 }
void parse_ranges(char *input, rofi_range_pair **list, unsigned int *length)
Definition: helper.c:1141
char helper_parse_char(const char *arg)
Definition: helper.c:360
char * rofi_expand_path(const char *input)
Definition: helper.c:717
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
Definition: helper.c:495
cairo_surface_t * rofi_icon_fetcher_get(const uint32_t uid)
uint32_t rofi_icon_fetcher_query(const char *name, const int size)
void * mode_get_private_data(const Mode *mode)
Definition: mode.c:131
ModeMode
Definition: mode.h:49
@ MENU_CUSTOM_COMMAND
Definition: mode.h:79
@ MENU_LOWER_MASK
Definition: mode.h:87
@ MENU_OK
Definition: mode.h:67
@ MENU_CUSTOM_INPUT
Definition: mode.h:73
@ MODE_EXIT
Definition: mode.h:51
@ RELOAD_DIALOG
Definition: mode.h:55
@ RESET_DIALOG
Definition: mode.h:59
Mode * script_mode_parse_setup(const char *str)
Definition: script.c:425
gboolean script_mode_is_valid(const char *token)
Definition: script.c:463
@ URGENT
Definition: textbox.h:105
@ ACTIVE
Definition: textbox.h:107
@ MARKUP
Definition: textbox.h:111
int rofi_view_error_dialog(const char *msg, int markup)
Definition: view.c:2077
static void script_mode_destroy(Mode *sw)
Definition: script.c:331
static cairo_surface_t * script_get_icon(const Mode *sw, unsigned int selected_line, int height)
Definition: script.c:409
static void script_switcher_free(Mode *sw)
Definition: script.c:236
static void parse_header_entry(Mode *sw, char *line, ssize_t length)
Definition: script.c:109
static ModeMode script_mode_result(Mode *sw, int mretv, char **input, unsigned int selected_line)
Definition: script.c:271
void dmenuscript_parse_entry_extras(G_GNUC_UNUSED Mode *sw, DmenuScriptEntry *entry, char *buffer, G_GNUC_UNUSED size_t length)
Definition: script.c:80
static unsigned int get_index(unsigned int length, int index)
Definition: script.c:348
static char * _get_display_value(const Mode *sw, unsigned int selected_line, G_GNUC_UNUSED int *state, G_GNUC_UNUSED GList **list, int get_entry)
Definition: script.c:358
static char * script_get_message(const Mode *sw)
Definition: script.c:404
static DmenuScriptEntry * execute_executor(Mode *sw, char *arg, unsigned int *length, int value, DmenuScriptEntry *entry)
Definition: script.c:142
static void script_mode_reset_highlight(Mode *sw)
Definition: script.c:260
static int script_mode_init(Mode *sw)
Definition: script.c:245
static int script_token_match(const Mode *sw, rofi_int_matcher **tokens, unsigned int index)
Definition: script.c:384
static unsigned int script_mode_get_num_entries(const Mode *sw)
Definition: script.c:254
struct rofi_range_pair * urgent_list
Definition: script.c:61
gboolean no_custom
Definition: script.c:72
struct rofi_range_pair * active_list
Definition: script.c:64
gboolean use_hot_keys
Definition: script.c:74
gboolean do_markup
Definition: script.c:69
unsigned int num_urgent_list
Definition: script.c:62
unsigned int id
Definition: script.c:54
unsigned int cmd_list_length
Definition: script.c:58
unsigned int num_active_list
Definition: script.c:65
DmenuScriptEntry * cmd_list
Definition: script.c:56
_mode_result _result
Definition: mode-private.h:177
__mode_get_num_entries _get_num_entries
Definition: mode-private.h:175
__mode_destroy _destroy
Definition: mode-private.h:173
_mode_preprocess_input _preprocess_input
Definition: mode-private.h:187
char * display_name
Definition: mode-private.h:165
_mode_free free
Definition: mode-private.h:199
_mode_token_match _token_match
Definition: mode-private.h:179
_mode_get_display_value _get_display_value
Definition: mode-private.h:181
_mode_get_icon _get_icon
Definition: mode-private.h:183
_mode_get_completion _get_completion
Definition: mode-private.h:185
void * ed
Definition: mode-private.h:201
__mode_init _init
Definition: mode-private.h:171
char * name
Definition: mode-private.h:163
_mode_get_message _get_message
Definition: mode-private.h:189
void * private_data
Definition: mode-private.h:192