rofi 2.1.0
dmenu.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
29#define G_LOG_DOMAIN "Modes.DMenu"
30#include "config.h"
31
32#include "display.h"
33#include "helper.h"
34#include "modes/dmenu.h"
35#include "rofi-icon-fetcher.h"
36#include "rofi.h"
37#include "settings.h"
38#include "view.h"
39#include "widgets/textbox.h"
40#include "xrmoptions.h"
41#include <ctype.h>
42#include <errno.h>
43#include <fcntl.h>
44#include <gio/gio.h>
45#include <gio/gunixinputstream.h>
46#include <glib-unix.h>
47#include <stdint.h>
48#include <stdio.h>
49#include <stdlib.h>
50#include <string.h>
51#include <strings.h>
52#include <sys/stat.h>
53#include <sys/types.h>
54#include <unistd.h>
55
57
58static int dmenu_mode_init(Mode *sw);
59static int dmenu_token_match(const Mode *sw, rofi_int_matcher **tokens,
60 unsigned int index);
61static cairo_surface_t *
62dmenu_get_icon(const Mode *sw, unsigned int selected_line, unsigned int height);
63static char *dmenu_get_message(const Mode *sw);
64
65static inline unsigned int bitget(uint32_t const *const array,
66 unsigned int index) {
67 uint32_t bit = index % 32;
68 uint32_t val = array[index / 32];
69 return (val >> bit) & 1;
70}
71
72static inline void bittoggle(uint32_t *const array, unsigned int index) {
73 uint32_t bit = index % 32;
74 uint32_t *v = &array[index / 32];
75 *v ^= 1 << bit;
76}
77
78typedef struct {
80 // Separator.
82
83 unsigned int selected_line;
84 char *message;
85 char *format;
87 unsigned int num_urgent_list;
89 unsigned int num_active_list;
90 uint32_t *selected_list;
91 unsigned int num_selected_list;
92 unsigned int do_markup;
93 // List with entries.
96 unsigned int cmd_list_length;
97 unsigned int only_selected;
98 unsigned int selected_count;
99
100 gchar **columns;
102 gboolean multi_select;
103
105 GAsyncQueue *async_queue;
106 gboolean async;
107 FILE *fd_file;
108 int fd;
109 int pipefd[2];
110 int pipefd2[2];
112 gboolean loading;
113
117
120#define BLOCK_LINES_SIZE 2048
126
127static void read_add_block(DmenuModePrivateData *pd, Block **block, char *data,
128 gsize len) {
129
130 if ((*block) == NULL) {
131 (*block) = g_malloc0(sizeof(Block));
132 (*block)->pd = pd;
133 (*block)->length = 0;
134 }
135 gsize data_len = len;
136 // Init.
137 (*block)->values[(*block)->length].icon_fetch_uid = 0;
138 (*block)->values[(*block)->length].icon_fetch_size = 0;
139 (*block)->values[(*block)->length].icon_fetch_scale = 0;
140 (*block)->values[(*block)->length].icon_fallback_index = 0;
141 (*block)->values[(*block)->length].icon_name = NULL;
142 (*block)->values[(*block)->length].meta = NULL;
143 (*block)->values[(*block)->length].info = NULL;
144 (*block)->values[(*block)->length].nonselectable = FALSE;
145 (*block)->values[(*block)->length].permanent = FALSE;
146 char *end = data;
147 while (end < data + len && *end != '\0') {
148 end++;
149 }
150 if (end != data + len) {
151 data_len = end - data;
152 dmenuscript_parse_entry_extras(NULL, &((*block)->values[(*block)->length]),
153 end + 1, len - data_len);
154 }
155 char *utfstr = rofi_force_utf8(data, data_len);
156 (*block)->values[(*block)->length].entry = utfstr;
157
158 (*block)->length++;
159}
160
161static void read_add(DmenuModePrivateData *pd, char *data, gsize len) {
162 gsize data_len = len;
163 if ((pd->cmd_list_length + 2) > pd->cmd_list_real_length) {
164 pd->cmd_list_real_length = MAX(pd->cmd_list_real_length * 2, 512);
165 pd->cmd_list = g_realloc(pd->cmd_list, (pd->cmd_list_real_length) *
166 sizeof(DmenuScriptEntry));
167 }
168 // Init.
173 pd->cmd_list[pd->cmd_list_length].icon_name = NULL;
174 pd->cmd_list[pd->cmd_list_length].display = NULL;
175 pd->cmd_list[pd->cmd_list_length].meta = NULL;
176 pd->cmd_list[pd->cmd_list_length].info = NULL;
177 pd->cmd_list[pd->cmd_list_length].active = FALSE;
178 pd->cmd_list[pd->cmd_list_length].urgent = FALSE;
179 pd->cmd_list[pd->cmd_list_length].nonselectable = FALSE;
180 char *end = data;
181 while (end < data + len && *end != '\0') {
182 end++;
183 }
184 if (end != data + len) {
185 data_len = end - data;
187 end + 1, len - data_len);
188 }
189 char *utfstr = rofi_force_utf8(data, data_len);
190 pd->cmd_list[pd->cmd_list_length].entry = utfstr;
191 pd->cmd_list[pd->cmd_list_length + 1].entry = NULL;
192
193 pd->cmd_list_length++;
194}
195
205static gboolean dmenu_async_read_proc(gint fd, GIOCondition condition,
206 gpointer user_data) {
208 char command;
209 // Only interrested in read events.
210 if ((condition & G_IO_IN) != G_IO_IN) {
211 return G_SOURCE_CONTINUE;
212 }
213 // Read the entry from the pipe that was used to signal this action.
214 if (read(fd, &command, 1) == 1) {
215 if (command == 'r') {
216 Block *block = NULL;
217 gboolean changed = FALSE;
218 // Empty out the AsyncQueue (that is thread safe) from all blocks pushed
219 // into it.
220 while ((block = g_async_queue_try_pop(pd->async_queue)) != NULL) {
221
222 if (pd->cmd_list_real_length < (pd->cmd_list_length + block->length)) {
223 pd->cmd_list_real_length = MAX(pd->cmd_list_real_length * 2, 4096);
224 pd->cmd_list = g_realloc(pd->cmd_list, sizeof(DmenuScriptEntry) *
226 }
227 memcpy(&(pd->cmd_list[pd->cmd_list_length]), &(block->values[0]),
228 sizeof(DmenuScriptEntry) * block->length);
229 pd->cmd_list_length += block->length;
230 g_free(block);
231 changed = TRUE;
232 }
233 if (changed) {
235 }
236 } else if (command == 'q') {
237 if (pd->loading) {
239 }
240 }
241 }
242 return G_SOURCE_CONTINUE;
243}
244
245static void read_input_sync(DmenuModePrivateData *pd, unsigned int pre_read) {
246 ssize_t nread = 0;
247 size_t len = 0;
248 char *line = NULL;
249 while (pre_read > 0 &&
250 (nread = getdelim(&line, &len, pd->separator, pd->fd_file)) != -1) {
251 if (line[nread - 1] == pd->separator) {
252 nread--;
253 line[nread] = '\0';
254 }
255 read_add(pd, line, nread);
256 pre_read--;
257 }
258 free(line);
259 return;
260}
261static gpointer read_input_thread(gpointer userdata) {
263 ssize_t nread = 0;
264 ssize_t len = 0;
265 char *line = NULL;
266 Block *block = NULL;
267
268 GTimer *tim = g_timer_new();
269 int fd = pd->fd;
270 while (1) {
271 // Wait for input from the input or from the main thread.
272 fd_set rfds;
273 // We wait for 0.25 seconds, before we flush what we have.
274 struct timeval tv = {.tv_sec = 0, .tv_usec = 250000};
275
276 FD_ZERO(&rfds);
277 FD_SET(fd, &rfds);
278 FD_SET(pd->pipefd[0], &rfds);
279
280 int retval = select(MAX(fd, pd->pipefd[0]) + 1, &rfds, NULL, NULL, &tv);
281 if (retval == -1) {
282 g_warning("select failed, giving up.");
283 break;
284 } else if (retval) {
285 // We get input from the UI thread, this is always an abort.
286 if (FD_ISSET(pd->pipefd[0], &rfds)) {
287 break;
288 }
289 // Input data is available.
290 if (FD_ISSET(fd, &rfds)) {
291 ssize_t readbytes = 0;
292 if ((nread + 1024) > len) {
293 line = g_realloc(line, (len + 2048));
294 len = len + 2048;
295 }
296 readbytes = read(fd, &line[nread], 1023);
297 if (readbytes > 0) {
298 nread += readbytes;
299 line[nread] = '\0';
300 ssize_t i = 0;
301 while (i < nread) {
302 if (line[i] == pd->separator) {
303 line[i] = '\0';
304 read_add_block(pd, &block, line, i);
305 memmove(&line[0], &line[i + 1], nread - (i + 1));
306 nread -= (i + 1);
307 i = 0;
308 if (block) {
309 double elapsed = g_timer_elapsed(tim, NULL);
310 if (elapsed >= 0.1 || block->length == BLOCK_LINES_SIZE) {
311 g_timer_start(tim);
312 g_async_queue_push(pd->async_queue, block);
313 block = NULL;
314 write(pd->pipefd2[1], "r", 1);
315 }
316 }
317 } else {
318 i++;
319 }
320 }
321 } else {
322 // remainder in buffer, then quit.
323 if (nread > 0) {
324 line[nread] = '\0';
325 read_add_block(pd, &block, line, nread);
326 }
327 if (block) {
328 g_timer_start(tim);
329 g_async_queue_push(pd->async_queue, block);
330 block = NULL;
331 write(pd->pipefd2[1], "r", 1);
332 }
333 break;
334 }
335 }
336 } else {
337 // Timeout, pushout remainder data.
338 if (nread > 0) {
339 line[nread] = '\0';
340 read_add_block(pd, &block, line, nread);
341 nread = 0;
342 }
343 if (block) {
344 g_timer_start(tim);
345 g_async_queue_push(pd->async_queue, block);
346 block = NULL;
347 write(pd->pipefd2[1], "r", 1);
348 }
349 }
350 }
351 g_timer_destroy(tim);
352 free(line);
353 write(pd->pipefd2[1], "q", 1);
354 return NULL;
355}
356
357static unsigned int dmenu_mode_get_num_entries(const Mode *sw) {
358 const DmenuModePrivateData *rmpd =
360 unsigned int retv = rmpd->cmd_list_length;
361 return retv;
362}
363
365 const char *input,
366 const unsigned int index,
367 gboolean multi_select) {
368 if (pd->columns == NULL) {
369 if (multi_select) {
370 if (index < pd->num_selected_list &&
371 bitget(pd->selected_list, index) == TRUE) {
372 return g_strdup_printf("%s%s", pd->ballot_selected, input);
373 } else {
374 return g_strdup_printf("%s%s", pd->ballot_unselected, input);
375 }
376 }
377 return g_strdup(input);
378 }
379 char *retv = NULL;
380 char **splitted =
381 g_regex_split_simple(pd->column_separator, input, G_REGEX_CASELESS, 00);
382 uint32_t ns = 0;
383 for (; splitted && splitted[ns]; ns++) {
384 ;
385 }
386 GString *str_retv = g_string_new("");
387
388 if (multi_select) {
389 if (index < pd->num_selected_list &&
390 bitget(pd->selected_list, index) == TRUE) {
391 g_string_append(str_retv, pd->ballot_selected);
392 } else {
393 g_string_append(str_retv, pd->ballot_unselected);
394 }
395 }
396 for (uint32_t i = 0; pd->columns && pd->columns[i]; i++) {
397 unsigned int col_index =
398 (unsigned int)g_ascii_strtoull(pd->columns[i], NULL, 10);
399 if (col_index <= ns && col_index > 0) {
400 if (i == 0) {
401 g_string_append(str_retv, splitted[col_index - 1]);
402 } else {
403 g_string_append_c(str_retv, '\t');
404 g_string_append(str_retv, splitted[col_index - 1]);
405 }
406 }
407 }
408 g_strfreev(splitted);
409 retv = str_retv->str;
410 g_string_free(str_retv, FALSE);
411 return retv;
412}
413
414static inline unsigned int get_index(unsigned int length, int index) {
415 if (index >= 0) {
416 return index;
417 }
418 if (((unsigned int)-index) <= length) {
419 return length + index;
420 }
421 // Out of range.
422 return UINT_MAX;
423}
424
425static char *dmenu_get_completion_data(const Mode *data, unsigned int index) {
426 Mode *sw = (Mode *)data;
429 if (retv[index].display) {
430 return dmenu_format_output_string(pd, retv[index].display, index, FALSE);
431 } else {
432 return dmenu_format_output_string(pd, retv[index].entry, index, FALSE);
433 }
434}
435
436static char *get_display_data(const Mode *data, unsigned int index, int *state,
437 G_GNUC_UNUSED GList **list, int get_entry) {
438 Mode *sw = (Mode *)data;
441 for (unsigned int i = 0; i < pd->num_active_list; i++) {
442 unsigned int start =
444 unsigned int stop = get_index(pd->cmd_list_length, pd->active_list[i].stop);
445 if (index >= start && index <= stop) {
446 *state |= ACTIVE;
447 }
448 }
449 for (unsigned int i = 0; i < pd->num_urgent_list; i++) {
450 unsigned int start =
452 unsigned int stop = get_index(pd->cmd_list_length, pd->urgent_list[i].stop);
453 if (index >= start && index <= stop) {
454 *state |= URGENT;
455 }
456 }
457 if (index < pd->num_selected_list &&
458 bitget(pd->selected_list, index) == TRUE) {
459 *state |= SELECTED;
460 }
461 if (pd->do_markup) {
462 *state |= MARKUP;
463 }
464 if (pd->cmd_list[index].urgent) {
465 *state |= URGENT;
466 }
467 if (pd->cmd_list[index].active) {
468 *state |= ACTIVE;
469 }
470 char *my_retv = NULL;
471 if (retv[index].display) {
472 my_retv = (get_entry ? dmenu_format_output_string(pd, retv[index].display,
473 index, pd->multi_select)
474 : NULL);
475 } else {
476 my_retv = (get_entry ? dmenu_format_output_string(pd, retv[index].entry,
477 index, pd->multi_select)
478 : NULL);
479 }
480 return my_retv;
481}
482
483static void dmenu_mode_free(Mode *sw) {
484 if (mode_get_private_data(sw) == NULL) {
485 return;
486 }
488 if (pd != NULL) {
489
490 for (size_t i = 0; i < pd->cmd_list_length; i++) {
491 if (pd->cmd_list[i].entry) {
492 g_free(pd->cmd_list[i].entry);
493 g_strfreev(pd->cmd_list[i].icon_name);
494 g_free(pd->cmd_list[i].display);
495 g_free(pd->cmd_list[i].meta);
496 g_free(pd->cmd_list[i].info);
497 }
498 }
499 g_free(pd->cmd_list);
500 g_free(pd->urgent_list);
501 g_free(pd->active_list);
502 g_free(pd->selected_list);
503
504 g_free(pd);
505 mode_set_private_data(sw, NULL);
506 }
507}
508
509#include "mode-private.h"
511Mode dmenu_mode = {.name = "dmenu",
512 .cfg_name_key = "display-combi",
513 ._init = dmenu_mode_init,
514 ._get_num_entries = dmenu_mode_get_num_entries,
515 ._result = NULL,
516 ._destroy = dmenu_mode_free,
517 ._token_match = dmenu_token_match,
518 ._get_display_value = get_display_data,
519 ._get_icon = dmenu_get_icon,
520 ._get_completion = dmenu_get_completion_data,
521 ._preprocess_input = NULL,
522 ._get_message = dmenu_get_message,
523 .private_data = NULL,
524 .free = NULL,
525 .display_name = "dmenu",
526 .type = MODE_TYPE_DMENU};
527
528static int dmenu_mode_init(Mode *sw) {
529 if (mode_get_private_data(sw) != NULL) {
530 return TRUE;
531 }
532 mode_set_private_data(sw, g_malloc0(sizeof(DmenuModePrivateData)));
534
535 pd->async = TRUE;
536 pd->multi_select = FALSE;
537
538 // For now these only work in sync mode.
539 if (find_arg("-sync") >= 0 || find_arg("-dump") >= 0 ||
540 find_arg("-select") >= 0 || find_arg("-no-custom") >= 0 ||
541 find_arg("-only-match") >= 0 || config.auto_select ||
542 find_arg("-selected-row") >= 0) {
543 pd->async = FALSE;
544 }
545 if (find_arg("-multi-select") >= 0) {
546 pd->multi_select = TRUE;
547 }
548
549 pd->separator = '\n';
550 pd->selected_line = UINT32_MAX;
551
552 find_arg_str("-mesg", &(pd->message));
553
554 // Input data separator.
555 find_arg_char("-sep", &(pd->separator));
556
557 find_arg_uint("-selected-row", &(pd->selected_line));
558 // By default we print the unescaped line back.
559 pd->format = "s";
560
561 // Allow user to override the output format.
562 find_arg_str("-format", &(pd->format));
563 // Urgent.
564 char *str = NULL;
565 find_arg_str("-u", &str);
566 if (str != NULL) {
567 parse_ranges(str, &(pd->urgent_list), &(pd->num_urgent_list));
568 }
569 // Active
570 str = NULL;
571 find_arg_str("-a", &str);
572 if (str != NULL) {
573 parse_ranges(str, &(pd->active_list), &(pd->num_active_list));
574 }
575
576 // DMENU COMPATIBILITY
577 unsigned int lines = DEFAULT_MENU_LINES;
578 find_arg_uint("-l", &(lines));
579 if (lines != DEFAULT_MENU_LINES) {
581 p->name = g_strdup("lines");
582 p->value.i = lines;
584 GHashTable *table =
585 g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
586 (GDestroyNotify)rofi_theme_property_free);
587
588 g_hash_table_replace(table, p->name, p);
590 g_hash_table_destroy(table);
591 }
592
593 str = NULL;
594 find_arg_str("-window-title", &str);
595 if (str) {
597 }
598
603 if (find_arg("-b") >= 0) {
604 config.location = 6;
605 }
606 /* -i case insensitive */
607 config.case_sensitive = TRUE;
608 if (find_arg("-i") >= 0) {
609 config.case_sensitive = FALSE;
610 }
611 if (pd->async) {
612 pd->fd = STDIN_FILENO;
613 if (find_arg_str("-input", &str)) {
614 char *estr = rofi_expand_path(str);
615 pd->fd = open(str, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
616 if (pd->fd == -1) {
617 char *msg = g_markup_printf_escaped(
618 "Failed to open file: <b>%s</b>:\n\t<i>%s</i>", estr,
619 g_strerror(errno));
620 rofi_view_error_dialog(msg, TRUE);
621 g_free(msg);
622 g_free(estr);
623 return TRUE;
624 }
625 g_free(estr);
626 }
627
628 if (pipe(pd->pipefd) == -1) {
629 g_error("Failed to create pipe");
630 }
631 if (pipe(pd->pipefd2) == -1) {
632 g_error("Failed to create pipe");
633 }
634 pd->wake_source =
635 g_unix_fd_add(pd->pipefd2[0], G_IO_IN, dmenu_async_read_proc, pd);
636 // Create the message passing queue to the UI thread.
637 pd->async_queue = g_async_queue_new();
638 pd->reading_thread =
639 g_thread_new("dmenu-read", (GThreadFunc)read_input_thread, pd);
640 pd->loading = TRUE;
641 } else {
642 pd->fd_file = stdin;
643 str = NULL;
644 if (find_arg_str("-input", &str)) {
645 char *estr = rofi_expand_path(str);
646 pd->fd_file = fopen(str, "r");
647 if (pd->fd_file == NULL) {
648 char *msg = g_markup_printf_escaped(
649 "Failed to open file: <b>%s</b>:\n\t<i>%s</i>", estr,
650 g_strerror(errno));
651 rofi_view_error_dialog(msg, TRUE);
652 g_free(msg);
653 g_free(estr);
654 return TRUE;
655 }
656 g_free(estr);
657 }
658
659 read_input_sync(pd, -1);
660 }
661 gchar *columns = NULL;
662 if (find_arg_str("-display-columns", &columns)) {
663 pd->columns = g_strsplit(columns, ",", 0);
664 pd->column_separator = "\t";
665 find_arg_str("-display-column-separator", &pd->column_separator);
666 }
667 return TRUE;
668}
669
670static int dmenu_token_match(const Mode *sw, rofi_int_matcher **tokens,
671 unsigned int index) {
674
676 char *esc = NULL;
677 if (rmpd->cmd_list[index].permanent == TRUE) {
678 // Always match
679 return 1;
680 }
681
682 if (rmpd->do_markup) {
683 pango_parse_markup(rmpd->cmd_list[index].entry, -1, 0, NULL, &esc, NULL,
684 NULL);
685 } else {
686 esc = rmpd->cmd_list[index].entry;
687 }
688 if (esc) {
689 // int retv = helper_token_match ( tokens, esc );
690 int match = 1;
691 if (tokens) {
692 for (int j = 0; match && tokens[j] != NULL; j++) {
693 rofi_int_matcher *ftokens[2] = {tokens[j], NULL};
694 int test = 0;
695 test = helper_token_match(ftokens, esc);
696 if (test == tokens[j]->invert && rmpd->cmd_list[index].meta) {
697 test = helper_token_match(ftokens, rmpd->cmd_list[index].meta);
698 }
699
700 if (test == 0) {
701 match = 0;
702 }
703 }
704 }
705 if (rmpd->do_markup) {
706 g_free(esc);
707 }
708 return match;
709 }
710 return FALSE;
711}
712static char *dmenu_get_message(const Mode *sw) {
714 if (pd->message) {
715 return g_strdup(pd->message);
716 }
717 return NULL;
718}
719static cairo_surface_t *dmenu_get_icon(const Mode *sw,
720 unsigned int selected_line,
721 unsigned int height) {
723 const guint scale = display_scale();
724
725 g_return_val_if_fail(pd->cmd_list != NULL, NULL);
726 DmenuScriptEntry *dr = &(pd->cmd_list[selected_line]);
727 if (dr->icon_name == NULL) {
728 return NULL;
729 }
730
731 /* A row icon and icon-current-entry ask for the same entry at different
732 * sizes; one cached uid cannot serve both. */
733 cairo_surface_t *surface = NULL;
734 if (dr->icon_fetch_uid > 0 && dr->icon_fetch_size == height &&
735 dr->icon_fetch_scale == scale) {
736 gboolean query_done =
738
739 if (surface != NULL) {
740 return surface;
741 } else if (query_done) {
743 dr->icon_fetch_uid = 0;
744 } else {
745 return NULL;
746 }
747 }
748
749 char *current_icon = NULL;
750 if (dr->icon_name && dr->icon_fallback_index >= 0) {
751 int icon_count = g_strv_length(dr->icon_name);
752 if (dr->icon_fallback_index < icon_count) {
753 current_icon = dr->icon_name[dr->icon_fallback_index];
754 }
755 }
756 if (current_icon) {
757 dr->icon_fetch_uid = rofi_icon_fetcher_query(current_icon, height);
758 dr->icon_fetch_size = height;
759 dr->icon_fetch_scale = scale;
760 /* The sizes alternate, so returning NULL would blank the icon on every
761 * frame, not just the first. */
763 return surface;
764 }
765
766 dr->icon_fetch_uid = 0;
767 return NULL;
768}
769
771 int retv) {
772
773 if (pd->reading_thread) {
774 // Stop listinig to new messages from reading thread.
775 if (pd->wake_source > 0) {
776 g_source_remove(pd->wake_source);
777 }
778 // signal stop.
779 write(pd->pipefd[1], "q", 1);
780 g_thread_join(pd->reading_thread);
781 pd->reading_thread = NULL;
782 /* empty the queue, remove idle callbacks if still pending. */
783 g_async_queue_lock(pd->async_queue);
784 Block *block = NULL;
785 while ((block = g_async_queue_try_pop_unlocked(pd->async_queue)) != NULL) {
786 g_free(block);
787 }
788 g_async_queue_unlock(pd->async_queue);
789 g_async_queue_unref(pd->async_queue);
790 pd->async_queue = NULL;
791 close(pd->pipefd[0]);
792 close(pd->pipefd[1]);
793 }
794 if (pd->fd_file != NULL) {
795 if (pd->fd_file != stdin) {
796 fclose(pd->fd_file);
797 }
798 }
799 if (retv == FALSE) {
800 rofi_set_return_code(EXIT_FAILURE);
801 } else if (retv >= 10) {
803 } else {
804 rofi_set_return_code(EXIT_SUCCESS);
805 }
807 rofi_view_free(state);
809}
810
811static void dmenu_print_results(DmenuModePrivateData *pd, const char *input) {
812 DmenuScriptEntry *cmd_list = pd->cmd_list;
813 int seen = FALSE;
814 for (unsigned int st = 0; st < pd->num_selected_list; st++) {
815 if (bitget(pd->selected_list, st)) {
816 seen = TRUE;
817 rofi_output_formatted_line(pd->format, cmd_list[st].entry, st, input);
818 }
819 }
820 if (!seen) {
821 const char *cmd = input;
822 if (pd->selected_line != UINT32_MAX) {
823 cmd = cmd_list[pd->selected_line].entry;
824 }
825 if (cmd) {
826 rofi_output_formatted_line(pd->format, cmd, pd->selected_line, input);
827 }
828 }
829}
830
831static void dmenu_finalize(RofiViewState *state) {
832 int retv = FALSE;
835
836 unsigned int cmd_list_length = pd->cmd_list_length;
837 DmenuScriptEntry *cmd_list = pd->cmd_list;
838
839 char *input = g_strdup(rofi_view_get_user_input(state));
841 ;
843 unsigned int next_pos = rofi_view_get_next_position(state);
844 int restart = 0;
845 // Special behavior.
846 if (pd->only_selected) {
850 restart = 1;
851 // Skip if no valid item is selected.
852 if ((mretv & MENU_CANCEL) == MENU_CANCEL) {
853 // In no custom mode we allow canceling.
854 restart = (find_arg("-only-match") >= 0);
855 } else if (pd->selected_line != UINT32_MAX) {
856 if ((mretv & MENU_CUSTOM_ACTION) && pd->multi_select) {
857 restart = TRUE;
858 pd->loading = FALSE;
859 if (pd->num_selected_list != pd->cmd_list_length) {
860 size_t new_length = pd->cmd_list_length / 32 + 1;
861 pd->selected_list =
862 g_realloc(pd->selected_list, sizeof(uint32_t) * (new_length));
863 if (pd->num_selected_list == 0) {
864 memset(pd->selected_list, 0, new_length * sizeof(uint32_t));
865 } else {
866 size_t old_length = pd->num_selected_list / 32 + 1;
867 memset(&pd->selected_list[old_length], 0,
868 (new_length - old_length) * sizeof(uint32_t));
869 }
871 }
872 pd->selected_count +=
873 (bitget(pd->selected_list, pd->selected_line) ? (-1) : (1));
875 // Move to next line.
876 pd->selected_line = MIN(next_pos, cmd_list_length - 1);
877 if (pd->selected_count > 0) {
878 char *str =
879 g_strdup_printf("%u/%u", pd->selected_count, pd->cmd_list_length);
880 rofi_view_set_overlay(state, str);
881 g_free(str);
882 } else {
883 rofi_view_set_overlay(state, NULL);
884 }
885 } else if ((mretv & (MENU_OK | MENU_CUSTOM_COMMAND)) &&
886 cmd_list[pd->selected_line].entry != NULL) {
887 if (cmd_list[pd->selected_line].nonselectable == TRUE) {
888 g_free(input);
889 return;
890 }
891 dmenu_print_results(pd, input);
892 retv = TRUE;
893 if ((mretv & MENU_CUSTOM_COMMAND)) {
894 retv = 10 + (mretv & MENU_LOWER_MASK);
895 }
896 g_free(input);
897 dmenu_finish(pd, state, retv);
898 return;
899 } else {
900 pd->selected_line = next_pos - 1;
901 }
902 }
903 // Restart
904 rofi_view_restart(state);
906 if (!restart) {
907 dmenu_finish(pd, state, retv);
908 }
909 return;
910 }
911 // We normally do not want to restart the loop.
912 restart = FALSE;
913 // Normal mode
914 if ((mretv & MENU_OK) && pd->selected_line != UINT32_MAX &&
915 cmd_list[pd->selected_line].entry != NULL) {
916 // Check if entry is non-selectable.
917 if (cmd_list[pd->selected_line].nonselectable == TRUE) {
918 g_free(input);
919 return;
920 }
921 if ((mretv & MENU_CUSTOM_ACTION) && pd->multi_select) {
922 restart = TRUE;
923 if (pd->num_selected_list != pd->cmd_list_length) {
924 size_t new_length = pd->cmd_list_length / 32 + 1;
925 pd->selected_list =
926 g_realloc(pd->selected_list, sizeof(uint32_t) * (new_length));
927 if (pd->num_selected_list == 0) {
928 memset(pd->selected_list, 0, new_length * sizeof(uint32_t));
929 } else {
930 size_t old_length = pd->num_selected_list / 32 + 1;
931 memset(&pd->selected_list[old_length], 0,
932 (new_length - old_length) * sizeof(uint32_t));
933 }
935 }
936 pd->selected_count +=
937 (bitget(pd->selected_list, pd->selected_line) ? (-1) : (1));
939 // Move to next line.
940 pd->selected_line = MIN(next_pos, cmd_list_length - 1);
941 if (pd->selected_count > 0) {
942 char *str =
943 g_strdup_printf("%u/%u", pd->selected_count, pd->cmd_list_length);
944 rofi_view_set_overlay(state, str);
945 g_free(str);
946 } else {
947 rofi_view_set_overlay(state, NULL);
948 }
949 } else {
950 dmenu_print_results(pd, input);
951 }
952 retv = TRUE;
953 }
954 // Custom input
955 else if ((mretv & (MENU_CUSTOM_INPUT))) {
956 dmenu_print_results(pd, input);
957
958 retv = TRUE;
959 }
960 // Quick switch with entry selected.
961 else if ((mretv & MENU_CUSTOM_COMMAND)) {
962 dmenu_print_results(pd, input);
963
964 restart = FALSE;
965 retv = 10 + (mretv & MENU_LOWER_MASK);
966 }
967 g_free(input);
968 if (restart) {
969 rofi_view_restart(state);
971 } else {
972 dmenu_finish(pd, state, retv);
973 }
974}
975
978 MenuFlags menu_flags = MENU_NORMAL;
980
981 char *input = NULL;
982 unsigned int cmd_list_length = pd->cmd_list_length;
983 DmenuScriptEntry *cmd_list = pd->cmd_list;
984
985 pd->only_selected = FALSE;
986 pd->ballot_selected = "☑ ";
987 pd->ballot_unselected = "☐ ";
988 find_arg_str("-ballot-selected-str", &(pd->ballot_selected));
989 find_arg_str("-ballot-unselected-str", &(pd->ballot_unselected));
990
991 if (find_arg("-markup-rows") >= 0) {
992 pd->do_markup = TRUE;
993 }
994 if (find_arg("-only-match") >= 0 || find_arg("-no-custom") >= 0) {
995 pd->only_selected = TRUE;
996 if (cmd_list_length == 0) {
997 return TRUE;
998 }
999 }
1000 if (config.auto_select && cmd_list_length == 1) {
1001 rofi_output_formatted_line(pd->format, cmd_list[0].entry, 0, config.filter);
1002 return TRUE;
1003 }
1004 if (find_arg("-password") >= 0) {
1005 menu_flags |= MENU_PASSWORD;
1006 }
1007 /* copy filter string */
1008 input = g_strdup(config.filter);
1009
1010 char *select = NULL;
1011 find_arg_str("-select", &select);
1012 if (select != NULL) {
1013 rofi_int_matcher **tokens =
1014 helper_tokenize(select, parse_case_sensitivity(select));
1015 unsigned int i = 0;
1016 for (i = 0; i < cmd_list_length; i++) {
1017 if (helper_token_match(tokens, cmd_list[i].entry)) {
1018 pd->selected_line = i;
1019 break;
1020 }
1021 }
1022 helper_tokenize_free(tokens);
1023 }
1024 if (find_arg("-dump") >= 0) {
1025 char *filter = config.filter ? config.filter : "";
1026 rofi_int_matcher **tokens =
1027 helper_tokenize(filter, parse_case_sensitivity(filter));
1028 unsigned int i = 0;
1029 for (i = 0; i < cmd_list_length; i++) {
1030 if (tokens == NULL || helper_token_match(tokens, cmd_list[i].entry)) {
1031 rofi_output_formatted_line(pd->format, cmd_list[i].entry, i,
1032 config.filter);
1033 }
1034 }
1035 helper_tokenize_free(tokens);
1037 g_free(input);
1038 return TRUE;
1039 }
1041 RofiViewState *state =
1042 rofi_view_create(&dmenu_mode, input, menu_flags, dmenu_finalize);
1043
1044 if (find_arg("-keep-right") >= 0) {
1045 rofi_view_ellipsize_listview(state, PANGO_ELLIPSIZE_START);
1046 }
1047 char *ellipsize_mode = NULL;
1048 if (find_arg_str("-ellipsize-mode", &ellipsize_mode) >= 0) {
1049 if (ellipsize_mode) {
1050 if (g_ascii_strcasecmp(ellipsize_mode, "start") == 0) {
1051 rofi_view_ellipsize_listview(state, PANGO_ELLIPSIZE_START);
1052 } else if (g_ascii_strcasecmp(ellipsize_mode, "middle") == 0) {
1053 rofi_view_ellipsize_listview(state, PANGO_ELLIPSIZE_MIDDLE);
1054 } else if (g_ascii_strcasecmp(ellipsize_mode, "end") == 0) {
1055 rofi_view_ellipsize_listview(state, PANGO_ELLIPSIZE_END);
1056 } else {
1057 g_warning("Unrecognized ellipsize mode: '%s'", ellipsize_mode);
1058 }
1059 }
1060 }
1062 rofi_view_set_active(state);
1063 if (pd->loading) {
1064 rofi_view_set_overlay(state, "Loading.. ");
1065 }
1066
1067 return FALSE;
1068}
1069
1071 int is_term = isatty(fileno(stdout));
1073 "-mesg", "[string]",
1074 "Print a small user message under the prompt (uses pango markup)", NULL,
1075 is_term);
1076 print_help_msg("-p", "[string]", "Prompt to display left of entry field",
1077 NULL, is_term);
1078 print_help_msg("-selected-row", "[integer]", "Select row", NULL, is_term);
1079 print_help_msg("-format", "[string]", "Output format string", "s", is_term);
1080 print_help_msg("-u", "[list]", "List of row indexes to mark urgent", NULL,
1081 is_term);
1082 print_help_msg("-a", "[list]", "List of row indexes to mark active", NULL,
1083 is_term);
1084 print_help_msg("-l", "[integer] ", "Number of rows to display", NULL,
1085 is_term);
1086 print_help_msg("-window-title", "[string] ", "Set the dmenu window title",
1087 NULL, is_term);
1088 print_help_msg("-i", "", "Set filter to be case insensitive", NULL, is_term);
1089 print_help_msg("-only-match", "",
1090 "Force selection to be given entry, disallow no match", NULL,
1091 is_term);
1092 print_help_msg("-no-custom", "", "Don't accept custom entry, allow no match",
1093 NULL, is_term);
1094 print_help_msg("-select", "[string]", "Select the first row that matches",
1095 NULL, is_term);
1096 print_help_msg("-password", "",
1097 "Do not show what the user inputs. Show '*' instead.", NULL,
1098 is_term);
1099 print_help_msg("-markup-rows", "",
1100 "Allow and render pango markup as input data.", NULL, is_term);
1101 print_help_msg("-sep", "[char]", "Element separator.", "'\\n'", is_term);
1102 print_help_msg("-input", "[filename]",
1103 "Read input from file instead from standard input.", NULL,
1104 is_term);
1105 print_help_msg("-sync", "",
1106 "Force dmenu to first read all input data, then show dialog.",
1107 NULL, is_term);
1108 print_help_msg("-w", "windowid", "Position over window with X11 windowid.",
1109 NULL, is_term);
1110 print_help_msg("-keep-right", "", "Set ellipsize to end.", NULL, is_term);
1111 print_help_msg("-display-columns", "", "Only show the selected columns", NULL,
1112 is_term);
1113 print_help_msg("-display-column-separator", "\t",
1114 "Separator to use to split columns (regex)", NULL, is_term);
1115 print_help_msg("-ballot-selected-str", "\t",
1116 "When multi-select is enabled prefix this string when element "
1117 "is selected.",
1118 NULL, is_term);
1119 print_help_msg("-ballot-unselected-str", "\t",
1120 "When multi-select is enabled prefix this string when element "
1121 "is not selected.",
1122 NULL, is_term);
1123 print_help_msg("-ellipsize-mode", "end",
1124 "Set ellipsize mode(start | middle | end).", NULL, is_term);
1125}
guint display_scale(void)
Definition display.c:78
static void dmenu_finish(DmenuModePrivateData *pd, RofiViewState *state, int retv)
Definition dmenu.c:770
static void read_add(DmenuModePrivateData *pd, char *data, gsize len)
Definition dmenu.c:161
static cairo_surface_t * dmenu_get_icon(const Mode *sw, unsigned int selected_line, unsigned int height)
Definition dmenu.c:719
Mode dmenu_mode
Definition dmenu.c:511
static char * get_display_data(const Mode *data, unsigned int index, int *state, G_GNUC_UNUSED GList **list, int get_entry)
Definition dmenu.c:436
static gboolean dmenu_async_read_proc(gint fd, GIOCondition condition, gpointer user_data)
Definition dmenu.c:205
static int dmenu_token_match(const Mode *sw, rofi_int_matcher **tokens, unsigned int index)
Definition dmenu.c:670
static unsigned int get_index(unsigned int length, int index)
Definition dmenu.c:414
static void dmenu_mode_free(Mode *sw)
Definition dmenu.c:483
static void read_add_block(DmenuModePrivateData *pd, Block **block, char *data, gsize len)
Definition dmenu.c:127
static char * dmenu_get_message(const Mode *sw)
Definition dmenu.c:712
static gchar * dmenu_format_output_string(const DmenuModePrivateData *pd, const char *input, const unsigned int index, gboolean multi_select)
Definition dmenu.c:364
#define BLOCK_LINES_SIZE
Definition dmenu.c:120
static char * dmenu_get_completion_data(const Mode *data, unsigned int index)
Definition dmenu.c:425
static gpointer read_input_thread(gpointer userdata)
Definition dmenu.c:261
static void dmenu_print_results(DmenuModePrivateData *pd, const char *input)
Definition dmenu.c:811
static void bittoggle(uint32_t *const array, unsigned int index)
Definition dmenu.c:72
static void dmenu_finalize(RofiViewState *state)
Definition dmenu.c:831
static int dmenu_mode_init(Mode *sw)
Definition dmenu.c:528
static void read_input_sync(DmenuModePrivateData *pd, unsigned int pre_read)
Definition dmenu.c:245
static unsigned int dmenu_mode_get_num_entries(const Mode *sw)
Definition dmenu.c:357
static unsigned int bitget(uint32_t const *const array, unsigned int index)
Definition dmenu.c:65
void print_help_msg(const char *option, const char *type, const char *text, const char *def, int isatty)
int dmenu_mode_dialog(void)
Definition dmenu.c:976
void print_dmenu_options(void)
Definition dmenu.c:1070
void parse_ranges(char *input, rofi_range_pair **list, unsigned int *length)
Definition helper.c:1669
rofi_int_matcher ** helper_tokenize(const char *input, int case_sensitive)
Definition helper.c:286
int find_arg_char(const char *const key, char *val)
Definition helper.c:431
void helper_tokenize_free(rofi_int_matcher **tokens)
Definition helper.c:146
void rofi_output_formatted_line(const char *format, const char *string, int selected_line, const char *filter)
Definition helper.c:1708
char * rofi_expand_path(const char *input)
Definition helper.c:784
int find_arg_str(const char *const key, char **val)
Definition helper.c:334
int parse_case_sensitivity(const char *input)
Definition helper.c:1687
int find_arg_uint(const char *const key, unsigned int *val)
Definition helper.c:373
int find_arg(const char *const key)
Definition helper.c:325
int helper_token_match(rofi_int_matcher *const *tokens, const char *input)
Definition helper.c:541
char * rofi_force_utf8(const gchar *data, ssize_t length)
Definition helper.c:862
gboolean rofi_icon_fetcher_get_ex(const uint32_t uid, cairo_surface_t **surface)
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
struct rofi_mode Mode
Definition mode.h:49
void * mode_get_private_data(const Mode *mode)
Definition mode.c:176
MenuReturn
Definition mode.h:70
void mode_set_private_data(Mode *mode, void *pd)
Definition mode.c:181
@ MENU_CUSTOM_COMMAND
Definition mode.h:84
@ MENU_LOWER_MASK
Definition mode.h:92
@ MENU_CANCEL
Definition mode.h:74
@ MENU_CUSTOM_ACTION
Definition mode.h:90
@ MENU_OK
Definition mode.h:72
@ MENU_CUSTOM_INPUT
Definition mode.h:78
void rofi_set_return_code(int code)
Definition rofi.c:155
@ SELECTED
Definition textbox.h:111
@ URGENT
Definition textbox.h:107
@ ACTIVE
Definition textbox.h:109
@ MARKUP
Definition textbox.h:113
void rofi_view_set_overlay(RofiViewState *state, const char *text)
Definition view.c:2118
Mode * rofi_view_get_mode(RofiViewState *state)
Definition view.c:2092
void rofi_view_reload(void)
Definition view.c:2221
int rofi_view_error_dialog(const char *msg, int markup)
Definition view.c:1980
void rofi_view_set_active(RofiViewState *state)
Definition view.c:347
RofiViewState * rofi_view_get_active(void)
Definition view.c:321
void rofi_view_restart(RofiViewState *state)
Definition view.c:316
MenuFlags
Definition view.h:55
MenuReturn rofi_view_get_return_value(const RofiViewState *state)
Definition view.c:410
const char * rofi_view_get_user_input(const RofiViewState *state)
Definition view.c:439
void rofi_view_set_selected_line(RofiViewState *state, unsigned int selected_line)
Definition view.c:369
void rofi_view_free(RofiViewState *state)
Definition view.c:392
RofiViewState * rofi_view_create(Mode *sw, const char *input, MenuFlags menu_flags, void(*finalize)(RofiViewState *))
Definition view.c:1860
unsigned int rofi_view_get_selected_line(const RofiViewState *state)
Definition view.c:414
unsigned int rofi_view_get_next_position(const RofiViewState *state)
Definition view.c:426
@ MENU_PASSWORD
Definition view.h:59
@ MENU_NORMAL
Definition view.h:57
void rofi_view_ellipsize_listview(RofiViewState *state, PangoEllipsizeMode mode)
Definition view.c:2143
@ MODE_TYPE_DMENU
@ P_INTEGER
Definition rofi-types.h:12
struct rofi_int_matcher_t rofi_int_matcher
void dmenuscript_parse_entry_extras(G_GNUC_UNUSED Mode *sw, DmenuScriptEntry *entry, char *buffer, G_GNUC_UNUSED size_t length)
Definition script.c:89
Settings config
#define DEFAULT_MENU_LINES
Definition settings.h:231
Definition dmenu.c:121
DmenuModePrivateData * pd
Definition dmenu.c:124
DmenuScriptEntry values[BLOCK_LINES_SIZE]
Definition dmenu.c:123
unsigned int length
Definition dmenu.c:122
unsigned int num_urgent_list
Definition dmenu.c:87
uint32_t * selected_list
Definition dmenu.c:90
DmenuScriptEntry * cmd_list
Definition dmenu.c:94
unsigned int cmd_list_real_length
Definition dmenu.c:95
GThread * reading_thread
Definition dmenu.c:104
unsigned int num_selected_list
Definition dmenu.c:91
unsigned int do_markup
Definition dmenu.c:92
unsigned int selected_count
Definition dmenu.c:98
char * ballot_unselected
Definition dmenu.c:115
gchar * column_separator
Definition dmenu.c:101
unsigned int num_active_list
Definition dmenu.c:89
gboolean loading
Definition dmenu.c:112
struct rofi_range_pair * urgent_list
Definition dmenu.c:86
gboolean multi_select
Definition dmenu.c:102
unsigned int cmd_list_length
Definition dmenu.c:96
unsigned int only_selected
Definition dmenu.c:97
GAsyncQueue * async_queue
Definition dmenu.c:105
gchar ** columns
Definition dmenu.c:100
char * ballot_selected
Definition dmenu.c:114
unsigned int selected_line
Definition dmenu.c:83
struct rofi_range_pair * active_list
Definition dmenu.c:88
PropertyValue value
Definition rofi-types.h:293
char * name
Definition rofi-types.h:289
WindowLocation location
Definition settings.h:110
unsigned int auto_select
Definition settings.h:156
char * filter
Definition settings.h:169
unsigned int case_sensitive
Definition settings.h:142
char * display_name
void * private_data
ThemeWidget * rofi_theme_find_or_create_name(ThemeWidget *base, const char *name)
Definition theme.c:83
Property * rofi_theme_property_create(PropertyType type)
Definition theme.c:103
void rofi_theme_property_free(Property *p)
Definition theme.c:205
void rofi_theme_widget_add_properties(ThemeWidget *wid, GHashTable *table)
Definition theme.c:656
ThemeWidget * rofi_theme