mirror of
https://github.com/davatorium/rofi.git
synced 2025-02-03 15:34:54 -05:00
[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>
This commit is contained in:
parent
5870040256
commit
bc67d8a8a6
8 changed files with 239 additions and 2 deletions
|
@ -182,6 +182,7 @@ if FOUND_PANDOC
|
||||||
generate-manpage: doc/rofi.1\
|
generate-manpage: doc/rofi.1\
|
||||||
doc/rofi-sensible-terminal.1\
|
doc/rofi-sensible-terminal.1\
|
||||||
doc/rofi-theme-selector.1\
|
doc/rofi-theme-selector.1\
|
||||||
|
doc/rofi-actions.5\
|
||||||
doc/rofi-debugging.5\
|
doc/rofi-debugging.5\
|
||||||
doc/rofi-dmenu.5\
|
doc/rofi-dmenu.5\
|
||||||
doc/rofi-keys.5\
|
doc/rofi-keys.5\
|
||||||
|
|
|
@ -49,6 +49,18 @@ Settings config = {
|
||||||
/** Custom command to generate preview icons */
|
/** Custom command to generate preview icons */
|
||||||
.preview_cmd = NULL,
|
.preview_cmd = NULL,
|
||||||
|
|
||||||
|
/** Custom command to call when menu selection changes */
|
||||||
|
.on_selection_changed = NULL,
|
||||||
|
/** Custom command to call when menu mode changes */
|
||||||
|
.on_mode_changed = NULL,
|
||||||
|
/** Custom command to call when menu entry is accepted */
|
||||||
|
.on_entry_accepted = NULL,
|
||||||
|
/** Custom command to call when menu is canceled */
|
||||||
|
.on_menu_canceled = NULL,
|
||||||
|
/** Custom command to call when menu finds errors */
|
||||||
|
.on_menu_error = NULL,
|
||||||
|
/** Custom command to call when menu screenshot is taken */
|
||||||
|
.on_screenshot_taken = NULL,
|
||||||
/** Terminal to use. (for ssh and open in terminal) */
|
/** Terminal to use. (for ssh and open in terminal) */
|
||||||
.terminal_emulator = "rofi-sensible-terminal",
|
.terminal_emulator = "rofi-sensible-terminal",
|
||||||
.ssh_client = "ssh",
|
.ssh_client = "ssh",
|
||||||
|
|
|
@ -2,6 +2,7 @@ man_files = [
|
||||||
'rofi.1',
|
'rofi.1',
|
||||||
'rofi-sensible-terminal.1',
|
'rofi-sensible-terminal.1',
|
||||||
'rofi-theme-selector.1',
|
'rofi-theme-selector.1',
|
||||||
|
'rofi-actions.5',
|
||||||
'rofi-debugging.5',
|
'rofi-debugging.5',
|
||||||
'rofi-dmenu.5',
|
'rofi-dmenu.5',
|
||||||
'rofi-keys.5',
|
'rofi-keys.5',
|
||||||
|
|
89
doc/rofi-actions.5.markdown
Normal file
89
doc/rofi-actions.5.markdown
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
# rofi-actions(5)
|
||||||
|
|
||||||
|
## NAME
|
||||||
|
|
||||||
|
**rofi-actions** - Custom commands following interaction with rofi menus
|
||||||
|
|
||||||
|
## DESCRIPTION
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
This makes it possible for example to play sound effects or read aloud menu entries on selection.
|
||||||
|
|
||||||
|
## USAGE
|
||||||
|
|
||||||
|
Following is the list of rofi flags for specifying custom commands or scripts to execute on supported actions:
|
||||||
|
|
||||||
|
`-on-selection-changed` *cmd*
|
||||||
|
|
||||||
|
Command or script to run when the current selection changes. Selected text is forwarded to the command replacing the pattern *{entry}*.
|
||||||
|
|
||||||
|
`-on-entry-accepted` *cmd*
|
||||||
|
|
||||||
|
Command or script to run when a menu entry is accepted. Accepted text is forwarded to the command replacing the pattern *{entry}*.
|
||||||
|
|
||||||
|
`-on-mode-changed` *cmd*
|
||||||
|
|
||||||
|
Command or script to run when the menu mode (e.g. drun,window,ssh...) is changed.
|
||||||
|
|
||||||
|
`-on-menu-canceled` *cmd*
|
||||||
|
|
||||||
|
Command or script to run when the menu is canceled.
|
||||||
|
|
||||||
|
`-on-menu-error` *cmd*
|
||||||
|
|
||||||
|
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}*.
|
||||||
|
|
||||||
|
`-on-screenshot-taken` *cmd*
|
||||||
|
|
||||||
|
Command or script to run when a screenshot of rofi is taken. Screenshot path is forwarded to the command replacing the pattern *{path}*.
|
||||||
|
|
||||||
|
### Example usage
|
||||||
|
|
||||||
|
Rofi command line:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rofi -on-selection-changed "/path/to/select.sh {entry}" \
|
||||||
|
-on-entry-accepted "/path/to/accept.sh {entry}" \
|
||||||
|
-on-menu-canceled "/path/to/exit.sh" \
|
||||||
|
-on-mode-changed "/path/to/change.sh" \
|
||||||
|
-on-menu-error "/path/to/error.sh {error}" \
|
||||||
|
-on-screenshot-taken "/path/to/camera.sh {path}" \
|
||||||
|
-show drun
|
||||||
|
```
|
||||||
|
|
||||||
|
Rofi config file:
|
||||||
|
|
||||||
|
```css
|
||||||
|
configuration {
|
||||||
|
on-selection-changed: "/path/to/select.sh {entry}";
|
||||||
|
on-entry-accepted: "/path/to/accept.sh {entry}";
|
||||||
|
on-menu-canceled: "/path/to/exit.sh";
|
||||||
|
on-mode-changed: "/path/to/change.sh";
|
||||||
|
on-menu-error: "/path/to/error.sh {error}";
|
||||||
|
on-screenshot-taken: "/path/to/camera.sh {path}";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Play sound effects
|
||||||
|
|
||||||
|
Here's an example bash script that plays a sound effect using `aplay` when the current selection is changed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
coproc aplay -q $HOME/Music/selecting_an_item.wav
|
||||||
|
```
|
||||||
|
|
||||||
|
The use of `coproc` for playing sounds is suggested, otherwise the rofi process will wait for sounds to end playback before exiting.
|
||||||
|
|
||||||
|
### Read aloud
|
||||||
|
|
||||||
|
Here's an example bash script that reads aloud currently selected entries using `espeak`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
killall espeak
|
||||||
|
echo "selected: $@" | espeak
|
||||||
|
```
|
|
@ -64,6 +64,18 @@ typedef struct {
|
||||||
/** Custom command to generate preview icons */
|
/** Custom command to generate preview icons */
|
||||||
char *preview_cmd;
|
char *preview_cmd;
|
||||||
|
|
||||||
|
/** Custom command to call when menu selection changes */
|
||||||
|
char *on_selection_changed;
|
||||||
|
/** Custom command to call when menu mode changes */
|
||||||
|
char *on_mode_changed;
|
||||||
|
/** Custom command to call when menu entry is accepted */
|
||||||
|
char *on_entry_accepted;
|
||||||
|
/** Custom command to call when menu is canceled */
|
||||||
|
char *on_menu_canceled;
|
||||||
|
/** Custom command to call when menu finds errors */
|
||||||
|
char *on_menu_error;
|
||||||
|
/** Custom command to call when menu screenshot is taken */
|
||||||
|
char *on_screenshot_taken;
|
||||||
/** Terminal to use */
|
/** Terminal to use */
|
||||||
char *terminal_emulator;
|
char *terminal_emulator;
|
||||||
/** SSH client to use */
|
/** SSH client to use */
|
||||||
|
|
|
@ -90,6 +90,8 @@ struct RofiViewState {
|
||||||
int skip_absorb;
|
int skip_absorb;
|
||||||
/** The selected line (in the unfiltered list) */
|
/** The selected line (in the unfiltered list) */
|
||||||
unsigned int selected_line;
|
unsigned int selected_line;
|
||||||
|
/** The previously selected line (in the unfiltered list) */
|
||||||
|
unsigned int previous_line;
|
||||||
/** The return state of the view */
|
/** The return state of the view */
|
||||||
MenuReturn retv;
|
MenuReturn retv;
|
||||||
/** Monitor #workarea the view is displayed on */
|
/** Monitor #workarea the view is displayed on */
|
||||||
|
|
|
@ -212,6 +212,18 @@ static int lev_sort(const void *p1, const void *p2, void *arg) {
|
||||||
return distances[*a] - distances[*b];
|
return distances[*a] - distances[*b];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void screenshot_taken_user_callback(const char *path) {
|
||||||
|
if (config.on_screenshot_taken == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
char **args = NULL;
|
||||||
|
int argv = 0;
|
||||||
|
helper_parse_setup(config.on_screenshot_taken, &args, &argv, "{path}",
|
||||||
|
path, (char *)0);
|
||||||
|
if (args != NULL)
|
||||||
|
helper_execute(NULL, args, "", config.on_screenshot_taken, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a screenshot of Rofi at that point in time.
|
* Stores a screenshot of Rofi at that point in time.
|
||||||
*/
|
*/
|
||||||
|
@ -271,6 +283,7 @@ void rofi_capture_screenshot(void) {
|
||||||
g_warning("Failed to produce screenshot '%s', got error: '%s'", fpath,
|
g_warning("Failed to produce screenshot '%s', got error: '%s'", fpath,
|
||||||
cairo_status_to_string(status));
|
cairo_status_to_string(status));
|
||||||
}
|
}
|
||||||
|
screenshot_taken_user_callback(fpath);
|
||||||
}
|
}
|
||||||
cairo_destroy(draw);
|
cairo_destroy(draw);
|
||||||
}
|
}
|
||||||
|
@ -1285,9 +1298,30 @@ inline static void rofi_view_nav_last(RofiViewState *state) {
|
||||||
// state->selected = state->filtered_lines - 1;
|
// state->selected = state->filtered_lines - 1;
|
||||||
listview_set_selected(state->list_view, -1);
|
listview_set_selected(state->list_view, -1);
|
||||||
}
|
}
|
||||||
|
static void selection_changed_user_callback(unsigned int index, RofiViewState *state) {
|
||||||
|
if (config.on_selection_changed == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int fstate = 0;
|
||||||
|
char *text = mode_get_display_value(state->sw, state->line_map[index],
|
||||||
|
&fstate, NULL, TRUE);
|
||||||
|
char **args = NULL;
|
||||||
|
int argv = 0;
|
||||||
|
helper_parse_setup(config.on_selection_changed, &args, &argv, "{entry}",
|
||||||
|
text, (char *)0);
|
||||||
|
if (args != NULL)
|
||||||
|
helper_execute(NULL, args, "", config.on_selection_changed, NULL);
|
||||||
|
g_free(text);
|
||||||
|
}
|
||||||
static void selection_changed_callback(G_GNUC_UNUSED listview *lv,
|
static void selection_changed_callback(G_GNUC_UNUSED listview *lv,
|
||||||
unsigned int index, void *udata) {
|
unsigned int index, void *udata) {
|
||||||
RofiViewState *state = (RofiViewState *)udata;
|
RofiViewState *state = (RofiViewState *)udata;
|
||||||
|
if (index < state->filtered_lines) {
|
||||||
|
if (state->previous_line != state->line_map[index]) {
|
||||||
|
selection_changed_user_callback(index, state);
|
||||||
|
state->previous_line = state->line_map[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
if (state->tb_current_entry) {
|
if (state->tb_current_entry) {
|
||||||
if (index < state->filtered_lines) {
|
if (index < state->filtered_lines) {
|
||||||
int fstate = 0;
|
int fstate = 0;
|
||||||
|
@ -1295,7 +1329,6 @@ static void selection_changed_callback(G_GNUC_UNUSED listview *lv,
|
||||||
&fstate, NULL, TRUE);
|
&fstate, NULL, TRUE);
|
||||||
textbox_text(state->tb_current_entry, text);
|
textbox_text(state->tb_current_entry, text);
|
||||||
g_free(text);
|
g_free(text);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
textbox_text(state->tb_current_entry, "");
|
textbox_text(state->tb_current_entry, "");
|
||||||
}
|
}
|
||||||
|
@ -1882,7 +1915,6 @@ static void rofi_view_trigger_global_action(KeyBindingAction action) {
|
||||||
// Nothing entered and nothing selected.
|
// Nothing entered and nothing selected.
|
||||||
state->retv = MENU_CUSTOM_INPUT;
|
state->retv = MENU_CUSTOM_INPUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
state->quit = TRUE;
|
state->quit = TRUE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -2086,8 +2118,43 @@ void rofi_view_handle_mouse_motion(RofiViewState *state, gint x, gint y,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void rofi_quit_user_callback(RofiViewState *state) {
|
||||||
|
if (state->retv & MENU_OK) {
|
||||||
|
if (config.on_entry_accepted == NULL)
|
||||||
|
return;
|
||||||
|
int fstate = 0;
|
||||||
|
unsigned int selected = listview_get_selected(state->list_view);
|
||||||
|
// TODO: handle custom text
|
||||||
|
if (selected >= state->filtered_lines)
|
||||||
|
return;
|
||||||
|
// Pass selected text to custom command
|
||||||
|
char *text = mode_get_display_value(state->sw, state->line_map[selected],
|
||||||
|
&fstate, NULL, TRUE);
|
||||||
|
char **args = NULL;
|
||||||
|
int argv = 0;
|
||||||
|
helper_parse_setup(config.on_entry_accepted, &args, &argv, "{entry}",
|
||||||
|
text, (char *)0);
|
||||||
|
if (args != NULL)
|
||||||
|
helper_execute(NULL, args, "", config.on_entry_accepted, NULL);
|
||||||
|
g_free(text);
|
||||||
|
} else if(state->retv & MENU_CANCEL) {
|
||||||
|
if (config.on_menu_canceled == NULL)
|
||||||
|
return;
|
||||||
|
helper_execute_command(NULL, config.on_menu_canceled, FALSE, NULL);
|
||||||
|
} else if (state->retv & MENU_NEXT ||
|
||||||
|
state->retv & MENU_PREVIOUS ||
|
||||||
|
state->retv & MENU_QUICK_SWITCH ||
|
||||||
|
state->retv & MENU_COMPLETE) {
|
||||||
|
if (config.on_mode_changed == NULL)
|
||||||
|
return;
|
||||||
|
// TODO: pass mode name to custom command
|
||||||
|
helper_execute_command(NULL, config.on_mode_changed, FALSE, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
void rofi_view_maybe_update(RofiViewState *state) {
|
void rofi_view_maybe_update(RofiViewState *state) {
|
||||||
if (rofi_view_get_completed(state)) {
|
if (rofi_view_get_completed(state)) {
|
||||||
|
// Exec custom user commands
|
||||||
|
rofi_quit_user_callback(state);
|
||||||
// This menu is done.
|
// This menu is done.
|
||||||
rofi_view_finalize(state);
|
rofi_view_finalize(state);
|
||||||
// If there a state. (for example error) reload it.
|
// If there a state. (for example error) reload it.
|
||||||
|
@ -2482,6 +2549,7 @@ RofiViewState *rofi_view_create(Mode *sw, const char *input,
|
||||||
state->menu_flags = menu_flags;
|
state->menu_flags = menu_flags;
|
||||||
state->sw = sw;
|
state->sw = sw;
|
||||||
state->selected_line = UINT32_MAX;
|
state->selected_line = UINT32_MAX;
|
||||||
|
state->previous_line = UINT32_MAX;
|
||||||
state->retv = MENU_CANCEL;
|
state->retv = MENU_CANCEL;
|
||||||
state->distance = NULL;
|
state->distance = NULL;
|
||||||
state->quit = FALSE;
|
state->quit = FALSE;
|
||||||
|
@ -2571,6 +2639,18 @@ RofiViewState *rofi_view_create(Mode *sw, const char *input,
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void rofi_error_user_callback(const char *msg) {
|
||||||
|
if (config.on_menu_error == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
char **args = NULL;
|
||||||
|
int argv = 0;
|
||||||
|
helper_parse_setup(config.on_menu_error, &args, &argv, "{error}",
|
||||||
|
msg, (char *)0);
|
||||||
|
if (args != NULL)
|
||||||
|
helper_execute(NULL, args, "", config.on_menu_error, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
int rofi_view_error_dialog(const char *msg, int markup) {
|
int rofi_view_error_dialog(const char *msg, int markup) {
|
||||||
RofiViewState *state = __rofi_view_state_create();
|
RofiViewState *state = __rofi_view_state_create();
|
||||||
state->retv = MENU_CANCEL;
|
state->retv = MENU_CANCEL;
|
||||||
|
@ -2608,6 +2688,9 @@ int rofi_view_error_dialog(const char *msg, int markup) {
|
||||||
sn_launchee_context_complete(xcb->sncontext);
|
sn_launchee_context_complete(xcb->sncontext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exec custom command
|
||||||
|
rofi_error_user_callback(msg);
|
||||||
|
|
||||||
// Set it as current window.
|
// Set it as current window.
|
||||||
rofi_view_set_active(state);
|
rofi_view_set_active(state);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
|
@ -136,6 +136,43 @@ static XrmOption xrmOptions[] = {
|
||||||
"Custom command to generate preview icons",
|
"Custom command to generate preview icons",
|
||||||
CONFIG_DEFAULT},
|
CONFIG_DEFAULT},
|
||||||
|
|
||||||
|
{xrm_String,
|
||||||
|
"on-selection-changed",
|
||||||
|
{.str = &config.on_selection_changed},
|
||||||
|
NULL,
|
||||||
|
"Custom command to call when menu selection changes",
|
||||||
|
CONFIG_DEFAULT},
|
||||||
|
{xrm_String,
|
||||||
|
"on-mode-changed",
|
||||||
|
{.str = &config.on_mode_changed},
|
||||||
|
NULL,
|
||||||
|
"Custom command to call when menu mode changes",
|
||||||
|
CONFIG_DEFAULT},
|
||||||
|
{xrm_String,
|
||||||
|
"on-entry-accepted",
|
||||||
|
{.str = &config.on_entry_accepted},
|
||||||
|
NULL,
|
||||||
|
"Custom command to call when menu entry is accepted",
|
||||||
|
CONFIG_DEFAULT},
|
||||||
|
{xrm_String,
|
||||||
|
"on-menu-canceled",
|
||||||
|
{.str = &config.on_menu_canceled},
|
||||||
|
NULL,
|
||||||
|
"Custom command to call when menu is canceled",
|
||||||
|
CONFIG_DEFAULT},
|
||||||
|
{xrm_String,
|
||||||
|
"on-menu-error",
|
||||||
|
{.str = &config.on_menu_error},
|
||||||
|
NULL,
|
||||||
|
"Custom command to call when menu finds errors",
|
||||||
|
CONFIG_DEFAULT},
|
||||||
|
{xrm_String,
|
||||||
|
"on-screenshot-taken",
|
||||||
|
{.str = &config.on_screenshot_taken},
|
||||||
|
NULL,
|
||||||
|
"Custom command to call when menu screenshot is taken",
|
||||||
|
CONFIG_DEFAULT},
|
||||||
|
|
||||||
{xrm_String,
|
{xrm_String,
|
||||||
"terminal",
|
"terminal",
|
||||||
{.str = &config.terminal_emulator},
|
{.str = &config.terminal_emulator},
|
||||||
|
|
Loading…
Add table
Reference in a new issue