rofi  1.7.0
run.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 
34 #define G_LOG_DOMAIN "Dialogs.Run"
35 
36 #include <config.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 
40 #include <dirent.h>
41 #include <errno.h>
42 #include <limits.h>
43 #include <signal.h>
44 #include <string.h>
45 #include <strings.h>
46 #include <sys/types.h>
47 #include <unistd.h>
48 
49 #include "dialogs/filebrowser.h"
50 #include "dialogs/run.h"
51 #include "helper.h"
52 #include "history.h"
53 #include "rofi.h"
54 #include "settings.h"
55 
56 #include "mode-private.h"
57 
58 #include "rofi-icon-fetcher.h"
59 #include "timings.h"
63 #define RUN_CACHE_FILE "rofi-3.runcache"
64 
65 typedef struct {
66  char *entry;
67  uint32_t icon_fetch_uid;
68  /* Surface holding the icon. */
69  cairo_surface_t *icon;
70 } RunEntry;
71 
75 typedef struct {
79  unsigned int cmd_list_length;
80 
82  gboolean file_complete;
83  uint32_t selected_line;
84  char *old_input;
85 
90  cairo_surface_t *fallback_icon;
92 
99 static gboolean exec_cmd(const char *cmd, int run_in_term) {
100  GError *error = NULL;
101  if (!cmd || !cmd[0]) {
102  return FALSE;
103  }
104  gsize lf_cmd_size = 0;
105  gchar *lf_cmd = g_locale_from_utf8(cmd, -1, NULL, &lf_cmd_size, &error);
106  if (error != NULL) {
107  g_warning("Failed to convert command to locale encoding: %s",
108  error->message);
109  g_error_free(error);
110  return FALSE;
111  }
112 
113  char *path = g_build_filename(cache_dir, RUN_CACHE_FILE, NULL);
114  RofiHelperExecuteContext context = {.name = NULL};
115  // FIXME: assume startup notification support for terminals
116  if (helper_execute_command(NULL, lf_cmd, run_in_term,
117  run_in_term ? &context : NULL)) {
123  history_set(path, cmd);
124  g_free(path);
125  g_free(lf_cmd);
126  return TRUE;
127  }
128  history_remove(path, cmd);
129  g_free(path);
130  g_free(lf_cmd);
131  return FALSE;
132 }
133 
139 static void delete_entry(const RunEntry *cmd) {
140  char *path = g_build_filename(cache_dir, RUN_CACHE_FILE, NULL);
141 
142  history_remove(path, cmd->entry);
143 
144  g_free(path);
145 }
146 
157 static int sort_func(const void *a, const void *b, G_GNUC_UNUSED void *data) {
158  const RunEntry *astr = (const RunEntry *)a;
159  const RunEntry *bstr = (const RunEntry *)b;
160 
161  if (astr->entry == NULL && bstr->entry == NULL) {
162  return 0;
163  }
164  if (astr->entry == NULL) {
165  return 1;
166  }
167  if (bstr->entry == NULL) {
168  return -1;
169  }
170  return g_strcmp0(astr->entry, bstr->entry);
171 }
172 
176 static RunEntry *get_apps_external(RunEntry *retv, unsigned int *length,
177  unsigned int num_favorites) {
179  if (fd >= 0) {
180  FILE *inp = fdopen(fd, "r");
181  if (inp) {
182  char *buffer = NULL;
183  size_t buffer_length = 0;
184 
185  while (getline(&buffer, &buffer_length, inp) > 0) {
186  int found = 0;
187  // Filter out line-end.
188  if (buffer[strlen(buffer) - 1] == '\n') {
189  buffer[strlen(buffer) - 1] = '\0';
190  }
191 
192  // This is a nice little penalty, but doable? time will tell.
193  // given num_favorites is max 25.
194  for (unsigned int j = 0; found == 0 && j < num_favorites; j++) {
195  if (strcasecmp(buffer, retv[j].entry) == 0) {
196  found = 1;
197  }
198  }
199 
200  if (found == 1) {
201  continue;
202  }
203 
204  // No duplicate, add it.
205  retv = g_realloc(retv, ((*length) + 2) * sizeof(RunEntry));
206  retv[(*length)].entry = g_strdup(buffer);
207  retv[(*length)].icon = NULL;
208  retv[(*length)].icon_fetch_uid = 0;
209 
210  (*length)++;
211  }
212  if (buffer != NULL) {
213  free(buffer);
214  }
215  if (fclose(inp) != 0) {
216  g_warning("Failed to close stdout off executor script: '%s'",
217  g_strerror(errno));
218  }
219  }
220  }
221  retv[(*length)].entry = NULL;
222  retv[(*length)].icon = NULL;
223  retv[(*length)].icon_fetch_uid = 0;
224  return retv;
225 }
226 
230 static RunEntry *get_apps(unsigned int *length) {
231  GError *error = NULL;
232  RunEntry *retv = NULL;
233  unsigned int num_favorites = 0;
234  char *path;
235 
236  if (g_getenv("PATH") == NULL) {
237  return NULL;
238  }
239  TICK_N("start");
240  path = g_build_filename(cache_dir, RUN_CACHE_FILE, NULL);
241  char **hretv = history_get_list(path, length);
242  retv = (RunEntry *)g_malloc0((*length + 1) * sizeof(RunEntry));
243  for (unsigned int i = 0; i < *length; i++) {
244  retv[i].entry = hretv[i];
245  }
246  g_free(hretv);
247  g_free(path);
248  // Keep track of how many where loaded as favorite.
249  num_favorites = (*length);
250 
251  path = g_strdup(g_getenv("PATH"));
252 
253  gsize l = 0;
254  gchar *homedir = g_locale_to_utf8(g_get_home_dir(), -1, NULL, &l, &error);
255  if (error != NULL) {
256  g_debug("Failed to convert homedir to UTF-8: %s", error->message);
257  for (unsigned int i = 0; retv[i].entry != NULL; i++) {
258  g_free(retv[i].entry);
259  }
260  g_free(retv);
261  g_clear_error(&error);
262  g_free(homedir);
263  return NULL;
264  }
265 
266  const char *const sep = ":";
267  char *strtok_savepointer = NULL;
268  for (const char *dirname = strtok_r(path, sep, &strtok_savepointer);
269  dirname != NULL; dirname = strtok_r(NULL, sep, &strtok_savepointer)) {
270  char *fpath = rofi_expand_path(dirname);
271  DIR *dir = opendir(fpath);
272  g_debug("Checking path %s for executable.", fpath);
273  g_free(fpath);
274 
275  if (dir != NULL) {
276  struct dirent *dent;
277  gsize dirn_len = 0;
278  gchar *dirn = g_locale_to_utf8(dirname, -1, NULL, &dirn_len, &error);
279  if (error != NULL) {
280  g_debug("Failed to convert directory name to UTF-8: %s",
281  error->message);
282  g_clear_error(&error);
283  closedir(dir);
284  continue;
285  }
286  gboolean is_homedir = g_str_has_prefix(dirn, homedir);
287  g_free(dirn);
288 
289  while ((dent = readdir(dir)) != NULL) {
290  if (dent->d_type != DT_REG && dent->d_type != DT_LNK &&
291  dent->d_type != DT_UNKNOWN) {
292  continue;
293  }
294  // Skip dot files.
295  if (dent->d_name[0] == '.') {
296  continue;
297  }
298  if (is_homedir) {
299  gchar *full_path = g_build_filename(dirname, dent->d_name, NULL);
300  gboolean b = g_file_test(full_path, G_FILE_TEST_IS_EXECUTABLE);
301  g_free(full_path);
302  if (!b) {
303  continue;
304  }
305  }
306 
307  gsize name_len;
308  gchar *name =
309  g_filename_to_utf8(dent->d_name, -1, NULL, &name_len, &error);
310  if (error != NULL) {
311  g_debug("Failed to convert filename to UTF-8: %s", error->message);
312  g_clear_error(&error);
313  g_free(name);
314  continue;
315  }
316  // This is a nice little penalty, but doable? time will tell.
317  // given num_favorites is max 25.
318  int found = 0;
319  for (unsigned int j = 0; found == 0 && j < num_favorites; j++) {
320  if (g_strcmp0(name, retv[j].entry) == 0) {
321  found = 1;
322  }
323  }
324 
325  if (found == 1) {
326  g_free(name);
327  continue;
328  }
329 
330  retv = g_realloc(retv, ((*length) + 2) * sizeof(RunEntry));
331  retv[(*length)].entry = name;
332  retv[(*length)].icon = NULL;
333  retv[(*length)].icon_fetch_uid = 0;
334  retv[(*length) + 1].entry = NULL;
335  retv[(*length) + 1].icon = NULL;
336  retv[(*length) + 1].icon_fetch_uid = 0;
337  (*length)++;
338  }
339 
340  closedir(dir);
341  }
342  }
343  g_free(homedir);
344 
345  // Get external apps.
346  if (config.run_list_command != NULL && config.run_list_command[0] != '\0') {
347  retv = get_apps_external(retv, length, num_favorites);
348  }
349  // No sorting needed.
350  if ((*length) == 0) {
351  return retv;
352  }
353  // TODO: check this is still fast enough. (takes 1ms on laptop.)
354  if ((*length) > num_favorites) {
355  g_qsort_with_data(&(retv[num_favorites]), (*length) - num_favorites,
356  sizeof(RunEntry), sort_func, NULL);
357  }
358  g_free(path);
359 
360  unsigned int removed = 0;
361  for (unsigned int index = num_favorites; index < ((*length) - 1); index++) {
362  if (g_strcmp0(retv[index].entry, retv[index + 1].entry) == 0) {
363  g_free(retv[index].entry);
364  retv[index].entry = NULL;
365  removed++;
366  }
367  }
368 
369  if ((*length) > num_favorites) {
370  g_qsort_with_data(&(retv[num_favorites]), (*length) - num_favorites,
371  sizeof(RunEntry), sort_func, NULL);
372  }
373  // Reduce array length;
374  (*length) -= removed;
375 
376  TICK_N("stop");
377  return retv;
378 }
379 
380 static int run_mode_init(Mode *sw) {
381  if (sw->private_data == NULL) {
382  RunModePrivateData *pd = g_malloc0(sizeof(*pd));
383  sw->private_data = (void *)pd;
384  pd->cmd_list = get_apps(&(pd->cmd_list_length));
386  mode_init(pd->completer);
387  }
388 
389  return TRUE;
390 }
391 static void run_mode_destroy(Mode *sw) {
393  if (rmpd != NULL) {
394  for (unsigned int i = 0; i < rmpd->cmd_list_length; i++) {
395  g_free(rmpd->cmd_list[i].entry);
396  if (rmpd->cmd_list[i].icon != NULL) {
397  cairo_surface_destroy(rmpd->cmd_list[i].icon);
398  }
399  }
400  g_free(rmpd->cmd_list);
401  g_free(rmpd->old_input);
402  g_free(rmpd->old_completer_input);
403  mode_destroy(rmpd->completer);
404  g_free(rmpd);
405  sw->private_data = NULL;
406  }
407 }
408 
409 static unsigned int run_mode_get_num_entries(const Mode *sw) {
410  const RunModePrivateData *rmpd = (const RunModePrivateData *)sw->private_data;
411  if (rmpd->file_complete) {
412  return rmpd->completer->_get_num_entries(rmpd->completer);
413  }
414  return rmpd->cmd_list_length;
415 }
416 
417 static ModeMode run_mode_result(Mode *sw, int mretv, char **input,
418  unsigned int selected_line) {
420  ModeMode retv = MODE_EXIT;
421 
422  gboolean run_in_term = ((mretv & MENU_CUSTOM_ACTION) == MENU_CUSTOM_ACTION);
423  if (rmpd->file_complete == TRUE) {
424 
425  retv = RELOAD_DIALOG;
426 
427  if ((mretv & (MENU_COMPLETE))) {
428  g_free(rmpd->old_completer_input);
429  rmpd->old_completer_input = *input;
430  *input = NULL;
431  if (rmpd->selected_line < rmpd->cmd_list_length) {
432  (*input) = g_strdup(rmpd->old_input);
433  }
434  rmpd->file_complete = FALSE;
435  } else if ((mretv & MENU_CANCEL)) {
436  retv = MODE_EXIT;
437  } else {
438  char *path = NULL;
439  retv = file_browser_mode_completer(rmpd->completer, mretv, input,
440  selected_line, &path);
441  if (retv == MODE_EXIT) {
442  if (path == NULL) {
443  exec_cmd(rmpd->cmd_list[rmpd->selected_line].entry, run_in_term);
444  } else {
445  char *arg = g_strdup_printf(
446  "%s '%s'", rmpd->cmd_list[rmpd->selected_line].entry, path);
447  exec_cmd(arg, run_in_term);
448  g_free(arg);
449  }
450  }
451  g_free(path);
452  }
453  return retv;
454  }
455 
456  if ((mretv & MENU_OK) && rmpd->cmd_list[selected_line].entry != NULL) {
457  if (!exec_cmd(rmpd->cmd_list[selected_line].entry, run_in_term)) {
458  retv = RELOAD_DIALOG;
459  }
460  } else if ((mretv & MENU_CUSTOM_INPUT) && *input != NULL &&
461  *input[0] != '\0') {
462  if (!exec_cmd(*input, run_in_term)) {
463  retv = RELOAD_DIALOG;
464  }
465  } else if ((mretv & MENU_ENTRY_DELETE) &&
466  rmpd->cmd_list[selected_line].entry) {
467  delete_entry(&(rmpd->cmd_list[selected_line]));
468 
469  // Clear the list.
470  retv = RELOAD_DIALOG;
471  run_mode_destroy(sw);
472  run_mode_init(sw);
473  } else if (mretv & MENU_CUSTOM_COMMAND) {
474  retv = (mretv & MENU_LOWER_MASK);
475  } else if ((mretv & MENU_COMPLETE)) {
476  retv = RELOAD_DIALOG;
477  if (selected_line < rmpd->cmd_list_length) {
478  rmpd->selected_line = selected_line;
479 
480  g_free(rmpd->old_input);
481  rmpd->old_input = g_strdup(*input);
482 
483  if (*input)
484  g_free(*input);
485  *input = g_strdup(rmpd->old_completer_input);
486 
487  rmpd->file_complete = TRUE;
488  }
489  }
490  return retv;
491 }
492 
493 static char *_get_display_value(const Mode *sw, unsigned int selected_line,
494  G_GNUC_UNUSED int *state,
495  G_GNUC_UNUSED GList **list, int get_entry) {
496  const RunModePrivateData *rmpd = (const RunModePrivateData *)sw->private_data;
497  if (rmpd->file_complete) {
498  return rmpd->completer->_get_display_value(rmpd->completer, selected_line,
499  state, list, get_entry);
500  }
501  return get_entry ? g_strdup(rmpd->cmd_list[selected_line].entry) : NULL;
502 }
503 
504 static int run_token_match(const Mode *sw, rofi_int_matcher **tokens,
505  unsigned int index) {
506  const RunModePrivateData *rmpd = (const RunModePrivateData *)sw->private_data;
507  if (rmpd->file_complete) {
508  return rmpd->completer->_token_match(rmpd->completer, tokens, index);
509  }
510  return helper_token_match(tokens, rmpd->cmd_list[index].entry);
511 }
512 static char *run_get_message(const Mode *sw) {
514  if (pd->file_complete) {
515  if (pd->selected_line < pd->cmd_list_length) {
516  char *msg = mode_get_message(pd->completer);
517  if (msg) {
518  char *retv =
519  g_strdup_printf("File complete for: %s\n%s",
520  pd->cmd_list[pd->selected_line].entry, msg);
521  g_free(msg);
522  return retv;
523  }
524  return g_strdup_printf("File complete for: %s",
525  pd->cmd_list[pd->selected_line].entry);
526  }
527  }
528  return NULL;
529 }
530 static cairo_surface_t *fallback_icon(RunModePrivateData *pd, int height) {
532  // FALLBACK
533  if (pd->fallback_icon_fetch_uid > 0) {
535  }
538  }
539  return NULL;
540 }
541 static cairo_surface_t *_get_icon(const Mode *sw, unsigned int selected_line,
542  int height) {
544  if (pd->file_complete) {
545  return pd->completer->_get_icon(pd->completer, selected_line, height);
546  }
547  g_return_val_if_fail(pd->cmd_list != NULL, NULL);
548  RunEntry *dr = &(pd->cmd_list[selected_line]);
549 
550  if (dr->icon_fetch_uid > 0) {
551  cairo_surface_t *icon = rofi_icon_fetcher_get(dr->icon_fetch_uid);
552  if (icon) {
553  return icon;
554  }
555  return fallback_icon(pd, height);
556  }
558  char **str = g_strsplit(dr->entry, " ", 2);
559  if (str) {
560  dr->icon_fetch_uid = rofi_icon_fetcher_query(str[0], height);
561  g_strfreev(str);
562  cairo_surface_t *icon = rofi_icon_fetcher_get(dr->icon_fetch_uid);
563  if (icon) {
564  return icon;
565  }
566  }
567  return fallback_icon(pd, height);
568 }
569 
570 #include "mode-private.h"
571 Mode run_mode = {.name = "run",
572  .cfg_name_key = "display-run",
573  ._init = run_mode_init,
574  ._get_num_entries = run_mode_get_num_entries,
575  ._result = run_mode_result,
576  ._destroy = run_mode_destroy,
577  ._token_match = run_token_match,
578  ._get_message = run_get_message,
579  ._get_display_value = _get_display_value,
580  ._get_icon = _get_icon,
581  ._get_completion = NULL,
582  ._preprocess_input = NULL,
583  .private_data = NULL,
584  .free = NULL};
ModeMode file_browser_mode_completer(Mode *sw, int mretv, char **input, unsigned int selected_line, char **path)
Definition: filebrowser.c:570
Mode * create_new_file_browser(void)
Definition: filebrowser.c:560
gboolean helper_execute_command(const char *wd, const char *cmd, gboolean run_in_term, RofiHelperExecuteContext *context)
Definition: helper.c:1012
int execute_generator(const char *cmd)
Definition: helper.c:515
char * rofi_expand_path(const char *input)
Definition: helper.c:713
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
Definition: helper.c:494
void history_set(const char *filename, const char *entry)
Definition: history.c:178
void history_remove(const char *filename, const char *entry)
Definition: history.c:259
char ** history_get_list(const char *filename, unsigned int *length)
Definition: history.c:323
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_destroy(Mode *mode)
Definition: mode.c:48
int mode_init(Mode *mode)
Definition: mode.c:42
char * mode_get_message(const Mode *mode)
Definition: mode.c:173
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_COMPLETE
Definition: mode.h:83
@ MENU_LOWER_MASK
Definition: mode.h:87
@ MENU_CANCEL
Definition: mode.h:69
@ MENU_ENTRY_DELETE
Definition: mode.h:75
@ MENU_CUSTOM_ACTION
Definition: mode.h:85
@ 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
const char * cache_dir
Definition: rofi.c:83
static int run_token_match(const Mode *sw, rofi_int_matcher **tokens, unsigned int index)
Definition: run.c:504
static cairo_surface_t * _get_icon(const Mode *sw, unsigned int selected_line, int height)
Definition: run.c:541
static int sort_func(const void *a, const void *b, G_GNUC_UNUSED void *data)
Definition: run.c:157
static cairo_surface_t * fallback_icon(RunModePrivateData *pd, int height)
Definition: run.c:530
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: run.c:493
static void run_mode_destroy(Mode *sw)
Definition: run.c:391
static unsigned int run_mode_get_num_entries(const Mode *sw)
Definition: run.c:409
#define RUN_CACHE_FILE
Definition: run.c:63
static ModeMode run_mode_result(Mode *sw, int mretv, char **input, unsigned int selected_line)
Definition: run.c:417
static char * run_get_message(const Mode *sw)
Definition: run.c:512
static gboolean exec_cmd(const char *cmd, int run_in_term)
Definition: run.c:99
static void delete_entry(const RunEntry *cmd)
Definition: run.c:139
static RunEntry * get_apps(unsigned int *length)
Definition: run.c:230
Mode run_mode
Definition: run.c:571
static RunEntry * get_apps_external(RunEntry *retv, unsigned int *length, unsigned int num_favorites)
Definition: run.c:176
static int run_mode_init(Mode *sw)
Definition: run.c:380
#define TICK_N(a)
Definition: timings.h:69
struct _icon icon
Definition: icon.h:44
Settings config
const gchar * name
Definition: helper.h:296
Definition: run.c:65
char * entry
Definition: run.c:66
uint32_t icon_fetch_uid
Definition: run.c:67
cairo_surface_t * icon
Definition: run.c:69
unsigned int cmd_list_length
Definition: run.c:79
char * old_input
Definition: run.c:84
cairo_surface_t * fallback_icon
Definition: run.c:90
uint32_t selected_line
Definition: run.c:83
uint32_t fallback_icon_fetch_uid
Definition: run.c:89
Mode * completer
Definition: run.c:86
char * old_completer_input
Definition: run.c:87
gboolean file_complete
Definition: run.c:82
RunEntry * cmd_list
Definition: run.c:77
char * application_fallback_icon
Definition: settings.h:177
char * run_list_command
Definition: settings.h:75
Definition: icon.c:39
__mode_get_num_entries _get_num_entries
Definition: mode-private.h:175
_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
char * name
Definition: mode-private.h:163
void * private_data
Definition: mode-private.h:192