/* * rofi * * MIT/X11 License * Copyright © 2013-2022 Qball Cow <qball@gmpclient.org> * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * */ /** The log domain of this dialog. */ #define G_LOG_DOMAIN "Modes.Combi" #include "helper.h" #include "settings.h" #include <rofi.h> #include <stdio.h> #include <stdlib.h> #include "mode-private.h" #include "widgets/textbox.h" #include <modes/modes.h> #include <pango/pango.h> #include <theme.h> /** * Combi Mode */ typedef struct { Mode *mode; gboolean disable; } CombiMode; typedef struct { // List of (combined) entries. unsigned int cmd_list_length; // List to validate where each switcher starts. unsigned int *starts; unsigned int *lengths; // List of switchers to combine. unsigned int num_switchers; CombiMode *switchers; } CombiModePrivateData; static void combi_mode_parse_switchers(Mode *sw) { CombiModePrivateData *pd = mode_get_private_data(sw); char *savept = NULL; // Make a copy, as strtok will modify it. char *switcher_str = g_strdup(config.combi_modes); const char *const sep = ",#"; // Split token on ','. This modifies switcher_str. for (char *token = strtok_r(switcher_str, sep, &savept); token != NULL; token = strtok_r(NULL, sep, &savept)) { /* Check against recursion. */ if (g_strcmp0(token, sw->name) == 0) { g_warning("You cannot add '%s' to the list of combined modes.", sw->name); continue; } // Resize and add entry. pd->switchers = (CombiMode *)g_realloc( pd->switchers, sizeof(CombiMode) * (pd->num_switchers + 1)); Mode *mode = rofi_collect_modes_search(token); if (mode != NULL) { pd->switchers[pd->num_switchers].disable = FALSE; pd->switchers[pd->num_switchers++].mode = mode; continue; } // If not build in, use custom switchers. mode = script_mode_parse_setup(token); if (mode != NULL) { pd->switchers[pd->num_switchers].disable = FALSE; pd->switchers[pd->num_switchers++].mode = mode; continue; } // Report error, don't continue. g_warning("Invalid script switcher: %s", token); token = NULL; } // Free string that was modified by strtok_r g_free(switcher_str); } static unsigned int combi_mode_get_num_entries(const Mode *sw) { const CombiModePrivateData *pd = (const CombiModePrivateData *)mode_get_private_data(sw); unsigned int length = 0; for (unsigned int i = 0; i < pd->num_switchers; i++) { unsigned int entries = mode_get_num_entries(pd->switchers[i].mode); pd->starts[i] = length; pd->lengths[i] = entries; length += entries; } return length; } static int combi_mode_init(Mode *sw) { if (mode_get_private_data(sw) == NULL) { CombiModePrivateData *pd = g_malloc0(sizeof(*pd)); mode_set_private_data(sw, (void *)pd); combi_mode_parse_switchers(sw); pd->starts = g_malloc0(sizeof(int) * pd->num_switchers); pd->lengths = g_malloc0(sizeof(int) * pd->num_switchers); for (unsigned int i = 0; i < pd->num_switchers; i++) { if (!mode_init(pd->switchers[i].mode)) { return FALSE; } } if (pd->cmd_list_length == 0) { pd->cmd_list_length = combi_mode_get_num_entries(sw); } } return TRUE; } static void combi_mode_destroy(Mode *sw) { CombiModePrivateData *pd = (CombiModePrivateData *)mode_get_private_data(sw); if (pd != NULL) { g_free(pd->starts); g_free(pd->lengths); // Cleanup switchers. for (unsigned int i = 0; i < pd->num_switchers; i++) { mode_destroy(pd->switchers[i].mode); } g_free(pd->switchers); g_free(pd); mode_set_private_data(sw, NULL); } } static ModeMode combi_mode_result(Mode *sw, int mretv, char **input, unsigned int selected_line) { CombiModePrivateData *pd = mode_get_private_data(sw); if (input[0][0] == '!') { int switcher = -1; // Implement strchrnul behaviour. char *eob = g_utf8_strchr(input[0], -1, ' '); if (eob == NULL) { eob = &(input[0][strlen(input[0])]); } ssize_t bang_len = g_utf8_pointer_to_offset(input[0], eob) - 1; if (bang_len > 0) { for (unsigned i = 0; i < pd->num_switchers; i++) { const char *mode_name = mode_get_name(pd->switchers[i].mode); size_t mode_name_len = g_utf8_strlen(mode_name, -1); if ((size_t)bang_len <= mode_name_len && utf8_strncmp(&input[0][1], mode_name, bang_len) == 0) { switcher = i; break; } } } if (switcher >= 0) { if (eob[0] == ' ') { char *n = eob + 1; return mode_result(pd->switchers[switcher].mode, mretv, &n, selected_line - pd->starts[switcher]); } return MODE_EXIT; } } else if ((mretv & MENU_COMPLETE)) { return RELOAD_DIALOG; } for (unsigned i = 0; i < pd->num_switchers; i++) { if (selected_line >= pd->starts[i] && selected_line < (pd->starts[i] + pd->lengths[i])) { return mode_result(pd->switchers[i].mode, mretv, input, selected_line - pd->starts[i]); } } if ((mretv & MENU_CUSTOM_INPUT)) { return mode_result(pd->switchers[0].mode, mretv, input, selected_line); } return MODE_EXIT; } static int combi_mode_match(const Mode *sw, rofi_int_matcher **tokens, unsigned int index) { CombiModePrivateData *pd = mode_get_private_data(sw); for (unsigned i = 0; i < pd->num_switchers; i++) { if (pd->switchers[i].disable) { continue; } if (index >= pd->starts[i] && index < (pd->starts[i] + pd->lengths[i])) { return mode_token_match(pd->switchers[i].mode, tokens, index - pd->starts[i]); } } return 0; } static char *combi_mgrv(const Mode *sw, unsigned int selected_line, int *state, GList **attr_list, int get_entry) { CombiModePrivateData *pd = mode_get_private_data(sw); if (!get_entry) { for (unsigned i = 0; i < pd->num_switchers; i++) { if (selected_line >= pd->starts[i] && selected_line < (pd->starts[i] + pd->lengths[i])) { mode_get_display_value(pd->switchers[i].mode, selected_line - pd->starts[i], state, attr_list, FALSE); return NULL; } } return NULL; } for (unsigned i = 0; i < pd->num_switchers; i++) { if (selected_line >= pd->starts[i] && selected_line < (pd->starts[i] + pd->lengths[i])) { char *retv; char *str = retv = mode_get_display_value(pd->switchers[i].mode, selected_line - pd->starts[i], state, attr_list, TRUE); const char *dname = mode_get_display_name(pd->switchers[i].mode); if (!config.combi_hide_mode_prefix) { if (!(*state & MARKUP)) { char *tmp = str; str = g_markup_escape_text(tmp, -1); g_free(tmp); *state |= MARKUP; } retv = helper_string_replace_if_exists( config.combi_display_format, "{mode}", dname, "{text}", str, NULL); g_free(str); if (attr_list != NULL) { ThemeWidget *wid = rofi_config_find_widget(sw->name, NULL, TRUE); Property *p = rofi_theme_find_property( wid, P_COLOR, pd->switchers[i].mode->name, TRUE); if (p != NULL) { PangoAttribute *pa = pango_attr_foreground_new( p->value.color.red * 65535, p->value.color.green * 65535, p->value.color.blue * 65535); pa->start_index = PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING; pa->end_index = strlen(dname); *attr_list = g_list_append(*attr_list, pa); } } } return retv; } } return NULL; } static char *combi_get_completion(const Mode *sw, unsigned int index) { CombiModePrivateData *pd = mode_get_private_data(sw); for (unsigned i = 0; i < pd->num_switchers; i++) { if (index >= pd->starts[i] && index < (pd->starts[i] + pd->lengths[i])) { char *comp = mode_get_completion(pd->switchers[i].mode, index - pd->starts[i]); char *mcomp = g_strdup_printf("!%s %s", mode_get_name(pd->switchers[i].mode), comp); g_free(comp); return mcomp; } } // Should never get here. g_assert_not_reached(); return NULL; } static cairo_surface_t *combi_get_icon(const Mode *sw, unsigned int index, int height) { CombiModePrivateData *pd = mode_get_private_data(sw); for (unsigned i = 0; i < pd->num_switchers; i++) { if (index >= pd->starts[i] && index < (pd->starts[i] + pd->lengths[i])) { cairo_surface_t *icon = mode_get_icon(pd->switchers[i].mode, index - pd->starts[i], height); return icon; } } return NULL; } static char *combi_preprocess_input(Mode *sw, const char *input) { CombiModePrivateData *pd = mode_get_private_data(sw); for (unsigned i = 0; i < pd->num_switchers; i++) { pd->switchers[i].disable = FALSE; } if (input != NULL && input[0] == '!') { // Implement strchrnul behaviour. const char *eob = g_utf8_strchr(input, -1, ' '); if (eob == NULL) { // Set it to end. eob = &(input[strlen(input)]); } ssize_t bang_len = g_utf8_pointer_to_offset(input, eob) - 1; if (bang_len > 0) { for (unsigned i = 0; i < pd->num_switchers; i++) { const char *mode_name = mode_get_name(pd->switchers[i].mode); size_t mode_name_len = g_utf8_strlen(mode_name, -1); if (!((size_t)bang_len <= mode_name_len && utf8_strncmp(&input[1], mode_name, bang_len) == 0)) { // No match. pd->switchers[i].disable = TRUE; } } if (eob[0] == '\0' || eob[1] == '\0') { return NULL; } return g_strdup(eob + 1); } } return g_strdup(input); } Mode combi_mode = {.name = "combi", .cfg_name_key = "display-combi", ._init = combi_mode_init, ._get_num_entries = combi_mode_get_num_entries, ._result = combi_mode_result, ._destroy = combi_mode_destroy, ._token_match = combi_mode_match, ._get_completion = combi_get_completion, ._get_display_value = combi_mgrv, ._get_icon = combi_get_icon, ._preprocess_input = combi_preprocess_input, .private_data = NULL, .free = NULL};