Skip to content

Commit bc67d8a

Browse files
giomatfois62Matteo
andauthored
[WIP] Execute custom user commands or scripts on a variety of rofi events (#2053)
* Implemented custom user command execution on the following menu events: entry selected, entry accepted, menu canceled, menu error, mode changed, screenshot taken * fixed different signedness comparison warning and compare unfiltered entry index in selection_changed_user_callback * track previously selected line in RofiViewState * added documentation about custom scripts to run on certain actions --------- Co-authored-by: Matteo <giomatfois62@yahoo.it>
1 parent 5870040 commit bc67d8a

File tree

8 files changed

+239
-2
lines changed

8 files changed

+239
-2
lines changed

Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ if FOUND_PANDOC
182182
generate-manpage: doc/rofi.1\
183183
doc/rofi-sensible-terminal.1\
184184
doc/rofi-theme-selector.1\
185+
doc/rofi-actions.5\
185186
doc/rofi-debugging.5\
186187
doc/rofi-dmenu.5\
187188
doc/rofi-keys.5\

config/config.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ Settings config = {
4949
/** Custom command to generate preview icons */
5050
.preview_cmd = NULL,
5151

52+
/** Custom command to call when menu selection changes */
53+
.on_selection_changed = NULL,
54+
/** Custom command to call when menu mode changes */
55+
.on_mode_changed = NULL,
56+
/** Custom command to call when menu entry is accepted */
57+
.on_entry_accepted = NULL,
58+
/** Custom command to call when menu is canceled */
59+
.on_menu_canceled = NULL,
60+
/** Custom command to call when menu finds errors */
61+
.on_menu_error = NULL,
62+
/** Custom command to call when menu screenshot is taken */
63+
.on_screenshot_taken = NULL,
5264
/** Terminal to use. (for ssh and open in terminal) */
5365
.terminal_emulator = "rofi-sensible-terminal",
5466
.ssh_client = "ssh",

doc/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ man_files = [
22
'rofi.1',
33
'rofi-sensible-terminal.1',
44
'rofi-theme-selector.1',
5+
'rofi-actions.5',
56
'rofi-debugging.5',
67
'rofi-dmenu.5',
78
'rofi-keys.5',

doc/rofi-actions.5.markdown

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# rofi-actions(5)
2+
3+
## NAME
4+
5+
**rofi-actions** - Custom commands following interaction with rofi menus
6+
7+
## DESCRIPTION
8+
9+
**rofi** allows to set custom commands or scripts to be executed when some actions are performed in the menu, such as changing selection, accepting an entry or canceling.
10+
11+
This makes it possible for example to play sound effects or read aloud menu entries on selection.
12+
13+
## USAGE
14+
15+
Following is the list of rofi flags for specifying custom commands or scripts to execute on supported actions:
16+
17+
`-on-selection-changed` *cmd*
18+
19+
Command or script to run when the current selection changes. Selected text is forwarded to the command replacing the pattern *{entry}*.
20+
21+
`-on-entry-accepted` *cmd*
22+
23+
Command or script to run when a menu entry is accepted. Accepted text is forwarded to the command replacing the pattern *{entry}*.
24+
25+
`-on-mode-changed` *cmd*
26+
27+
Command or script to run when the menu mode (e.g. drun,window,ssh...) is changed.
28+
29+
`-on-menu-canceled` *cmd*
30+
31+
Command or script to run when the menu is canceled.
32+
33+
`-on-menu-error` *cmd*
34+
35+
Command or script to run when an error menu is shown (e.g. `rofi -e "error message"`). Error text is forwarded to the command replacing the pattern *{error}*.
36+
37+
`-on-screenshot-taken` *cmd*
38+
39+
Command or script to run when a screenshot of rofi is taken. Screenshot path is forwarded to the command replacing the pattern *{path}*.
40+
41+
### Example usage
42+
43+
Rofi command line:
44+
45+
```bash
46+
rofi -on-selection-changed "/path/to/select.sh {entry}" \
47+
-on-entry-accepted "/path/to/accept.sh {entry}" \
48+
-on-menu-canceled "/path/to/exit.sh" \
49+
-on-mode-changed "/path/to/change.sh" \
50+
-on-menu-error "/path/to/error.sh {error}" \
51+
-on-screenshot-taken "/path/to/camera.sh {path}" \
52+
-show drun
53+
```
54+
55+
Rofi config file:
56+
57+
```css
58+
configuration {
59+
on-selection-changed: "/path/to/select.sh {entry}";
60+
on-entry-accepted: "/path/to/accept.sh {entry}";
61+
on-menu-canceled: "/path/to/exit.sh";
62+
on-mode-changed: "/path/to/change.sh";
63+
on-menu-error: "/path/to/error.sh {error}";
64+
on-screenshot-taken: "/path/to/camera.sh {path}";
65+
}
66+
```
67+
68+
### Play sound effects
69+
70+
Here's an example bash script that plays a sound effect using `aplay` when the current selection is changed:
71+
72+
```bash
73+
#!/bin/bash
74+
75+
coproc aplay -q $HOME/Music/selecting_an_item.wav
76+
```
77+
78+
The use of `coproc` for playing sounds is suggested, otherwise the rofi process will wait for sounds to end playback before exiting.
79+
80+
### Read aloud
81+
82+
Here's an example bash script that reads aloud currently selected entries using `espeak`:
83+
84+
```bash
85+
#!/bin/bash
86+
87+
killall espeak
88+
echo "selected: $@" | espeak
89+
```

include/settings.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,18 @@ typedef struct {
6464
/** Custom command to generate preview icons */
6565
char *preview_cmd;
6666

67+
/** Custom command to call when menu selection changes */
68+
char *on_selection_changed;
69+
/** Custom command to call when menu mode changes */
70+
char *on_mode_changed;
71+
/** Custom command to call when menu entry is accepted */
72+
char *on_entry_accepted;
73+
/** Custom command to call when menu is canceled */
74+
char *on_menu_canceled;
75+
/** Custom command to call when menu finds errors */
76+
char *on_menu_error;
77+
/** Custom command to call when menu screenshot is taken */
78+
char *on_screenshot_taken;
6779
/** Terminal to use */
6880
char *terminal_emulator;
6981
/** SSH client to use */

include/view-internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ struct RofiViewState {
9090
int skip_absorb;
9191
/** The selected line (in the unfiltered list) */
9292
unsigned int selected_line;
93+
/** The previously selected line (in the unfiltered list) */
94+
unsigned int previous_line;
9395
/** The return state of the view */
9496
MenuReturn retv;
9597
/** Monitor #workarea the view is displayed on */

source/view.c

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,18 @@ static int lev_sort(const void *p1, const void *p2, void *arg) {
212212
return distances[*a] - distances[*b];
213213
}
214214

215+
static void screenshot_taken_user_callback(const char *path) {
216+
if (config.on_screenshot_taken == NULL)
217+
return;
218+
219+
char **args = NULL;
220+
int argv = 0;
221+
helper_parse_setup(config.on_screenshot_taken, &args, &argv, "{path}",
222+
path, (char *)0);
223+
if (args != NULL)
224+
helper_execute(NULL, args, "", config.on_screenshot_taken, NULL);
225+
}
226+
215227
/**
216228
* Stores a screenshot of Rofi at that point in time.
217229
*/
@@ -271,6 +283,7 @@ void rofi_capture_screenshot(void) {
271283
g_warning("Failed to produce screenshot '%s', got error: '%s'", fpath,
272284
cairo_status_to_string(status));
273285
}
286+
screenshot_taken_user_callback(fpath);
274287
}
275288
cairo_destroy(draw);
276289
}
@@ -1285,17 +1298,37 @@ inline static void rofi_view_nav_last(RofiViewState *state) {
12851298
// state->selected = state->filtered_lines - 1;
12861299
listview_set_selected(state->list_view, -1);
12871300
}
1301+
static void selection_changed_user_callback(unsigned int index, RofiViewState *state) {
1302+
if (config.on_selection_changed == NULL)
1303+
return;
1304+
1305+
int fstate = 0;
1306+
char *text = mode_get_display_value(state->sw, state->line_map[index],
1307+
&fstate, NULL, TRUE);
1308+
char **args = NULL;
1309+
int argv = 0;
1310+
helper_parse_setup(config.on_selection_changed, &args, &argv, "{entry}",
1311+
text, (char *)0);
1312+
if (args != NULL)
1313+
helper_execute(NULL, args, "", config.on_selection_changed, NULL);
1314+
g_free(text);
1315+
}
12881316
static void selection_changed_callback(G_GNUC_UNUSED listview *lv,
12891317
unsigned int index, void *udata) {
12901318
RofiViewState *state = (RofiViewState *)udata;
1319+
if (index < state->filtered_lines) {
1320+
if (state->previous_line != state->line_map[index]) {
1321+
selection_changed_user_callback(index, state);
1322+
state->previous_line = state->line_map[index];
1323+
}
1324+
}
12911325
if (state->tb_current_entry) {
12921326
if (index < state->filtered_lines) {
12931327
int fstate = 0;
12941328
char *text = mode_get_display_value(state->sw, state->line_map[index],
12951329
&fstate, NULL, TRUE);
12961330
textbox_text(state->tb_current_entry, text);
12971331
g_free(text);
1298-
12991332
} else {
13001333
textbox_text(state->tb_current_entry, "");
13011334
}
@@ -1882,7 +1915,6 @@ static void rofi_view_trigger_global_action(KeyBindingAction action) {
18821915
// Nothing entered and nothing selected.
18831916
state->retv = MENU_CUSTOM_INPUT;
18841917
}
1885-
18861918
state->quit = TRUE;
18871919
break;
18881920
}
@@ -2086,8 +2118,43 @@ void rofi_view_handle_mouse_motion(RofiViewState *state, gint x, gint y,
20862118
}
20872119
}
20882120

2121+
static void rofi_quit_user_callback(RofiViewState *state) {
2122+
if (state->retv & MENU_OK) {
2123+
if (config.on_entry_accepted == NULL)
2124+
return;
2125+
int fstate = 0;
2126+
unsigned int selected = listview_get_selected(state->list_view);
2127+
// TODO: handle custom text
2128+
if (selected >= state->filtered_lines)
2129+
return;
2130+
// Pass selected text to custom command
2131+
char *text = mode_get_display_value(state->sw, state->line_map[selected],
2132+
&fstate, NULL, TRUE);
2133+
char **args = NULL;
2134+
int argv = 0;
2135+
helper_parse_setup(config.on_entry_accepted, &args, &argv, "{entry}",
2136+
text, (char *)0);
2137+
if (args != NULL)
2138+
helper_execute(NULL, args, "", config.on_entry_accepted, NULL);
2139+
g_free(text);
2140+
} else if(state->retv & MENU_CANCEL) {
2141+
if (config.on_menu_canceled == NULL)
2142+
return;
2143+
helper_execute_command(NULL, config.on_menu_canceled, FALSE, NULL);
2144+
} else if (state->retv & MENU_NEXT ||
2145+
state->retv & MENU_PREVIOUS ||
2146+
state->retv & MENU_QUICK_SWITCH ||
2147+
state->retv & MENU_COMPLETE) {
2148+
if (config.on_mode_changed == NULL)
2149+
return;
2150+
// TODO: pass mode name to custom command
2151+
helper_execute_command(NULL, config.on_mode_changed, FALSE, NULL);
2152+
}
2153+
}
20892154
void rofi_view_maybe_update(RofiViewState *state) {
20902155
if (rofi_view_get_completed(state)) {
2156+
// Exec custom user commands
2157+
rofi_quit_user_callback(state);
20912158
// This menu is done.
20922159
rofi_view_finalize(state);
20932160
// If there a state. (for example error) reload it.
@@ -2482,6 +2549,7 @@ RofiViewState *rofi_view_create(Mode *sw, const char *input,
24822549
state->menu_flags = menu_flags;
24832550
state->sw = sw;
24842551
state->selected_line = UINT32_MAX;
2552+
state->previous_line = UINT32_MAX;
24852553
state->retv = MENU_CANCEL;
24862554
state->distance = NULL;
24872555
state->quit = FALSE;
@@ -2571,6 +2639,18 @@ RofiViewState *rofi_view_create(Mode *sw, const char *input,
25712639
return state;
25722640
}
25732641

2642+
static void rofi_error_user_callback(const char *msg) {
2643+
if (config.on_menu_error == NULL)
2644+
return;
2645+
2646+
char **args = NULL;
2647+
int argv = 0;
2648+
helper_parse_setup(config.on_menu_error, &args, &argv, "{error}",
2649+
msg, (char *)0);
2650+
if (args != NULL)
2651+
helper_execute(NULL, args, "", config.on_menu_error, NULL);
2652+
}
2653+
25742654
int rofi_view_error_dialog(const char *msg, int markup) {
25752655
RofiViewState *state = __rofi_view_state_create();
25762656
state->retv = MENU_CANCEL;
@@ -2608,6 +2688,9 @@ int rofi_view_error_dialog(const char *msg, int markup) {
26082688
sn_launchee_context_complete(xcb->sncontext);
26092689
}
26102690

2691+
// Exec custom command
2692+
rofi_error_user_callback(msg);
2693+
26112694
// Set it as current window.
26122695
rofi_view_set_active(state);
26132696
return TRUE;

source/xrmoptions.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,43 @@ static XrmOption xrmOptions[] = {
136136
"Custom command to generate preview icons",
137137
CONFIG_DEFAULT},
138138

139+
{xrm_String,
140+
"on-selection-changed",
141+
{.str = &config.on_selection_changed},
142+
NULL,
143+
"Custom command to call when menu selection changes",
144+
CONFIG_DEFAULT},
145+
{xrm_String,
146+
"on-mode-changed",
147+
{.str = &config.on_mode_changed},
148+
NULL,
149+
"Custom command to call when menu mode changes",
150+
CONFIG_DEFAULT},
151+
{xrm_String,
152+
"on-entry-accepted",
153+
{.str = &config.on_entry_accepted},
154+
NULL,
155+
"Custom command to call when menu entry is accepted",
156+
CONFIG_DEFAULT},
157+
{xrm_String,
158+
"on-menu-canceled",
159+
{.str = &config.on_menu_canceled},
160+
NULL,
161+
"Custom command to call when menu is canceled",
162+
CONFIG_DEFAULT},
163+
{xrm_String,
164+
"on-menu-error",
165+
{.str = &config.on_menu_error},
166+
NULL,
167+
"Custom command to call when menu finds errors",
168+
CONFIG_DEFAULT},
169+
{xrm_String,
170+
"on-screenshot-taken",
171+
{.str = &config.on_screenshot_taken},
172+
NULL,
173+
"Custom command to call when menu screenshot is taken",
174+
CONFIG_DEFAULT},
175+
139176
{xrm_String,
140177
"terminal",
141178
{.str = &config.terminal_emulator},

0 commit comments

Comments
 (0)