rofi  1.7.5
run.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 
34 #define G_LOG_DOMAIN "Modes.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 "helper.h"
50 #include "history.h"
51 #include "modes/filebrowser.h"
52 #include "modes/run.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  uint32_t icon_fetch_size;
69  /* Surface holding the icon. */
70  cairo_surface_t *icon;
71 } RunEntry;
72 
76 typedef struct {
80  unsigned int cmd_list_length;
81 
83  gboolean file_complete;
84  uint32_t selected_line;
85  char *old_input;
86 
90 
97 static gboolean exec_cmd(const char *cmd, int run_in_term) {
98  GError *error = NULL;
99  if (!cmd || !cmd[0]) {
100  return FALSE;
101  }
102  gsize lf_cmd_size = 0;
103  gchar *lf_cmd = g_locale_from_utf8(cmd, -1, NULL, &lf_cmd_size, &error);
104  if (error != NULL) {
105  g_warning("Failed to convert command to locale encoding: %s",
106  error->message);
107  g_error_free(error);
108  return FALSE;
109  }
110 
111  char *path = g_build_filename(cache_dir, RUN_CACHE_FILE, NULL);
112  RofiHelperExecuteContext context = {.name = NULL};
113  // FIXME: assume startup notification support for terminals
114  if (helper_execute_command(NULL, lf_cmd, run_in_term,
115  run_in_term ? &context : NULL)) {
121  history_set(path, cmd);
122  g_free(path);
123  g_free(lf_cmd);
124  return TRUE;
125  }
126  history_remove(path, cmd);
127  g_free(path);
128  g_free(lf_cmd);
129  return FALSE;
130 }
131 
137 static void delete_entry(const RunEntry *cmd) {
138  char *path = g_build_filename(cache_dir, RUN_CACHE_FILE, NULL);
139 
140  history_remove(path, cmd->entry);
141 
142  g_free(path);
143 }
144 
155 static int sort_func(const void *a, const void *b, G_GNUC_UNUSED void *data) {
156  const RunEntry *astr = (const RunEntry *)a;
157  const RunEntry *bstr = (const RunEntry *)b;
158 
159  if (astr->entry == NULL && bstr->entry == NULL) {
160  return 0;
161  }
162  if (astr->entry == NULL) {
163  return 1;
164  }
165  if (bstr->entry == NULL) {
166  return -1;
167  }
168  return g_strcmp0(astr->entry, bstr->entry);
169 }
170 
174 static RunEntry *get_apps_external(RunEntry *retv, unsigned int *length,
175  unsigned int num_favorites) {
177  if (fd >= 0) {
178  FILE *inp = fdopen(fd, "r");
179  if (inp) {
180  char *buffer = NULL;
181  size_t buffer_length = 0;
182 
183  while (getline(&buffer, &buffer_length, inp) > 0) {
184  int found = 0;
185  // Filter out line-end.
186  if (buffer[strlen(buffer) - 1] == '\n') {
187  buffer[strlen(buffer) - 1] = '\0';
188  }
189 
190  // This is a nice little penalty, but doable? time will tell.
191  // given num_favorites is max 25.
192  for (unsigned int j = 0; found == 0 && j < num_favorites; j++) {
193  if (strcasecmp(buffer, retv[j].entry) == 0) {
194  found = 1;
195  }
196  }
197 
198  if (found == 1) {
199  continue;
200  }
201 
202  // No duplicate, add it.
203  retv = g_realloc(retv, ((*length) + 2) * sizeof(RunEntry));
204  retv[(*length)].entry = g_strdup(buffer);
205  retv[(*length)].icon = NULL;
206  retv[(*length)].icon_fetch_uid = 0;
207  retv[(*length)].icon_fetch_size = 0;
208 
209  (*length)++;
210  }
211  if (buffer != NULL) {
212  free(buffer);
213  }
214  if (fclose(inp) != 0) {
215  g_warning("Failed to close stdout off executor script: '%s'",
216  g_strerror(errno));
217  }
218  }
219  }
220  retv[(*length)].entry = NULL;
221  retv[(*length)].icon = NULL;
222  retv[(*length)].icon_fetch_uid = 0;
223  retv[(*length)].icon_fetch_size = 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)].icon_fetch_size = 0;
335  retv[(*length) + 1].entry = NULL;
336  retv[(*length) + 1].icon = NULL;
337  retv[(*length) + 1].icon_fetch_uid = 0;
338  retv[(*length) + 1].icon_fetch_size = 0;
339  (*length)++;
340  }
341 
342  closedir(dir);
343  }
344  }
345  g_free(homedir);
346 
347  // Get external apps.
348  if (config.run_list_command != NULL && config.run_list_command[0] != '\0') {
349  retv = get_apps_external(retv, length, num_favorites);
350  }
351  // No sorting needed.
352  if ((*length) == 0) {
353  return retv;
354  }
355  // TODO: check this is still fast enough. (takes 1ms on laptop.)
356  if ((*length) > num_favorites) {
357  g_qsort_with_data(&(retv[num_favorites]), (*length) - num_favorites,
358  sizeof(RunEntry), sort_func, NULL);
359  }
360  g_free(path);
361 
362  unsigned int removed = 0;
363  for (unsigned int index = num_favorites; index < ((*length) - 1); index++) {
364  if (g_strcmp0(retv[index].entry, retv[index + 1].entry) == 0) {
365  g_free(retv[index].entry);
366  retv[index].entry = NULL;
367  removed++;
368  }
369  }
370 
371  if ((*length) > num_favorites) {
372  g_qsort_with_data(&(retv[num_favorites]), (*length) - num_favorites,
373  sizeof(RunEntry), sort_func, NULL);
374  }
375  // Reduce array length;
376  (*length) -= removed;
377 
378  TICK_N("stop");
379  return retv;
380 }
381 
382 static int run_mode_init(Mode *sw) {
383  if (sw->private_data == NULL) {
384  RunModePrivateData *pd = g_malloc0(sizeof(*pd));
385  sw->private_data = (void *)pd;
386  pd->cmd_list = get_apps(&(pd->cmd_list_length));
387  pd->completer = NULL;
388  }
389 
390  return TRUE;
391 }
392 static void run_mode_destroy(Mode *sw) {
394  if (rmpd != NULL) {
395  for (unsigned int i = 0; i < rmpd->cmd_list_length; i++) {
396  g_free(rmpd->cmd_list[i].entry);
397  if (rmpd->cmd_list[i].icon != NULL) {
398  cairo_surface_destroy(rmpd->cmd_list[i].icon);
399  }
400  }
401  g_free(rmpd->cmd_list);
402  g_free(rmpd->old_input);
403  g_free(rmpd->old_completer_input);
404  if (rmpd->completer != NULL) {
405  mode_destroy(rmpd->completer);
406  g_free(rmpd->completer);
407  }
408  g_free(rmpd);
409  sw->private_data = NULL;
410  }
411 }
412 
413 static unsigned int run_mode_get_num_entries(const Mode *sw) {
414  const RunModePrivateData *rmpd = (const RunModePrivateData *)sw->private_data;
415  if (rmpd->file_complete) {
416  return rmpd->completer->_get_num_entries(rmpd->completer);
417  }
418  return rmpd->cmd_list_length;
419 }
420 
421 static ModeMode run_mode_result(Mode *sw, int mretv, char **input,
422  unsigned int selected_line) {
424  ModeMode retv = MODE_EXIT;
425 
426  gboolean run_in_term = ((mretv & MENU_CUSTOM_ACTION) == MENU_CUSTOM_ACTION);
427  if (rmpd->file_complete == TRUE) {
428 
429  retv = RELOAD_DIALOG;
430 
431  if ((mretv & (MENU_COMPLETE))) {
432  g_free(rmpd->old_completer_input);
433  rmpd->old_completer_input = *input;
434  *input = NULL;
435  if (rmpd->selected_line < rmpd->cmd_list_length) {
436  (*input) = g_strdup(rmpd->old_input);
437  }
438  rmpd->file_complete = FALSE;
439  } else if ((mretv & MENU_CANCEL)) {
440  retv = MODE_EXIT;
441  } else {
442  char *path = NULL;
443  retv = file_browser_mode_completer(rmpd->completer, mretv, input,
444  selected_line, &path);
445  if (retv == MODE_EXIT) {
446  if (path == NULL) {
447  exec_cmd(rmpd->cmd_list[rmpd->selected_line].entry, run_in_term);
448  } else {
449  char *arg = g_strdup_printf(
450  "%s '%s'", rmpd->cmd_list[rmpd->selected_line].entry, path);
451  exec_cmd(arg, run_in_term);
452  g_free(arg);
453  }
454  }
455  g_free(path);
456  }
457  return retv;
458  }
459 
460  if ((mretv & MENU_OK) && rmpd->cmd_list[selected_line].entry != NULL) {
461  if (!exec_cmd(rmpd->cmd_list[selected_line].entry, run_in_term)) {
462  retv = RELOAD_DIALOG;
463  }
464  } else if ((mretv & MENU_CUSTOM_INPUT) && *input != NULL &&
465  *input[0] != '\0') {
466  if (!exec_cmd(*input, run_in_term)) {
467  retv = RELOAD_DIALOG;
468  }
469  } else if ((mretv & MENU_ENTRY_DELETE) &&
470  rmpd->cmd_list[selected_line].entry) {
471  delete_entry(&(rmpd->cmd_list[selected_line]));
472 
473  // Clear the list.
474  retv = RELOAD_DIALOG;
475  run_mode_destroy(sw);
476  run_mode_init(sw);
477  } else if (mretv & MENU_CUSTOM_COMMAND) {
478  retv = (mretv & MENU_LOWER_MASK);
479  } else if ((mretv & MENU_COMPLETE)) {
480  retv = RELOAD_DIALOG;
481  if (selected_line < rmpd->cmd_list_length) {
482  rmpd->selected_line = selected_line;
483 
484  g_free(rmpd->old_input);
485  rmpd->old_input = g_strdup(*input);
486 
487  if (*input)
488  g_free(*input);
489  *input = g_strdup(rmpd->old_completer_input);
490 
492  mode_init(rmpd->completer);
493  rmpd->file_complete = TRUE;
494  }
495  }
496  return retv;
497 }
498 
499 static char *_get_display_value(const Mode *sw, unsigned int selected_line,
500  G_GNUC_UNUSED int *state,
501  G_GNUC_UNUSED GList **list, int get_entry) {
502  const RunModePrivateData *rmpd = (const RunModePrivateData *)sw->private_data;
503  if (rmpd->file_complete) {
504  return rmpd->completer->_get_display_value(rmpd->completer, selected_line,
505  state, list, get_entry);
506  }
507  return get_entry ? g_strdup(rmpd->cmd_list[selected_line].entry) : NULL;
508 }
509 
510 static int run_token_match(const Mode *sw, rofi_int_matcher **tokens,
511  unsigned int index) {
512  const RunModePrivateData *rmpd = (const RunModePrivateData *)sw->private_data;
513  if (rmpd->file_complete) {
514  return rmpd->completer->_token_match(rmpd->completer, tokens, index);
515  }
516  return helper_token_match(tokens, rmpd->cmd_list[index].entry);
517 }
518 static char *run_get_message(const Mode *sw) {
520  if (pd->file_complete) {
521  if (pd->selected_line < pd->cmd_list_length) {
522  char *msg = mode_get_message(pd->completer);
523  if (msg) {
524  char *retv =
525  g_strdup_printf("File complete for: %s\n%s",
526  pd->cmd_list[pd->selected_line].entry, msg);
527  g_free(msg);
528  return retv;
529  }
530  return g_strdup_printf("File complete for: %s",
531  pd->cmd_list[pd->selected_line].entry);
532  }
533  }
534  return NULL;
535 }
536 static cairo_surface_t *_get_icon(const Mode *sw, unsigned int selected_line,
537  unsigned int height) {
539  if (pd->file_complete) {
540  return pd->completer->_get_icon(pd->completer, selected_line, height);
541  }
542  g_return_val_if_fail(pd->cmd_list != NULL, NULL);
543  RunEntry *dr = &(pd->cmd_list[selected_line]);
544 
545  if (dr->icon_fetch_uid > 0 && dr->icon_fetch_size == height) {
546  cairo_surface_t *icon = rofi_icon_fetcher_get(dr->icon_fetch_uid);
547  return icon;
548  }
550  char **str = g_strsplit(dr->entry, " ", 2);
551  if (str) {
552  dr->icon_fetch_uid = rofi_icon_fetcher_query(str[0], height);
553  dr->icon_fetch_size = height;
554  g_strfreev(str);
555  cairo_surface_t *icon = rofi_icon_fetcher_get(dr->icon_fetch_uid);
556  return icon;
557  }
558  return NULL;
559 }
560 
561 #include "mode-private.h"
562 Mode run_mode = {.name = "run",
563  .cfg_name_key = "display-run",
564  ._init = run_mode_init,
565  ._get_num_entries = run_mode_get_num_entries,
566  ._result = run_mode_result,
567  ._destroy = run_mode_destroy,
568  ._token_match = run_token_match,
569  ._get_message = run_get_message,
570  ._get_display_value = _get_display_value,
571  ._get_icon = _get_icon,
572  ._get_completion = NULL,
573  ._preprocess_input = NULL,
574  .private_data = NULL,
575  .free = NULL};
ModeMode file_browser_mode_completer(Mode *sw, int mretv, char **input, unsigned int selected_line, char **path)
Definition: filebrowser.c:618
Mode * create_new_file_browser(void)
Definition: filebrowser.c:608
gboolean helper_execute_command(const char *wd, const char *cmd, gboolean run_in_term, RofiHelperExecuteContext *context)
Definition: helper.c:1030
int execute_generator(const char *cmd)
Definition: helper.c:539
char * rofi_expand_path(const char *input)
Definition: helper.c:740
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
Definition: helper.c:518
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:52
int mode_init(Mode *mode)
Definition: mode.c:43
char * mode_get_message(const Mode *mode)
Definition: mode.c:201
void * mode_get_private_data(const Mode *mode)
Definition: mode.c:159
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:510
static int sort_func(const void *a, const void *b, G_GNUC_UNUSED void *data)
Definition: run.c:155
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:499
static void run_mode_destroy(Mode *sw)
Definition: run.c:392
static unsigned int run_mode_get_num_entries(const Mode *sw)
Definition: run.c:413
#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:421
static char * run_get_message(const Mode *sw)
Definition: run.c:518
static gboolean exec_cmd(const char *cmd, int run_in_term)
Definition: run.c:97
static void delete_entry(const RunEntry *cmd)
Definition: run.c:137
static RunEntry * get_apps(unsigned int *length)
Definition: run.c:230
Mode run_mode
Definition: run.c:562
static cairo_surface_t * _get_icon(const Mode *sw, unsigned int selected_line, unsigned int height)
Definition: run.c:536
static RunEntry * get_apps_external(RunEntry *retv, unsigned int *length, unsigned int num_favorites)
Definition: run.c:174
static int run_mode_init(Mode *sw)
Definition: run.c:382
#define TICK_N(a)
Definition: timings.h:69
struct _icon icon
Definition: icon.h:44
Settings config
const gchar * name
Definition: helper.h:288
Definition: run.c:65
uint32_t icon_fetch_size
Definition: run.c:68
char * entry
Definition: run.c:66
uint32_t icon_fetch_uid
Definition: run.c:67
cairo_surface_t * icon
Definition: run.c:70
unsigned int cmd_list_length
Definition: run.c:80
char * old_input
Definition: run.c:85
uint32_t selected_line
Definition: run.c:84
Mode * completer
Definition: run.c:87
char * old_completer_input
Definition: run.c:88
gboolean file_complete
Definition: run.c:83
RunEntry * cmd_list
Definition: run.c:78
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