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