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