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
65typedef struct {
66 char *entry;
69 /* Surface holding the icon. */
70 cairo_surface_t *icon;
71} RunEntry;
72
90
97static 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
137static 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
155static 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
174static 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
230static 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
382static 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}
392static 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
413static 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
421static 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;
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
499static 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
510static 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}
518static 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}
536static 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"
562Mode 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};
Mode * create_new_file_browser(void)
ModeMode file_browser_mode_completer(Mode *sw, int mretv, char **input, unsigned int selected_line, char **path)
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
void * mode_get_private_data(const Mode *mode)
Definition mode.c:159
char * mode_get_message(const Mode *mode)
Definition mode.c:201
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 RunEntry * get_apps(unsigned int *length)
Definition run.c:230
static int sort_func(const void *a, const void *b, G_GNUC_UNUSED void *data)
Definition run.c:155
static char * run_get_message(const Mode *sw)
Definition run.c:518
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 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 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 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
Mode run_mode
Definition run.c:562
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
_mode_token_match _token_match
_mode_get_display_value _get_display_value
_mode_get_icon _get_icon
char * name
void * private_data