rofi-1.5.0/0000775000175000017500000000000013234677335007474 500000000000000rofi-1.5.0/config/0000775000175000017500000000000013234677335010741 500000000000000rofi-1.5.0/config/config.c0000664000175000017500000001353413234677115012274 00000000000000/* * rofi * * MIT/X11 License * Copyright © 2013-2017 Qball Cow * * 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. * */ #include "config.h" #include #include #include #include "rofi-types.h" #include "settings.h" Settings config = { /** List of enabled modi. */ /** -modi */ #ifdef WINDOW_MODE .modi = "window,run,ssh", #else .modi = "run,ssh", #endif /** Border width around the window. */ .menu_bw = 1, /** The width of the switcher. (0100 in % > 100 in pixels) */ .menu_width = 50, /** Maximum number of options to show. */ .menu_lines = 15, /** Number of columns */ .menu_columns = 1, /** Font */ .menu_font = "mono 12", /** Whether to load and show icons */ .show_icons = FALSE, /** Terminal to use. (for ssh and open in terminal) */ .terminal_emulator = "rofi-sensible-terminal", .ssh_client = "ssh", /** Command when executing ssh. */ .ssh_command = "{terminal} -e {ssh-client} {host}", /** Command when running */ .run_command = "{cmd}", /** Command used to list executable commands. empty -> internal */ .run_list_command = "", /** Command executed when running application in terminal */ .run_shell_command = "{terminal} -e {cmd}", /** Command executed on accep-entry-custom for window modus */ .window_command = "xkill -id {window}", /** No default icon theme, we search Adwaita and gnome as fallback */ .drun_icon_theme = NULL, /** * Location of the window. * Enumeration indicating location or gravity of window. * * WL_NORTH_WEST WL_NORTH WL_NORTH_EAST * * WL_EAST WL_CENTER WL_EAST * * WL_SOUTH_WEST WL_SOUTH WL_SOUTH_EAST * */ .location = WL_CENTER, /** Padding between elements */ .padding = 5, /** Y offset */ .y_offset = 0, /** X offset */ .x_offset = 0, /** Always show config.menu_lines lines, even if less lines are available */ .fixed_num_lines = TRUE, /** Do not use history */ .disable_history = FALSE, /** Sort the displayed list */ .sort = FALSE, /** Use levenshtein sorting when matching */ .levenshtein_sort = FALSE, /** Case sensitivity of the search */ .case_sensitive = FALSE, /** Cycle through in the element list */ .cycle = TRUE, /** Height of an element in #chars */ .element_height = 1, /** Sidebar mode, show the modi */ .sidebar_mode = FALSE, /** auto select */ .auto_select = FALSE, /** Parse /etc/hosts file in ssh view. */ .parse_hosts = FALSE, /** Parse ~/.ssh/known_hosts file in ssh view. */ .parse_known_hosts = TRUE, /** Modi to combine into one view. */ .combi_modi = "window,run", .tokenize = TRUE, .matching = "normal", .matching_method = MM_NORMAL, /** Desktop entry fields to match*/ .drun_match_fields = "name,generic,exec,categories", /** Window fields to match in window mode*/ .window_match_fields = "all", /** Monitor */ .monitor = "-5", /** set line margin */ .line_margin = 2, .line_padding = 1, /** Set filter */ .filter = NULL, /** Separator style: dash/solid */ .separator_style = "dash", /** Hide scrollbar */ .hide_scrollbar = FALSE, .fullscreen = FALSE, .fake_transparency = FALSE, .dpi = -1, .threads = 0, .scroll_method = 0, .scrollbar_width = 8, .fake_background = "screenshot", .window_format = "{w} {i}{c} {t}", .click_to_exit = TRUE, .show_match = TRUE, .theme = NULL, .color_normal = NULL, .color_active = NULL, .color_urgent = NULL, .color_window = NULL, .plugin_path = PLUGIN_PATH, .max_history_size = 25, .combi_hide_mode_prefix = FALSE, }; rofi-1.5.0/INSTALL.md0000664000175000017500000001036213234677115011042 00000000000000# Installation guide This guide explains how to install rofi using its build system and how you can make debug builds. Rofi uses autotools (GNU Build system), for more information see [here](https://www.gnu.org/software/automake/manual/html_node/Autotools-Introduction.html). ## DEPENDENCY ### For building: * C compiler that supports the c99 standard. (gcc or clang) * make * autoconf * automake (1.11.3 or up) * pkg-config * flex 2.5.39 or higher * bison * Developer packages of the external libraries ### External libraries * libpango * libpangocairo * libcairo * libcairo-xcb * libglib2.0 >= 2.40 * gmodule-2.0 * gio-unix-2.0 * librsvg2.0 * libstartup-notification-1.0 * libxkbcommon >= 0.4.1 * libxkbcommon-x11 * libxcb (sometimes split, you need libxcb, libxcb-xkb and libxcb-randr libxcb-xinerama) * xcb-util * xcb-util-wm (sometimes split as libxcb-ewmh and libxcb-icccm) * xcb-util-xrm [new module, can be found here](https://github.com/Airblader/xcb-util-xrm/) On debian based systems, the developer packages are in the form of: `-dev` on rpm based `-devel`. ## Install from a release Check dependencies and configure build system: ``` ./configure ``` Build Rofi: ``` make ``` The actual install, execute as root (if needed): ``` make install ``` The default installation prefix is: `/usr/local/` use `./configure --prefix={prefix}` to install into another location. ## Install a checkout from git The GitHub Pages version of these directions may be out of date. Please use [INSTALL.md from the online repo][master-install] or your local repository. [master-install]: https://github.com/DaveDavenport/rofi/blob/master/INSTALL.md#install-a-checkout-from-git Make a checkout: ``` git clone https://github.com/DaveDavenport/rofi cd rofi/ ``` Pull in dependencies ``` git submodule update --init ``` Generate build system: ``` autoreconf -i ``` Create a build directory: ``` mkdir build ``` Enter build directory: ``` cd build ``` Check dependencies and configure build system: ``` ../configure ``` Build rofi: ``` make ``` The actual install, execute as root (if needed): ``` make install ``` ## Options for configure When you run the configure step there are several you can configure. (To see the full list type `./configure --help` ). The most useful one to set the installation prefix: ``` ./configure --prefix= ``` f.e. ``` ./configure --prefix=/usr/ ``` ### Install locally or to install locally: ``` ./configure --prefix=${HOME}/.local/ ``` ## Options for make When you run make you can tweak the build process a little. ### Verbose output Show the commands called: ``` make V=1 ``` ### Debug build Compile with debug symbols and no optimization, this is useful for making backtraces: ``` make CFLAGS="-O0 -g3" clean rofi ``` ### Get a backtrace Getting a backtrace using GDB is not very handy. Because if rofi get stuck, it grabs keyboard and mouse. So if it crashes in GDB you are stuck. The best way to go is to enable core file. (ulimit -c unlimited in bash) then make rofi crash. You can then load the core in GDB. ``` gdb rofi core ``` > Where the core file is located and what its exact name is different on each distributions. Please consult the > relevant documentation. ## Install distribution ### Debian or Ubuntu ``` apt install rofi ``` #### Ubuntu 16.04 Xenial **Please note that the latest version of rofi in Ubuntu 16.04 is extremely outdated (v0.15.11)** This will cause issues with newer scripts (i.e. with clerk) and we recommend to manually download and install the deb file for zesty instead. You can find the deb on [ubuntu's launchpad page for rofi](https://launchpad.net/ubuntu/+source/rofi). ### Fedora rofi from [russianfedora repository](http://ru.fedoracommunity.org/repository) and also [Yaroslav's COPR (Cool Other Package Repo)](https://copr.fedorainfracloud.org/coprs/yaroslav/i3desktop/) ### ArchLinux ``` pacman -S rofi ``` ### Gentoo An ebuild is available, `x11-misc/rofi`. It's up to date, but you may need to enable ~arch to get the latest release: ``` echo 'x11-misc/rofi ~amd64' >> /etc/portage/package.accept_keywords ``` for amd64 or: ``` echo 'x11-misc/rofi ~x86' >> /etc/portage/package.accept_keywords ``` for i386. To install it, simply issue `emerge rofi`. rofi-1.5.0/source/0000775000175000017500000000000013234677335010774 500000000000000rofi-1.5.0/source/view.c0000664000175000017500000020100113234677115012020 00000000000000/* * rofi * * MIT/X11 License * Copyright © 2013-2017 Qball Cow * * 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 Rofi View log domain */ #define G_LOG_DOMAIN "View" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SN_API_NOT_YET_FROZEN #include #include "rofi.h" #include "timings.h" #include "settings.h" #include "mode.h" #include "display.h" #include "xcb-internal.h" #include "helper.h" #include "helper-theme.h" #include "xrmoptions.h" #include "dialogs/dialogs.h" #include "view.h" #include "view-internal.h" #include "theme.h" #include "xcb.h" /** * @param state The handle to the view * @param qr Indicate if queue_redraw should be called on changes. * * Update the state of the view. This involves filter state. */ void rofi_view_update ( RofiViewState *state, gboolean qr ); static int rofi_view_calculate_height ( RofiViewState *state ); /** Thread pool used for filtering */ GThreadPool *tpool = NULL; /** Global pointer to the currently active RofiViewState */ RofiViewState *current_active_menu = NULL; /** * Structure holding cached state. */ struct { /** main x11 windows */ xcb_window_t main_window; /** surface containing the fake background. */ cairo_surface_t *fake_bg; /** Draw context for main window */ xcb_gcontext_t gc; /** Main X11 side pixmap to draw on. */ xcb_pixmap_t edit_pixmap; /** Cairo Surface for edit_pixmap */ cairo_surface_t *edit_surf; /** Drawable context for edit_surf */ cairo_t *edit_draw; /** Indicate that fake background should be drawn relative to the window */ int fake_bgrel; /** Main flags */ MenuFlags flags; /** List of stacked views */ GQueue views; /** Current work area */ workarea mon; /** timeout for reloading */ guint idle_timeout; /** debug counter for redraws */ unsigned long long count; /** redraw idle time. */ guint repaint_source; /** Window fullscreen */ gboolean fullscreen; } CacheState = { .main_window = XCB_WINDOW_NONE, .fake_bg = NULL, .edit_surf = NULL, .edit_draw = NULL, .fake_bgrel = FALSE, .flags = MENU_NORMAL, .views = G_QUEUE_INIT, .idle_timeout = 0, .count = 0L, .repaint_source = 0, .fullscreen = FALSE, }; void rofi_view_get_current_monitor ( int *width, int *height ) { if ( width ) { *width = CacheState.mon.w; } if ( height ) { *height = CacheState.mon.h; } } static char * get_matching_state ( void ) { if ( config.case_sensitive ) { if ( config.sort ) { return "±"; } else { return "-"; } } else{ if ( config.sort ) { return "+"; } } return " "; } /** * Levenshtein Sorting. */ static int lev_sort ( const void *p1, const void *p2, void *arg ) { const int *a = p1; const int *b = p2; int *distances = arg; return distances[*a] - distances[*b]; } /** * Stores a screenshot of Rofi at that point in time. */ void rofi_capture_screenshot ( void ) { const char *outp = g_getenv ( "ROFI_PNG_OUTPUT" ); if ( CacheState.edit_surf == NULL ) { // Nothing to store. g_warning ( "There is no rofi surface to store" ); return; } const char *xdg_pict_dir = g_get_user_special_dir ( G_USER_DIRECTORY_PICTURES ); if ( outp == NULL && xdg_pict_dir == NULL ) { g_warning ( "XDG user picture directory or ROFI_PNG_OUTPUT is not set. Cannot store screenshot." ); return; } // Get current time. GDateTime *now = g_date_time_new_now_local (); // Format filename. char *timestmp = g_date_time_format ( now, "rofi-%Y-%m-%d-%H%M" ); char *filename = g_strdup_printf ( "%s-%05d.png", timestmp, 0 ); // Build full path char *fpath = NULL; if ( outp == NULL ) { int index = 0; fpath = g_build_filename ( xdg_pict_dir, filename, NULL ); while ( g_file_test ( fpath, G_FILE_TEST_EXISTS ) && index < 99 ) { g_free ( fpath ); g_free ( filename ); // Try the next index. index++; // Format filename. filename = g_strdup_printf ( "%s-%05d.png", timestmp, index ); // Build full path fpath = g_build_filename ( xdg_pict_dir, filename, NULL ); } } else { fpath = g_strdup ( outp ); } fprintf ( stderr, color_green "Storing screenshot %s\n"color_reset, fpath ); cairo_status_t status = cairo_surface_write_to_png ( CacheState.edit_surf, fpath ); if ( status != CAIRO_STATUS_SUCCESS ) { g_warning ( "Failed to produce screenshot '%s', got error: '%s'", fpath, cairo_status_to_string ( status ) ); } g_free ( fpath ); g_free ( filename ); g_free ( timestmp ); g_date_time_unref ( now ); } static gboolean rofi_view_repaint ( G_GNUC_UNUSED void * data ) { if ( current_active_menu ) { // Repaint the view (if needed). // After a resize the edit_pixmap surface might not contain anything anymore. // If we already re-painted, this does nothing. rofi_view_update ( current_active_menu, FALSE ); g_debug ( "expose event" ); TICK_N ( "Expose" ); xcb_copy_area ( xcb->connection, CacheState.edit_pixmap, CacheState.main_window, CacheState.gc, 0, 0, 0, 0, current_active_menu->width, current_active_menu->height ); xcb_flush ( xcb->connection ); TICK_N ( "flush" ); CacheState.repaint_source = 0; } return G_SOURCE_REMOVE; } static void rofi_view_update_prompt ( RofiViewState *state ) { if ( state->prompt ) { const char *str = mode_get_display_name ( state->sw ); textbox_text ( state->prompt, str ); } } /** * Calculates the window position */ /** Convert the old location to the new location type. * 123 * 804 * 765 * * nw n ne * w c e * sw s se */ static const int loc_transtable[9] = { WL_CENTER, WL_NORTH | WL_WEST, WL_NORTH, WL_NORTH | WL_EAST, WL_EAST, WL_SOUTH | WL_EAST, WL_SOUTH, WL_SOUTH | WL_WEST, WL_WEST }; static void rofi_view_calculate_window_position ( RofiViewState *state ) { int location = rofi_theme_get_position ( WIDGET ( state->main_window ), "location", loc_transtable[config.location] ); int anchor = location; if ( !listview_get_fixed_num_lines ( state->list_view ) ) { anchor = location; if ( location == WL_CENTER ) { anchor = WL_NORTH; } else if ( location == WL_EAST ) { anchor = WL_NORTH_EAST; } else if ( location == WL_WEST ) { anchor = WL_NORTH_WEST; } } anchor = rofi_theme_get_position ( WIDGET ( state->main_window ), "anchor", anchor ); if ( CacheState.fullscreen ) { state->x = CacheState.mon.x; state->y = CacheState.mon.y; return; } state->y = CacheState.mon.y + ( CacheState.mon.h ) / 2; state->x = CacheState.mon.x + ( CacheState.mon.w ) / 2; // Determine window location switch ( location ) { case WL_NORTH_WEST: state->x = CacheState.mon.x; /* FALLTHRU */ case WL_NORTH: state->y = CacheState.mon.y; break; case WL_NORTH_EAST: state->y = CacheState.mon.y; /* FALLTHRU */ case WL_EAST: state->x = CacheState.mon.x + CacheState.mon.w; break; case WL_SOUTH_EAST: state->x = CacheState.mon.x + CacheState.mon.w; /* FALLTHRU */ case WL_SOUTH: state->y = CacheState.mon.y + CacheState.mon.h; break; case WL_SOUTH_WEST: state->y = CacheState.mon.y + CacheState.mon.h; /* FALLTHRU */ case WL_WEST: state->x = CacheState.mon.x; break; case WL_CENTER: ; /* FALLTHRU */ default: break; } switch ( anchor ) { case WL_SOUTH_WEST: state->y -= state->height; break; case WL_SOUTH: state->x -= state->width / 2; state->y -= state->height; break; case WL_SOUTH_EAST: state->x -= state->width; state->y -= state->height; break; case WL_NORTH_EAST: state->x -= state->width; break; case WL_NORTH_WEST: break; case WL_NORTH: state->x -= state->width / 2; break; case WL_EAST: state->x -= state->width; state->y -= state->height / 2; break; case WL_WEST: state->y -= state->height / 2; break; case WL_CENTER: state->y -= state->height / 2; state->x -= state->width / 2; break; default: break; } // Apply offset. RofiDistance x = rofi_theme_get_distance ( WIDGET ( state->main_window ), "x-offset", config.x_offset ); RofiDistance y = rofi_theme_get_distance ( WIDGET ( state->main_window ), "y-offset", config.y_offset ); state->x += distance_get_pixel ( x, ROFI_ORIENTATION_HORIZONTAL ); state->y += distance_get_pixel ( y, ROFI_ORIENTATION_VERTICAL ); } static void rofi_view_window_update_size ( RofiViewState * state ) { uint16_t mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; uint32_t vals[] = { state->x, state->y, state->width, state->height }; // Display it. xcb_configure_window ( xcb->connection, CacheState.main_window, mask, vals ); cairo_destroy ( CacheState.edit_draw ); cairo_surface_destroy ( CacheState.edit_surf ); xcb_free_pixmap ( xcb->connection, CacheState.edit_pixmap ); CacheState.edit_pixmap = xcb_generate_id ( xcb->connection ); xcb_create_pixmap ( xcb->connection, depth->depth, CacheState.edit_pixmap, CacheState.main_window, state->width, state->height ); CacheState.edit_surf = cairo_xcb_surface_create ( xcb->connection, CacheState.edit_pixmap, visual, state->width, state->height ); CacheState.edit_draw = cairo_create ( CacheState.edit_surf ); g_debug ( "Re-size window based internal request: %dx%d.", state->width, state->height ); // Should wrap main window in a widget. widget_resize ( WIDGET ( state->main_window ), state->width, state->height ); } static void rofi_view_reload_message_bar ( RofiViewState *state ) { if ( state->mesg_box == NULL ) { return; } char *msg = mode_get_message ( state->sw ); if ( msg ) { textbox_text ( state->mesg_tb, msg ); widget_enable ( WIDGET ( state->mesg_box ) ); g_free ( msg ); } else { widget_disable ( WIDGET ( state->mesg_box ) ); } } static gboolean rofi_view_reload_idle ( G_GNUC_UNUSED gpointer data ) { if ( current_active_menu ) { current_active_menu->reload = TRUE; current_active_menu->refilter = TRUE; rofi_view_queue_redraw (); } CacheState.idle_timeout = 0; return G_SOURCE_REMOVE; } void rofi_view_reload ( void ) { // @TODO add check if current view is equal to the callee if ( CacheState.idle_timeout == 0 ) { CacheState.idle_timeout = g_timeout_add ( 1000 / 10, rofi_view_reload_idle, NULL ); } } void rofi_view_queue_redraw ( void ) { if ( current_active_menu && CacheState.repaint_source == 0 ) { CacheState.count++; g_debug ( "redraw %llu", CacheState.count ); CacheState.repaint_source = g_idle_add_full ( G_PRIORITY_HIGH_IDLE, rofi_view_repaint, NULL, NULL ); } } void rofi_view_restart ( RofiViewState *state ) { state->quit = FALSE; state->retv = MENU_CANCEL; } RofiViewState * rofi_view_get_active ( void ) { return current_active_menu; } void rofi_view_set_active ( RofiViewState *state ) { if ( current_active_menu != NULL && state != NULL ) { g_queue_push_head ( &( CacheState.views ), current_active_menu ); // TODO check. current_active_menu = state; g_debug ( "stack view." ); rofi_view_window_update_size ( current_active_menu ); rofi_view_queue_redraw (); return; } else if ( state == NULL && !g_queue_is_empty ( &( CacheState.views ) ) ) { g_debug ( "pop view." ); current_active_menu = g_queue_pop_head ( &( CacheState.views ) ); rofi_view_window_update_size ( current_active_menu ); rofi_view_queue_redraw (); return; } g_assert ( ( current_active_menu == NULL && state != NULL ) || ( current_active_menu != NULL && state == NULL ) ); current_active_menu = state; rofi_view_queue_redraw (); } void rofi_view_set_selected_line ( RofiViewState *state, unsigned int selected_line ) { state->selected_line = selected_line; // Find the line. unsigned int selected = 0; for ( unsigned int i = 0; ( ( state->selected_line ) ) < UINT32_MAX && !selected && i < state->filtered_lines; i++ ) { if ( state->line_map[i] == ( state->selected_line ) ) { selected = i; break; } } listview_set_selected ( state->list_view, selected ); xcb_clear_area ( xcb->connection, CacheState.main_window, 1, 0, 0, 1, 1 ); xcb_flush ( xcb->connection ); } void rofi_view_free ( RofiViewState *state ) { if ( state->tokens ) { helper_tokenize_free ( state->tokens ); state->tokens = NULL; } // Do this here? // Wait for final release? widget_free ( WIDGET ( state->main_window ) ); widget_free ( WIDGET ( state->overlay ) ); g_free ( state->line_map ); g_free ( state->distance ); // Free the switcher boxes. // When state is free'ed we should no longer need these. if ( config.sidebar_mode == TRUE ) { g_free ( state->modi ); state->num_modi = 0; } g_free ( state ); } MenuReturn rofi_view_get_return_value ( const RofiViewState *state ) { return state->retv; } unsigned int rofi_view_get_selected_line ( const RofiViewState *state ) { return state->selected_line; } unsigned int rofi_view_get_next_position ( const RofiViewState *state ) { unsigned int next_pos = state->selected_line; unsigned int selected = listview_get_selected ( state->list_view ); if ( ( selected + 1 ) < state->num_lines ) { ( next_pos ) = state->line_map[selected + 1]; } return next_pos; } unsigned int rofi_view_get_completed ( const RofiViewState *state ) { return state->quit; } const char * rofi_view_get_user_input ( const RofiViewState *state ) { if ( state->text ) { return state->text->text; } return NULL; } /** * Create a new, 0 initialized RofiViewState structure. * * @returns a new 0 initialized RofiViewState */ static RofiViewState * __rofi_view_state_create ( void ) { return g_malloc0 ( sizeof ( RofiViewState ) ); } /** * Structure with data to process by each worker thread. */ typedef struct _thread_state { RofiViewState *state; unsigned int start; unsigned int stop; unsigned int count; GCond *cond; GMutex *mutex; unsigned int *acount; const char *pattern; glong plen; void ( *callback )( struct _thread_state *t, gpointer data ); }thread_state; /** * @param data A thread_state object. * @param user_data User data to pass to thread_state callback * * Small wrapper function that is internally used to pass a job to a worker. */ static void rofi_view_call_thread ( gpointer data, gpointer user_data ) { thread_state *t = (thread_state *) data; t->callback ( t, user_data ); g_mutex_lock ( t->mutex ); ( *( t->acount ) )--; g_cond_signal ( t->cond ); g_mutex_unlock ( t->mutex ); } static void filter_elements ( thread_state *t, G_GNUC_UNUSED gpointer user_data ) { for ( unsigned int i = t->start; i < t->stop; i++ ) { int match = mode_token_match ( t->state->sw, t->state->tokens, i ); // If each token was matched, add it to list. if ( match ) { t->state->line_map[t->start + t->count] = i; if ( config.sort ) { // This is inefficient, need to fix it. char * str = mode_get_completion ( t->state->sw, i ); glong slen = g_utf8_strlen ( str, -1 ); if ( config.levenshtein_sort || config.matching_method != MM_FUZZY ) { t->state->distance[i] = levenshtein ( t->pattern, t->plen, str, slen ); } else { t->state->distance[i] = rofi_scorer_fuzzy_evaluate ( t->pattern, t->plen, str, slen ); } g_free ( str ); } t->count++; } } } static void rofi_view_setup_fake_transparency ( const char* const fake_background ) { if ( CacheState.fake_bg == NULL ) { cairo_surface_t *s = NULL; /** * Select Background to use for fake transparency. * Current options: 'real', 'screenshot','background' */ TICK_N ( "Fake start" ); if ( g_strcmp0 ( fake_background, "real" ) == 0 ) { return; } else if ( g_strcmp0 ( fake_background, "screenshot" ) == 0 ) { s = x11_helper_get_screenshot_surface (); } else if ( g_strcmp0 ( fake_background, "background" ) == 0 ) { s = x11_helper_get_bg_surface (); } else { char *fpath = rofi_expand_path ( fake_background ); g_debug ( "Opening %s to use as background.", fpath ); s = cairo_image_surface_create_from_png ( fpath ); CacheState.fake_bgrel = TRUE; g_free ( fpath ); } TICK_N ( "Get surface." ); if ( s != NULL ) { if ( cairo_surface_status ( s ) != CAIRO_STATUS_SUCCESS ) { g_debug ( "Failed to open surface fake background: %s", cairo_status_to_string ( cairo_surface_status ( s ) ) ); cairo_surface_destroy ( s ); s = NULL; } else { CacheState.fake_bg = cairo_image_surface_create ( CAIRO_FORMAT_ARGB32, CacheState.mon.w, CacheState.mon.h ); cairo_t *dr = cairo_create ( CacheState.fake_bg ); if ( CacheState.fake_bgrel ) { cairo_set_source_surface ( dr, s, 0, 0 ); } else { cairo_set_source_surface ( dr, s, -CacheState.mon.x, -CacheState.mon.y ); } cairo_paint ( dr ); cairo_destroy ( dr ); cairo_surface_destroy ( s ); } } TICK_N ( "Fake transparency" ); } } void __create_window ( MenuFlags menu_flags ) { uint32_t selmask = XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL | XCB_CW_BIT_GRAVITY | XCB_CW_BACKING_STORE | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP; uint32_t selval[] = { XCB_BACK_PIXMAP_NONE, 0, XCB_GRAVITY_STATIC, XCB_BACKING_STORE_NOT_USEFUL, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_KEYMAP_STATE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_BUTTON_1_MOTION, map }; xcb_window_t box_window = xcb_generate_id ( xcb->connection ); xcb_void_cookie_t cc = xcb_create_window_checked ( xcb->connection, depth->depth, box_window, xcb_stuff_get_root_window ( ), 0, 0, 200, 100, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visual->visual_id, selmask, selval ); xcb_generic_error_t *error; error = xcb_request_check ( xcb->connection, cc ); if ( error ) { g_error ( "xcb_create_window() failed error=0x%x\n", error->error_code ); exit ( EXIT_FAILURE ); } TICK_N ( "xcb create window" ); CacheState.gc = xcb_generate_id ( xcb->connection ); xcb_create_gc ( xcb->connection, CacheState.gc, box_window, 0, 0 ); TICK_N ( "xcb create gc" ); // Create a drawable. CacheState.edit_pixmap = xcb_generate_id ( xcb->connection ); xcb_create_pixmap ( xcb->connection, depth->depth, CacheState.edit_pixmap, CacheState.main_window, 200, 100 ); CacheState.edit_surf = cairo_xcb_surface_create ( xcb->connection, CacheState.edit_pixmap, visual, 200, 100 ); CacheState.edit_draw = cairo_create ( CacheState.edit_surf ); TICK_N ( "create cairo surface" ); // Set up pango context. cairo_font_options_t *fo = cairo_font_options_create (); // Take font description from xlib surface cairo_surface_get_font_options ( CacheState.edit_surf, fo ); // TODO should we update the drawable each time? PangoContext *p = pango_cairo_create_context ( CacheState.edit_draw ); // Set the font options from the xlib surface pango_cairo_context_set_font_options ( p, fo ); TICK_N ( "pango cairo font setup" ); CacheState.main_window = box_window; CacheState.flags = menu_flags; monitor_active ( &( CacheState.mon ) ); // Setup dpi if ( config.dpi > 1 ) { PangoFontMap *font_map = pango_cairo_font_map_get_default (); pango_cairo_font_map_set_resolution ( (PangoCairoFontMap *) font_map, (double) config.dpi ); } else if ( config.dpi == 0 || config.dpi == 1 ) { // Auto-detect mode. double dpi = 96; if ( CacheState.mon.mh > 0 && config.dpi == 1 ) { dpi = ( CacheState.mon.h * 25.4 ) / (double) ( CacheState.mon.mh ); } else { dpi = ( xcb->screen->height_in_pixels * 25.4 ) / (double) ( xcb->screen->height_in_millimeters ); } g_debug ( "Auto-detected DPI: %.2lf", dpi ); PangoFontMap *font_map = pango_cairo_font_map_get_default (); pango_cairo_font_map_set_resolution ( (PangoCairoFontMap *) font_map, dpi ); config.dpi = dpi; } // Setup font. // Dummy widget. box *win = box_create ( NULL, "window", ROFI_ORIENTATION_HORIZONTAL ); const char *font = rofi_theme_get_string ( WIDGET ( win ), "font", config.menu_font ); if ( font ) { PangoFontDescription *pfd = pango_font_description_from_string ( font ); if ( helper_validate_font ( pfd, font ) ) { pango_context_set_font_description ( p, pfd ); } pango_font_description_free ( pfd ); } PangoLanguage *l = pango_language_get_default (); pango_context_set_language ( p, l ); TICK_N ( "configure font" ); // Tell textbox to use this context. textbox_set_pango_context ( font, p ); // cleanup g_object_unref ( p ); cairo_font_options_destroy ( fo ); TICK_N ( "textbox setup" ); // // make it an unmanaged window if ( ( ( menu_flags & MENU_NORMAL_WINDOW ) == 0 ) ) { window_set_atom_prop ( box_window, xcb->ewmh._NET_WM_STATE, &( xcb->ewmh._NET_WM_STATE_ABOVE ), 1 ); uint32_t values[] = { 1 }; xcb_change_window_attributes ( xcb->connection, box_window, XCB_CW_OVERRIDE_REDIRECT, values ); } else{ window_set_atom_prop ( box_window, xcb->ewmh._NET_WM_WINDOW_TYPE, &( xcb->ewmh._NET_WM_WINDOW_TYPE_NORMAL ), 1 ); x11_disable_decoration ( box_window ); } TICK_N ( "setup window attributes" ); CacheState.fullscreen = rofi_theme_get_boolean ( WIDGET ( win ), "fullscreen", config.fullscreen ); if ( CacheState.fullscreen ) { xcb_atom_t atoms[] = { xcb->ewmh._NET_WM_STATE_FULLSCREEN, xcb->ewmh._NET_WM_STATE_ABOVE }; window_set_atom_prop ( box_window, xcb->ewmh._NET_WM_STATE, atoms, sizeof ( atoms ) / sizeof ( xcb_atom_t ) ); } TICK_N ( "setup window fullscreen" ); // Set the WM_NAME xcb_change_property ( xcb->connection, XCB_PROP_MODE_REPLACE, box_window, xcb->ewmh._NET_WM_NAME, xcb->ewmh.UTF8_STRING, 8, 4, "rofi" ); xcb_change_property ( xcb->connection, XCB_PROP_MODE_REPLACE, box_window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, 4, "rofi" ); const char wm_class_name[] = "rofi\0Rofi"; xcb_icccm_set_wm_class ( xcb->connection, box_window, sizeof ( wm_class_name ), wm_class_name ); TICK_N ( "setup window name and class" ); const char *transparency = rofi_theme_get_string ( WIDGET ( win ), "transparency", NULL ); if ( transparency ) { rofi_view_setup_fake_transparency ( transparency ); } else if ( config.fake_transparency && config.fake_background ) { rofi_view_setup_fake_transparency ( config.fake_background ); } if ( xcb->sncontext != NULL ) { sn_launchee_context_setup_window ( xcb->sncontext, CacheState.main_window ); } TICK_N ( "setup startup notification" ); widget_free ( WIDGET ( win ) ); TICK_N ( "done" ); // Set the PID. pid_t pid = getpid (); xcb_ewmh_set_wm_pid ( &( xcb->ewmh ), CacheState.main_window, pid ); // Get hostname const char *hostname = g_get_host_name (); char *ahost = g_hostname_to_ascii ( hostname ); if ( ahost != NULL ) { xcb_icccm_set_wm_client_machine ( xcb->connection, CacheState.main_window, XCB_ATOM_STRING, 8, strlen ( ahost ), ahost ); g_free ( ahost ); } } /** * @param state Internal state of the menu. * * Calculate the width of the window and the width of an element. */ static void rofi_view_calculate_window_width ( RofiViewState *state ) { if ( CacheState.fullscreen ) { state->width = CacheState.mon.w; return; } if ( config.menu_width < 0 ) { double fw = textbox_get_estimated_char_width ( ); state->width = -( fw * config.menu_width ); state->width += widget_padding_get_padding_width ( WIDGET ( state->main_window ) ); } else{ // Calculate as float to stop silly, big rounding down errors. state->width = config.menu_width < 101 ? ( CacheState.mon.w / 100.0f ) * ( float ) config.menu_width : config.menu_width; } // Use theme configured width, if set. RofiDistance width = rofi_theme_get_distance ( WIDGET ( state->main_window ), "width", state->width ); state->width = distance_get_pixel ( width, ROFI_ORIENTATION_HORIZONTAL ); } /** * Nav helper functions, to avoid duplicate code. */ /** * @param state The current RofiViewState * * Tab handling. */ static void rofi_view_nav_row_tab ( RofiViewState *state ) { if ( state->filtered_lines == 1 ) { state->retv = MENU_OK; ( state->selected_line ) = state->line_map[listview_get_selected ( state->list_view )]; state->quit = 1; return; } // Double tab! if ( state->filtered_lines == 0 && ROW_TAB == state->prev_action ) { state->retv = MENU_NEXT; ( state->selected_line ) = 0; state->quit = TRUE; } else { listview_nav_down ( state->list_view ); } state->prev_action = ROW_TAB; } /** * @param state The current RofiViewState * * complete current row. */ inline static void rofi_view_nav_row_select ( RofiViewState *state ) { if ( state->list_view == NULL ) { return; } unsigned int selected = listview_get_selected ( state->list_view ); // If a valid item is selected, return that.. if ( selected < state->filtered_lines ) { char *str = mode_get_completion ( state->sw, state->line_map[selected] ); textbox_text ( state->text, str ); g_free ( str ); textbox_keybinding ( state->text, MOVE_END ); state->refilter = TRUE; } } /** * @param state The current RofiViewState * * Move the selection to first row. */ inline static void rofi_view_nav_first ( RofiViewState * state ) { // state->selected = 0; listview_set_selected ( state->list_view, 0 ); } /** * @param state The current RofiViewState * * Move the selection to last row. */ inline static void rofi_view_nav_last ( RofiViewState * state ) { // If no lines, do nothing. if ( state->filtered_lines == 0 ) { return; } //state->selected = state->filtered_lines - 1; listview_set_selected ( state->list_view, -1 ); } static void update_callback ( textbox *t, unsigned int index, void *udata, TextBoxFontType type, gboolean full ) { RofiViewState *state = (RofiViewState *) udata; if ( full ) { GList *add_list = NULL; int fstate = 0; char *text = mode_get_display_value ( state->sw, state->line_map[index], &fstate, &add_list, TRUE ); type |= fstate; textbox_font ( t, type ); // Move into list view. textbox_text ( t, text ); PangoAttrList *list = textbox_get_pango_attributes ( t ); if ( list != NULL ) { pango_attr_list_ref ( list ); } else{ list = pango_attr_list_new (); } int icon_height = textbox_get_font_height ( t ); cairo_surface_t *icon = mode_get_icon ( state->sw, state->line_map[index], icon_height ); textbox_icon ( t, icon ); if ( state->tokens && config.show_match ) { RofiHighlightColorStyle th = { ROFI_HL_BOLD | ROFI_HL_UNDERLINE, { 0.0, 0.0, 0.0, 0.0 } }; th = rofi_theme_get_highlight ( WIDGET ( t ), "highlight", th ); helper_token_match_get_pango_attr ( th, state->tokens, textbox_get_visible_text ( t ), list ); } for ( GList *iter = g_list_first ( add_list ); iter != NULL; iter = g_list_next ( iter ) ) { pango_attr_list_insert ( list, (PangoAttribute *) ( iter->data ) ); } textbox_set_pango_attributes ( t, list ); pango_attr_list_unref ( list ); g_list_free ( add_list ); g_free ( text ); } else { int fstate = 0; mode_get_display_value ( state->sw, state->line_map[index], &fstate, NULL, FALSE ); type |= fstate; textbox_font ( t, type ); } } void rofi_view_update ( RofiViewState *state, gboolean qr ) { if ( !widget_need_redraw ( WIDGET ( state->main_window ) ) ) { return; } g_debug ( "Redraw view" ); TICK (); cairo_t *d = CacheState.edit_draw; cairo_set_operator ( d, CAIRO_OPERATOR_SOURCE ); if ( CacheState.fake_bg != NULL ) { if ( CacheState.fake_bgrel ) { cairo_set_source_surface ( d, CacheState.fake_bg, 0.0, 0.0 ); } else { cairo_set_source_surface ( d, CacheState.fake_bg, -(double) ( state->x - CacheState.mon.x ), -(double) ( state->y - CacheState.mon.y ) ); } cairo_paint ( d ); cairo_set_operator ( d, CAIRO_OPERATOR_OVER ); } else { // Paint the background transparent. cairo_set_source_rgba ( d, 0, 0, 0, 0.0 ); cairo_paint ( d ); } TICK_N ( "Background" ); // Always paint as overlay over the background. cairo_set_operator ( d, CAIRO_OPERATOR_OVER ); widget_draw ( WIDGET ( state->main_window ), d ); if ( state->overlay ) { widget_draw ( WIDGET ( state->overlay ), d ); } TICK_N ( "widgets" ); cairo_surface_flush ( CacheState.edit_surf ); if ( qr ) { rofi_view_queue_redraw (); } } static void _rofi_view_reload_row ( RofiViewState *state ) { g_free ( state->line_map ); g_free ( state->distance ); state->num_lines = mode_get_num_entries ( state->sw ); state->line_map = g_malloc0_n ( state->num_lines, sizeof ( unsigned int ) ); state->distance = g_malloc0_n ( state->num_lines, sizeof ( int ) ); listview_set_max_lines ( state->list_view, state->num_lines ); rofi_view_reload_message_bar ( state ); } static void rofi_view_refilter ( RofiViewState *state ) { TICK_N ( "Filter start" ); if ( state->reload ) { _rofi_view_reload_row ( state ); state->reload = FALSE; } if ( state->tokens ) { helper_tokenize_free ( state->tokens ); state->tokens = NULL; } if ( state->text && strlen ( state->text->text ) > 0 ) { unsigned int j = 0; gchar *pattern = mode_preprocess_input ( state->sw, state->text->text ); glong plen = pattern ? g_utf8_strlen ( pattern, -1 ) : 0; state->tokens = helper_tokenize ( pattern, config.case_sensitive ); /** * On long lists it can be beneficial to parallelize. * If number of threads is 1, no thread is spawn. * If number of threads > 1 and there are enough (> 1000) items, spawn jobs for the thread pool. * For large lists with 8 threads I see a factor three speedup of the whole function. */ unsigned int nt = MAX ( 1, state->num_lines / 500 ); thread_state states[nt]; GCond cond; GMutex mutex; g_mutex_init ( &mutex ); g_cond_init ( &cond ); unsigned int count = nt; unsigned int steps = ( state->num_lines + nt ) / nt; for ( unsigned int i = 0; i < nt; i++ ) { states[i].state = state; states[i].start = i * steps; states[i].stop = MIN ( state->num_lines, ( i + 1 ) * steps ); states[i].count = 0; states[i].cond = &cond; states[i].mutex = &mutex; states[i].acount = &count; states[i].plen = plen; states[i].pattern = pattern; states[i].callback = filter_elements; if ( i > 0 ) { g_thread_pool_push ( tpool, &states[i], NULL ); } } // Run one in this thread. rofi_view_call_thread ( &states[0], NULL ); // No need to do this with only one thread. if ( nt > 1 ) { g_mutex_lock ( &mutex ); while ( count > 0 ) { g_cond_wait ( &cond, &mutex ); } g_mutex_unlock ( &mutex ); } g_cond_clear ( &cond ); g_mutex_clear ( &mutex ); for ( unsigned int i = 0; i < nt; i++ ) { if ( j != states[i].start ) { memmove ( &( state->line_map[j] ), &( state->line_map[states[i].start] ), sizeof ( unsigned int ) * ( states[i].count ) ); } j += states[i].count; } if ( config.sort ) { g_qsort_with_data ( state->line_map, j, sizeof ( int ), lev_sort, state->distance ); } // Cleanup + bookkeeping. state->filtered_lines = j; g_free ( pattern ); } else{ for ( unsigned int i = 0; i < state->num_lines; i++ ) { state->line_map[i] = i; } state->filtered_lines = state->num_lines; } listview_set_num_elements ( state->list_view, state->filtered_lines ); if ( config.auto_select == TRUE && state->filtered_lines == 1 && state->num_lines > 1 ) { ( state->selected_line ) = state->line_map[listview_get_selected ( state->list_view )]; state->retv = MENU_OK; state->quit = TRUE; } // Size the window. int height = rofi_view_calculate_height ( state ); if ( height != state->height ) { state->height = height; rofi_view_calculate_window_position ( state ); rofi_view_window_update_size ( state ); g_debug ( "Resize based on re-filter" ); } state->refilter = FALSE; TICK_N ( "Filter done" ); } /** * @param state The Menu Handle * * Check if a finalize function is set, and if sets executes it. */ void process_result ( RofiViewState *state ); void rofi_view_finalize ( RofiViewState *state ) { if ( state && state->finalize != NULL ) { state->finalize ( state ); } } static void rofi_view_trigger_global_action ( KeyBindingAction action ) { RofiViewState *state = rofi_view_get_active (); switch ( action ) { // Handling of paste case PASTE_PRIMARY: xcb_convert_selection ( xcb->connection, CacheState.main_window, XCB_ATOM_PRIMARY, xcb->ewmh.UTF8_STRING, xcb->ewmh.UTF8_STRING, XCB_CURRENT_TIME ); xcb_flush ( xcb->connection ); break; case PASTE_SECONDARY: xcb_convert_selection ( xcb->connection, CacheState.main_window, netatoms[CLIPBOARD], xcb->ewmh.UTF8_STRING, xcb->ewmh.UTF8_STRING, XCB_CURRENT_TIME ); xcb_flush ( xcb->connection ); break; case SCREENSHOT: rofi_capture_screenshot ( ); break; case TOGGLE_SORT: if ( state->case_indicator != NULL ) { config.sort = !config.sort; state->refilter = TRUE; textbox_text ( state->case_indicator, get_matching_state () ); } break; case MODE_PREVIOUS: state->retv = MENU_PREVIOUS; ( state->selected_line ) = 0; state->quit = TRUE; break; // Menu navigation. case MODE_NEXT: state->retv = MENU_NEXT; ( state->selected_line ) = 0; state->quit = TRUE; break; // Toggle case sensitivity. case TOGGLE_CASE_SENSITIVITY: if ( state->case_indicator != NULL ) { config.case_sensitive = !config.case_sensitive; ( state->selected_line ) = 0; state->refilter = TRUE; textbox_text ( state->case_indicator, get_matching_state () ); } break; // Special delete entry command. case DELETE_ENTRY: { unsigned int selected = listview_get_selected ( state->list_view ); if ( selected < state->filtered_lines ) { ( state->selected_line ) = state->line_map[selected]; state->retv = MENU_ENTRY_DELETE; state->quit = TRUE; } break; } case SELECT_ELEMENT_1: case SELECT_ELEMENT_2: case SELECT_ELEMENT_3: case SELECT_ELEMENT_4: case SELECT_ELEMENT_5: case SELECT_ELEMENT_6: case SELECT_ELEMENT_7: case SELECT_ELEMENT_8: case SELECT_ELEMENT_9: case SELECT_ELEMENT_10: { unsigned int index = action - SELECT_ELEMENT_1; if ( index < state->filtered_lines ) { state->selected_line = state->line_map[index]; state->retv = MENU_OK; state->quit = TRUE; } break; } case CUSTOM_1: case CUSTOM_2: case CUSTOM_3: case CUSTOM_4: case CUSTOM_5: case CUSTOM_6: case CUSTOM_7: case CUSTOM_8: case CUSTOM_9: case CUSTOM_10: case CUSTOM_11: case CUSTOM_12: case CUSTOM_13: case CUSTOM_14: case CUSTOM_15: case CUSTOM_16: case CUSTOM_17: case CUSTOM_18: case CUSTOM_19: { state->selected_line = UINT32_MAX; unsigned int selected = listview_get_selected ( state->list_view ); if ( selected < state->filtered_lines ) { ( state->selected_line ) = state->line_map[selected]; } state->retv = MENU_QUICK_SWITCH | ( ( action - CUSTOM_1 ) & MENU_LOWER_MASK ); state->quit = TRUE; break; } // If you add a binding here, make sure to add it to rofi_view_keyboard_navigation too case CANCEL: state->retv = MENU_CANCEL; state->quit = TRUE; break; case ROW_UP: listview_nav_up ( state->list_view ); break; case ROW_TAB: rofi_view_nav_row_tab ( state ); break; case ROW_DOWN: listview_nav_down ( state->list_view ); break; case ROW_LEFT: listview_nav_left ( state->list_view ); break; case ROW_RIGHT: listview_nav_right ( state->list_view ); break; case PAGE_PREV: listview_nav_page_prev ( state->list_view ); break; case PAGE_NEXT: listview_nav_page_next ( state->list_view ); break; case ROW_FIRST: rofi_view_nav_first ( state ); break; case ROW_LAST: rofi_view_nav_last ( state ); break; case ROW_SELECT: rofi_view_nav_row_select ( state ); break; // If you add a binding here, make sure to add it to textbox_keybinding too case MOVE_CHAR_BACK: { if ( textbox_keybinding ( state->text, action ) == 0 ) { listview_nav_left ( state->list_view ); } break; } case MOVE_CHAR_FORWARD: { if ( textbox_keybinding ( state->text, action ) == 0 ) { listview_nav_right ( state->list_view ); } break; } case CLEAR_LINE: case MOVE_FRONT: case MOVE_END: case REMOVE_TO_EOL: case REMOVE_TO_SOL: case REMOVE_WORD_BACK: case REMOVE_WORD_FORWARD: case REMOVE_CHAR_FORWARD: case MOVE_WORD_BACK: case MOVE_WORD_FORWARD: case REMOVE_CHAR_BACK: { int rc = textbox_keybinding ( state->text, action ); if ( rc == 1 ) { // Entry changed. state->refilter = TRUE; } else if ( rc == 2 ) { // Movement. } break; } case ACCEPT_ALT: { unsigned int selected = listview_get_selected ( state->list_view ); state->selected_line = UINT32_MAX; if ( selected < state->filtered_lines ) { ( state->selected_line ) = state->line_map[selected]; state->retv = MENU_OK; } else { // Nothing entered and nothing selected. state->retv = MENU_CUSTOM_INPUT; } state->retv |= MENU_CUSTOM_ACTION; state->quit = TRUE; break; } case ACCEPT_CUSTOM: { state->selected_line = UINT32_MAX; state->retv = MENU_CUSTOM_INPUT; state->quit = TRUE; break; } case ACCEPT_ENTRY: { // If a valid item is selected, return that.. unsigned int selected = listview_get_selected ( state->list_view ); state->selected_line = UINT32_MAX; if ( selected < state->filtered_lines ) { ( state->selected_line ) = state->line_map[selected]; state->retv = MENU_OK; } else { // Nothing entered and nothing selected. state->retv = MENU_CUSTOM_INPUT; } state->quit = TRUE; break; } } } gboolean rofi_view_trigger_action ( RofiViewState *state, BindingsScope scope, guint action ) { switch ( scope ) { case SCOPE_GLOBAL: rofi_view_trigger_global_action ( action ); return TRUE; case SCOPE_MOUSE_LISTVIEW: case SCOPE_MOUSE_LISTVIEW_ELEMENT: case SCOPE_MOUSE_EDITBOX: case SCOPE_MOUSE_SCROLLBAR: case SCOPE_MOUSE_SIDEBAR_MODI: { gint x = state->mouse.x, y = state->mouse.y; widget *target = widget_find_mouse_target ( WIDGET ( state->main_window ), scope, x, y ); if ( target == NULL ) { return FALSE; } widget_xy_to_relative ( target, &x, &y ); switch ( widget_trigger_action ( target, action, x, y ) ) { case WIDGET_TRIGGER_ACTION_RESULT_IGNORED: return FALSE; case WIDGET_TRIGGER_ACTION_RESULT_GRAB_MOTION_END: target = NULL; /* FALLTHRU */ case WIDGET_TRIGGER_ACTION_RESULT_GRAB_MOTION_BEGIN: state->mouse.motion_target = target; /* FALLTHRU */ case WIDGET_TRIGGER_ACTION_RESULT_HANDLED: return TRUE; } break; } } return FALSE; } void rofi_view_handle_text ( RofiViewState *state, char *text ) { if ( textbox_append_text ( state->text, text, strlen ( text ) ) ) { state->refilter = TRUE; } } void rofi_view_handle_mouse_motion ( RofiViewState *state, gint x, gint y ) { state->mouse.x = x; state->mouse.y = y; if ( state->mouse.motion_target != NULL ) { widget_xy_to_relative ( state->mouse.motion_target, &x, &y ); widget_motion_notify ( state->mouse.motion_target, x, y ); } } void rofi_view_maybe_update ( RofiViewState *state ) { if ( rofi_view_get_completed ( state ) ) { // This menu is done. rofi_view_finalize ( state ); // If there a state. (for example error) reload it. state = rofi_view_get_active (); // cleanup, if no more state to display. if ( state == NULL ) { // Quit main-loop. rofi_quit_main_loop (); return; } } // Update if requested. if ( state->refilter ) { rofi_view_refilter ( state ); } rofi_view_update ( state, TRUE ); } void rofi_view_temp_configure_notify ( RofiViewState *state, xcb_configure_notify_event_t *xce ) { if ( xce->window == CacheState.main_window ) { if ( state->x != xce->x || state->y != xce->y ) { state->x = xce->x; state->y = xce->y; widget_queue_redraw ( WIDGET ( state->main_window ) ); } if ( state->width != xce->width || state->height != xce->height ) { state->width = xce->width; state->height = xce->height; cairo_destroy ( CacheState.edit_draw ); cairo_surface_destroy ( CacheState.edit_surf ); xcb_free_pixmap ( xcb->connection, CacheState.edit_pixmap ); CacheState.edit_pixmap = xcb_generate_id ( xcb->connection ); xcb_create_pixmap ( xcb->connection, depth->depth, CacheState.edit_pixmap, CacheState.main_window, state->width, state->height ); CacheState.edit_surf = cairo_xcb_surface_create ( xcb->connection, CacheState.edit_pixmap, visual, state->width, state->height ); CacheState.edit_draw = cairo_create ( CacheState.edit_surf ); g_debug ( "Re-size window based external request: %d %d", state->width, state->height ); widget_resize ( WIDGET ( state->main_window ), state->width, state->height ); } } } void rofi_view_temp_click_to_exit ( RofiViewState *state, xcb_window_t target ) { if ( ( CacheState.flags & MENU_NORMAL_WINDOW ) == 0 ) { if ( target != CacheState.main_window ) { state->quit = TRUE; state->retv = MENU_CANCEL; } } } void rofi_view_frame_callback ( void ) { if ( CacheState.repaint_source == 0 ) { CacheState.repaint_source = g_idle_add_full ( G_PRIORITY_HIGH_IDLE, rofi_view_repaint, NULL, NULL ); } } static int rofi_view_calculate_height ( RofiViewState *state ) { if ( CacheState.fullscreen == TRUE ) { return CacheState.mon.h; } RofiDistance h = rofi_theme_get_distance ( WIDGET ( state->main_window ), "height", 0 ); unsigned int height = distance_get_pixel ( h, ROFI_ORIENTATION_VERTICAL ); // If height is set, return it. if ( height > 0 ) { return height; } // Autosize based on widgets. widget *main_window = WIDGET ( state->main_window ); return widget_get_desired_height ( main_window ); } static WidgetTriggerActionResult textbox_sidebar_modi_trigger_action ( widget *wid, MouseBindingMouseDefaultAction action, G_GNUC_UNUSED gint x, G_GNUC_UNUSED gint y, G_GNUC_UNUSED void *user_data ) { RofiViewState *state = ( RofiViewState *) user_data; unsigned int i; for ( i = 0; i < state->num_modi; i++ ) { if ( WIDGET ( state->modi[i] ) == wid ) { break; } } if ( i == state->num_modi ) { return WIDGET_TRIGGER_ACTION_RESULT_IGNORED; } switch ( action ) { case MOUSE_CLICK_DOWN: state->retv = MENU_QUICK_SWITCH | ( i & MENU_LOWER_MASK ); state->quit = TRUE; state->skip_absorb = TRUE; return WIDGET_TRIGGER_ACTION_RESULT_HANDLED; case MOUSE_CLICK_UP: case MOUSE_DCLICK_DOWN: case MOUSE_DCLICK_UP: break; } return WIDGET_TRIGGER_ACTION_RESULT_IGNORED; } // @TODO don't like this construction. static void rofi_view_listview_mouse_activated_cb ( listview *lv, gboolean custom, void *udata ) { RofiViewState *state = (RofiViewState *) udata; state->retv = MENU_OK; if ( custom ) { state->retv |= MENU_CUSTOM_ACTION; } ( state->selected_line ) = state->line_map[listview_get_selected ( lv )]; // Quit state->quit = TRUE; state->skip_absorb = TRUE; } static void rofi_view_add_widget ( RofiViewState *state, widget *parent_widget, const char *name ) { char *defaults = NULL; widget *wid = NULL; /** * MAINBOX */ if ( strcmp ( name, "mainbox" ) == 0 ) { wid = (widget *) box_create ( parent_widget, name, ROFI_ORIENTATION_VERTICAL ); box_add ( (box *) parent_widget, WIDGET ( wid ), TRUE ); defaults = "inputbar,message,listview,sidebar"; } /** * INPUTBAR */ else if ( strcmp ( name, "inputbar" ) == 0 ) { wid = (widget *) box_create ( parent_widget, name, ROFI_ORIENTATION_HORIZONTAL ); defaults = "prompt,entry,case-indicator"; box_add ( (box *) parent_widget, WIDGET ( wid ), FALSE ); } /** * PROMPT */ else if ( strcmp ( name, "prompt" ) == 0 ) { if ( state->prompt != NULL ) { g_error ( "Prompt widget can only be added once to the layout." ); return; } // Prompt box. state->prompt = textbox_create ( parent_widget, WIDGET_TYPE_TEXTBOX_TEXT, name, TB_AUTOWIDTH | TB_AUTOHEIGHT, NORMAL, "", 0, 0 ); rofi_view_update_prompt ( state ); box_add ( (box *) parent_widget, WIDGET ( state->prompt ), FALSE ); defaults = NULL; } /** * CASE INDICATOR */ else if ( strcmp ( name, "case-indicator" ) == 0 ) { if ( state->case_indicator != NULL ) { g_error ( "Case indicator widget can only be added once to the layout." ); return; } state->case_indicator = textbox_create ( parent_widget, WIDGET_TYPE_TEXTBOX_TEXT, name, TB_AUTOWIDTH | TB_AUTOHEIGHT, NORMAL, "*", 0, 0 ); // Add small separator between case indicator and text box. box_add ( (box *) parent_widget, WIDGET ( state->case_indicator ), FALSE ); textbox_text ( state->case_indicator, get_matching_state () ); } /** * ENTRY BOX */ else if ( strcmp ( name, "entry" ) == 0 ) { if ( state->text != NULL ) { g_error ( "Entry textbox widget can only be added once to the layout." ); return; } // Entry box TextboxFlags tfl = TB_EDITABLE; tfl |= ( ( state->menu_flags & MENU_PASSWORD ) == MENU_PASSWORD ) ? TB_PASSWORD : 0; state->text = textbox_create ( parent_widget, WIDGET_TYPE_EDITBOX, name, tfl | TB_AUTOHEIGHT, NORMAL, NULL, 0, 0 ); box_add ( (box *) parent_widget, WIDGET ( state->text ), TRUE ); } /** * MESSAGE */ else if ( strcmp ( name, "message" ) == 0 ) { if ( state->mesg_box != NULL ) { g_error ( "Message widget can only be added once to the layout." ); return; } state->mesg_box = container_create ( parent_widget, name ); state->mesg_tb = textbox_create ( WIDGET ( state->mesg_box ), WIDGET_TYPE_TEXTBOX_TEXT, "textbox", TB_AUTOHEIGHT | TB_MARKUP | TB_WRAP, NORMAL, NULL, 0, 0 ); container_add ( state->mesg_box, WIDGET ( state->mesg_tb ) ); rofi_view_reload_message_bar ( state ); box_add ( (box *) parent_widget, WIDGET ( state->mesg_box ), FALSE ); } /** * LISTVIEW */ else if ( strcmp ( name, "listview" ) == 0 ) { if ( state->list_view != NULL ) { g_error ( "Listview widget can only be added once to the layout." ); return; } state->list_view = listview_create ( parent_widget, name, update_callback, state, config.element_height, 0 ); box_add ( (box *) parent_widget, WIDGET ( state->list_view ), TRUE ); // Set configuration listview_set_multi_select ( state->list_view, ( state->menu_flags & MENU_INDICATOR ) == MENU_INDICATOR ); listview_set_scroll_type ( state->list_view, config.scroll_method ); listview_set_mouse_activated_cb ( state->list_view, rofi_view_listview_mouse_activated_cb, state ); int lines = rofi_theme_get_integer ( WIDGET ( state->list_view ), "lines", config.menu_lines ); listview_set_num_lines ( state->list_view, lines ); listview_set_max_lines ( state->list_view, state->num_lines ); } /** * SIDEBAR */ else if ( strcmp ( name, "sidebar" ) == 0 ) { if ( state->sidebar_bar != NULL ) { g_error ( "Sidebar widget can only be added once to the layout." ); return; } if ( config.sidebar_mode ) { state->sidebar_bar = box_create ( parent_widget, name, ROFI_ORIENTATION_HORIZONTAL ); box_add ( (box *) parent_widget, WIDGET ( state->sidebar_bar ), FALSE ); state->num_modi = rofi_get_num_enabled_modi (); state->modi = g_malloc0 ( state->num_modi * sizeof ( textbox * ) ); for ( unsigned int j = 0; j < state->num_modi; j++ ) { const Mode * mode = rofi_get_mode ( j ); state->modi[j] = textbox_create ( WIDGET ( state->sidebar_bar ), WIDGET_TYPE_SIDEBAR_MODI, "button", TB_AUTOHEIGHT, ( mode == state->sw ) ? HIGHLIGHT : NORMAL, mode_get_display_name ( mode ), 0.5, 0.5 ); box_add ( state->sidebar_bar, WIDGET ( state->modi[j] ), TRUE ); widget_set_trigger_action_handler ( WIDGET ( state->modi[j] ), textbox_sidebar_modi_trigger_action, state ); } } } else if ( g_ascii_strncasecmp ( name, "textbox", 7 ) == 0 ) { textbox *t = textbox_create ( parent_widget, WIDGET_TYPE_TEXTBOX_TEXT, name, TB_AUTOHEIGHT | TB_WRAP, NORMAL, "", 0, 0 ); box_add ( (box *) parent_widget, WIDGET ( t ), TRUE ); } else { wid = (widget *) box_create ( parent_widget, name, ROFI_ORIENTATION_VERTICAL ); box_add ( (box *) parent_widget, WIDGET ( wid ), TRUE ); //g_error("The widget %s does not exists. Invalid layout.", name); } if ( wid ) { GList *list = rofi_theme_get_list ( wid, "children", defaults ); for ( const GList *iter = list; iter != NULL; iter = g_list_next ( iter ) ) { rofi_view_add_widget ( state, wid, (const char *) iter->data ); } g_list_free_full ( list, g_free ); } } RofiViewState *rofi_view_create ( Mode *sw, const char *input, MenuFlags menu_flags, void ( *finalize )( RofiViewState * ) ) { TICK (); RofiViewState *state = __rofi_view_state_create (); state->menu_flags = menu_flags; state->sw = sw; state->selected_line = UINT32_MAX; state->retv = MENU_CANCEL; state->distance = NULL; state->quit = FALSE; state->skip_absorb = FALSE; //We want to filter on the first run. state->refilter = TRUE; state->finalize = finalize; state->mouse_seen = FALSE; // Request the lines to show. state->num_lines = mode_get_num_entries ( sw ); TICK_N ( "Startup notification" ); // Get active monitor size. TICK_N ( "Get active monitor" ); state->main_window = box_create ( NULL, "window", ROFI_ORIENTATION_VERTICAL ); // Get children. GList *list = rofi_theme_get_list ( WIDGET ( state->main_window ), "children", "mainbox" ); for ( const GList *iter = list; iter != NULL; iter = g_list_next ( iter ) ) { rofi_view_add_widget ( state, WIDGET ( state->main_window ), (const char *) iter->data ); } g_list_free_full ( list, g_free ); if ( state->text && input ) { textbox_text ( state->text, input ); textbox_cursor_end ( state->text ); } state->overlay = textbox_create ( WIDGET ( state->main_window ), WIDGET_TYPE_TEXTBOX_TEXT, "overlay", TB_AUTOWIDTH | TB_AUTOHEIGHT, URGENT, "blaat", 0.5, 0 ); widget_disable ( WIDGET ( state->overlay ) ); // filtered list state->line_map = g_malloc0_n ( state->num_lines, sizeof ( unsigned int ) ); state->distance = (int *) g_malloc0_n ( state->num_lines, sizeof ( int ) ); rofi_view_calculate_window_width ( state ); // Need to resize otherwise calculated desired height is wrong. widget_resize ( WIDGET ( state->main_window ), state->width, 100 ); // Only needed when window is fixed size. if ( ( CacheState.flags & MENU_NORMAL_WINDOW ) == MENU_NORMAL_WINDOW ) { listview_set_fixed_num_lines ( state->list_view ); } state->height = rofi_view_calculate_height ( state ); // Move the window to the correct x,y position. rofi_view_calculate_window_position ( state ); rofi_view_window_update_size ( state ); state->quit = FALSE; rofi_view_refilter ( state ); rofi_view_update ( state, TRUE ); xcb_map_window ( xcb->connection, CacheState.main_window ); widget_queue_redraw ( WIDGET ( state->main_window ) ); xcb_flush ( xcb->connection ); if ( xcb->sncontext != NULL ) { sn_launchee_context_complete ( xcb->sncontext ); } return state; } int rofi_view_error_dialog ( const char *msg, int markup ) { RofiViewState *state = __rofi_view_state_create (); state->retv = MENU_CANCEL; state->menu_flags = MENU_ERROR_DIALOG; state->finalize = process_result; state->main_window = box_create ( NULL, "window", ROFI_ORIENTATION_VERTICAL ); box *box = box_create ( WIDGET ( state->main_window ), "message", ROFI_ORIENTATION_VERTICAL ); box_add ( state->main_window, WIDGET ( box ), TRUE ); state->text = textbox_create ( WIDGET ( box ), WIDGET_TYPE_TEXTBOX_TEXT, "textbox", ( TB_AUTOHEIGHT | TB_WRAP ) + ( ( markup ) ? TB_MARKUP : 0 ), NORMAL, ( msg != NULL ) ? msg : "", 0, 0 ); box_add ( box, WIDGET ( state->text ), TRUE ); // Make sure we enable fixed num lines when in normal window mode. if ( ( CacheState.flags & MENU_NORMAL_WINDOW ) == MENU_NORMAL_WINDOW ) { listview_set_fixed_num_lines ( state->list_view ); } rofi_view_calculate_window_width ( state ); // Need to resize otherwise calculated desired height is wrong. widget_resize ( WIDGET ( state->main_window ), state->width, 100 ); // resize window vertically to suit state->height = widget_get_desired_height ( WIDGET ( state->main_window ) ); // Calculte window position. rofi_view_calculate_window_position ( state ); // Move the window to the correct x,y position. rofi_view_window_update_size ( state ); // Display it. xcb_map_window ( xcb->connection, CacheState.main_window ); widget_queue_redraw ( WIDGET ( state->main_window ) ); if ( xcb->sncontext != NULL ) { sn_launchee_context_complete ( xcb->sncontext ); } // Set it as current window. rofi_view_set_active ( state ); return TRUE; } void rofi_view_hide ( void ) { if ( CacheState.main_window != XCB_WINDOW_NONE ) { xcb_unmap_window ( xcb->connection, CacheState.main_window ); display_early_cleanup (); } } void rofi_view_cleanup () { g_debug ( "Cleanup." ); if ( CacheState.idle_timeout > 0 ) { g_source_remove ( CacheState.idle_timeout ); CacheState.idle_timeout = 0; } if ( CacheState.repaint_source > 0 ) { g_source_remove ( CacheState.repaint_source ); CacheState.repaint_source = 0; } if ( CacheState.fake_bg ) { cairo_surface_destroy ( CacheState.fake_bg ); CacheState.fake_bg = NULL; } if ( CacheState.edit_draw ) { cairo_destroy ( CacheState.edit_draw ); CacheState.edit_draw = NULL; } if ( CacheState.edit_surf ) { cairo_surface_destroy ( CacheState.edit_surf ); CacheState.edit_surf = NULL; } if ( CacheState.main_window != XCB_WINDOW_NONE ) { g_debug ( "Unmapping and free'ing window" ); xcb_unmap_window ( xcb->connection, CacheState.main_window ); xcb_free_gc ( xcb->connection, CacheState.gc ); xcb_free_pixmap ( xcb->connection, CacheState.edit_pixmap ); xcb_destroy_window ( xcb->connection, CacheState.main_window ); CacheState.main_window = XCB_WINDOW_NONE; } if ( map != XCB_COLORMAP_NONE ) { xcb_free_colormap ( xcb->connection, map ); map = XCB_COLORMAP_NONE; } xcb_flush ( xcb->connection ); g_assert ( g_queue_is_empty ( &( CacheState.views ) ) ); } void rofi_view_workers_initialize ( void ) { TICK_N ( "Setup Threadpool, start" ); if ( config.threads == 0 ) { config.threads = 1; long procs = sysconf ( _SC_NPROCESSORS_CONF ); if ( procs > 0 ) { config.threads = MIN ( procs, 128l ); } } // Create thread pool GError *error = NULL; tpool = g_thread_pool_new ( rofi_view_call_thread, NULL, config.threads, FALSE, &error ); if ( error == NULL ) { // Idle threads should stick around for a max of 60 seconds. g_thread_pool_set_max_idle_time ( 60000 ); // We are allowed to have g_thread_pool_set_max_threads ( tpool, config.threads, &error ); } // If error occurred during setup of pool, tell user and exit. if ( error != NULL ) { g_warning ( "Failed to setup thread pool: '%s'", error->message ); g_error_free ( error ); exit ( EXIT_FAILURE ); } TICK_N ( "Setup Threadpool, done" ); } void rofi_view_workers_finalize ( void ) { if ( tpool ) { g_thread_pool_free ( tpool, TRUE, TRUE ); tpool = NULL; } } Mode * rofi_view_get_mode ( RofiViewState *state ) { return state->sw; } void rofi_view_set_overlay ( RofiViewState *state, const char *text ) { if ( state->overlay == NULL || state->list_view == NULL ) { return; } if ( text == NULL ) { widget_disable ( WIDGET ( state->overlay ) ); return; } widget_enable ( WIDGET ( state->overlay ) ); textbox_text ( state->overlay, text ); int x_offset = widget_get_width ( WIDGET ( state->list_view ) ); // Within padding of window. x_offset += widget_get_absolute_xpos ( WIDGET ( state->list_view ) ); x_offset -= widget_get_width ( WIDGET ( state->overlay ) ); // Within the border of widget. int top_offset = widget_get_absolute_ypos ( WIDGET ( state->list_view ) ); widget_move ( WIDGET ( state->overlay ), x_offset, top_offset ); // We want to queue a repaint. rofi_view_queue_redraw ( ); } void rofi_view_clear_input ( RofiViewState *state ) { if ( state->text ) { textbox_text ( state->text, "" ); rofi_view_set_selected_line ( state, 0 ); } } void rofi_view_switch_mode ( RofiViewState *state, Mode *mode ) { state->sw = mode; // Update prompt; if ( state->prompt ) { rofi_view_update_prompt ( state ); } if ( config.sidebar_mode && state->sidebar_bar ) { for ( unsigned int j = 0; j < state->num_modi; j++ ) { const Mode * mode = rofi_get_mode ( j ); textbox_font ( state->modi[j], ( mode == state->sw ) ? HIGHLIGHT : NORMAL ); } } rofi_view_restart ( state ); state->reload = TRUE; state->refilter = TRUE; rofi_view_refilter ( state ); rofi_view_update ( state, TRUE ); } xcb_window_t rofi_view_get_window ( void ) { return CacheState.main_window; } rofi-1.5.0/source/dialogs/0000775000175000017500000000000013234677335012416 500000000000000rofi-1.5.0/source/dialogs/run.c0000664000175000017500000003250613234677115013310 00000000000000/* * rofi * * MIT/X11 License * Copyright © 2013-2017 Qball Cow * * 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. * */ /** * \ingroup RUNMode * @{ */ /** The log domain of this dialog. */ #define G_LOG_DOMAIN "Dialogs.Run" #include #include #include #include #include #include #include #include #include #include #include #include "rofi.h" #include "settings.h" #include "helper.h" #include "history.h" #include "dialogs/run.h" #include "mode-private.h" #include "timings.h" /** * Name of the history file where previously chosen commands are stored. */ #define RUN_CACHE_FILE "rofi-3.runcache" /** * The internal data structure holding the private data of the Run Mode. */ typedef struct { /** list of available commands. */ char **cmd_list; /** Length of the #cmd_list. */ unsigned int cmd_list_length; } RunModePrivateData; /** * @param cmd The cmd to execute * @param run_in_term Indicate if command should be run in a terminal * * Execute command and add to history. */ static void exec_cmd ( const char *cmd, int run_in_term ) { GError *error = NULL; if ( !cmd || !cmd[0] ) { return; } gsize lf_cmd_size = 0; gchar *lf_cmd = g_locale_from_utf8 ( cmd, -1, NULL, &lf_cmd_size, &error ); if ( error != NULL ) { g_warning ( "Failed to convert command to locale encoding: %s", error->message ); g_error_free ( error ); return; } char *path = g_build_filename ( cache_dir, RUN_CACHE_FILE, NULL ); RofiHelperExecuteContext context = { .name = NULL }; // FIXME: assume startup notification support for terminals if ( helper_execute_command ( NULL, lf_cmd, run_in_term, run_in_term ? &context : NULL ) ) { /** * This happens in non-critical time (After launching app) * It is allowed to be a bit slower. */ history_set ( path, cmd ); } else { history_remove ( path, cmd ); } g_free ( path ); g_free ( lf_cmd ); } /** * @param cmd The command to remove from history * * Remove command from history. */ static void delete_entry ( const char *cmd ) { char *path = g_build_filename ( cache_dir, RUN_CACHE_FILE, NULL ); history_remove ( path, cmd ); g_free ( path ); } /** * @param a The First key to compare * @param b The second key to compare * @param data Unused. * * Function used for sorting. * * @returns returns less then, equal to and greater than zero is a is less than, is a match or greater than b. */ static int sort_func ( const void *a, const void *b, G_GNUC_UNUSED void *data ) { const char *astr = *( const char * const * ) a; const char *bstr = *( const char * const * ) b; if ( astr == NULL && bstr == NULL ) { return 0; } else if ( astr == NULL ) { return 1; } else if ( bstr == NULL ) { return -1; } return g_strcmp0 ( astr, bstr ); } /** * External spider to get list of executables. */ static char ** get_apps_external ( char **retv, unsigned int *length, unsigned int num_favorites ) { int fd = execute_generator ( config.run_list_command ); if ( fd >= 0 ) { FILE *inp = fdopen ( fd, "r" ); if ( inp ) { char *buffer = NULL; size_t buffer_length = 0; while ( getline ( &buffer, &buffer_length, inp ) > 0 ) { int found = 0; // Filter out line-end. if ( buffer[strlen ( buffer ) - 1] == '\n' ) { buffer[strlen ( buffer ) - 1] = '\0'; } // This is a nice little penalty, but doable? time will tell. // given num_favorites is max 25. for ( unsigned int j = 0; found == 0 && j < num_favorites; j++ ) { if ( strcasecmp ( buffer, retv[j] ) == 0 ) { found = 1; } } if ( found == 1 ) { continue; } // No duplicate, add it. retv = g_realloc ( retv, ( ( *length ) + 2 ) * sizeof ( char* ) ); retv[( *length )] = g_strdup ( buffer ); ( *length )++; } if ( buffer != NULL ) { free ( buffer ); } if ( fclose ( inp ) != 0 ) { g_warning ( "Failed to close stdout off executor script: '%s'", g_strerror ( errno ) ); } } } retv[( *length ) ] = NULL; return retv; } /** * Internal spider used to get list of executables. */ static char ** get_apps ( unsigned int *length ) { GError *error = NULL; char **retv = NULL; unsigned int num_favorites = 0; char *path; if ( g_getenv ( "PATH" ) == NULL ) { return NULL; } TICK_N ( "start" ); path = g_build_filename ( cache_dir, RUN_CACHE_FILE, NULL ); retv = history_get_list ( path, length ); g_free ( path ); // Keep track of how many where loaded as favorite. num_favorites = ( *length ); path = g_strdup ( g_getenv ( "PATH" ) ); gsize l = 0; gchar *homedir = g_locale_to_utf8 ( g_get_home_dir (), -1, NULL, &l, &error ); if ( error != NULL ) { g_debug ( "Failed to convert homedir to UTF-8: %s", error->message ); g_clear_error ( &error ); g_free ( homedir ); return NULL; } const char *const sep = ":"; char *strtok_savepointer = NULL; for ( const char *dirname = strtok_r ( path, sep, &strtok_savepointer ); dirname != NULL; dirname = strtok_r ( NULL, sep, &strtok_savepointer ) ) { char *fpath = rofi_expand_path ( dirname ); DIR *dir = opendir ( fpath ); g_debug ( "Checking path %s for executable.", fpath ); g_free ( fpath ); if ( dir != NULL ) { struct dirent *dent; gsize dirn_len = 0; gchar *dirn = g_locale_to_utf8 ( dirname, -1, NULL, &dirn_len, &error ); if ( error != NULL ) { g_debug ( "Failed to convert directory name to UTF-8: %s", error->message ); g_clear_error ( &error ); closedir ( dir ); continue; } gboolean is_homedir = g_str_has_prefix ( dirn, homedir ); g_free ( dirn ); while ( ( dent = readdir ( dir ) ) != NULL ) { if ( dent->d_type != DT_REG && dent->d_type != DT_LNK && dent->d_type != DT_UNKNOWN ) { continue; } // Skip dot files. if ( dent->d_name[0] == '.' ) { continue; } if ( is_homedir ) { gchar *fpath = g_build_filename ( dirname, dent->d_name, NULL ); gboolean b = g_file_test ( fpath, G_FILE_TEST_IS_EXECUTABLE ); g_free ( fpath ); if ( !b ) { continue; } } gsize name_len; gchar *name = g_filename_to_utf8 ( dent->d_name, -1, NULL, &name_len, &error ); if ( error != NULL ) { g_debug ( "Failed to convert filename to UTF-8: %s", error->message ); g_clear_error ( &error ); g_free ( name ); continue; } // This is a nice little penalty, but doable? time will tell. // given num_favorites is max 25. int found = 0; for ( unsigned int j = 0; found == 0 && j < num_favorites; j++ ) { if ( g_strcmp0 ( name, retv[j] ) == 0 ) { found = 1; } } if ( found == 1 ) { g_free ( name ); continue; } retv = g_realloc ( retv, ( ( *length ) + 2 ) * sizeof ( char* ) ); retv[( *length )] = name; retv[( *length ) + 1] = NULL; ( *length )++; } closedir ( dir ); } } g_free ( homedir ); // Get external apps. if ( config.run_list_command != NULL && config.run_list_command[0] != '\0' ) { retv = get_apps_external ( retv, length, num_favorites ); } // No sorting needed. if ( ( *length ) == 0 ) { return retv; } // TODO: check this is still fast enough. (takes 1ms on laptop.) if ( ( *length ) > num_favorites ) { g_qsort_with_data ( &retv[num_favorites], ( *length ) - num_favorites, sizeof ( char* ), sort_func, NULL ); } g_free ( path ); unsigned int removed = 0; for ( unsigned int index = num_favorites; index < ( ( *length ) - 1 ); index++ ) { if ( g_strcmp0 ( retv[index], retv[index + 1] ) == 0 ) { g_free ( retv[index] ); retv[index] = NULL; removed++; } } if ( ( *length ) > num_favorites ) { g_qsort_with_data ( &retv[num_favorites], ( *length ) - num_favorites, sizeof ( char* ), sort_func, NULL ); } // Reduce array length; ( *length ) -= removed; TICK_N ( "stop" ); return retv; } static int run_mode_init ( Mode *sw ) { if ( sw->private_data == NULL ) { RunModePrivateData *pd = g_malloc0 ( sizeof ( *pd ) ); sw->private_data = (void *) pd; pd->cmd_list = get_apps ( &( pd->cmd_list_length ) ); } return TRUE; } static void run_mode_destroy ( Mode *sw ) { RunModePrivateData *rmpd = (RunModePrivateData *) sw->private_data; if ( rmpd != NULL ) { g_strfreev ( rmpd->cmd_list ); g_free ( rmpd ); sw->private_data = NULL; } } static unsigned int run_mode_get_num_entries ( const Mode *sw ) { const RunModePrivateData *rmpd = (const RunModePrivateData *) sw->private_data; return rmpd->cmd_list_length; } static ModeMode run_mode_result ( Mode *sw, int mretv, char **input, unsigned int selected_line ) { RunModePrivateData *rmpd = (RunModePrivateData *) sw->private_data; ModeMode retv = MODE_EXIT; gboolean run_in_term = ( ( mretv & MENU_CUSTOM_ACTION ) == MENU_CUSTOM_ACTION ); if ( mretv & MENU_NEXT ) { retv = NEXT_DIALOG; } else if ( mretv & MENU_PREVIOUS ) { retv = PREVIOUS_DIALOG; } else if ( mretv & MENU_QUICK_SWITCH ) { retv = ( mretv & MENU_LOWER_MASK ); } else if ( ( mretv & MENU_OK ) && rmpd->cmd_list[selected_line] != NULL ) { exec_cmd ( rmpd->cmd_list[selected_line], run_in_term ); } else if ( ( mretv & MENU_CUSTOM_INPUT ) && *input != NULL && *input[0] != '\0' ) { exec_cmd ( *input, run_in_term ); } else if ( ( mretv & MENU_ENTRY_DELETE ) && rmpd->cmd_list[selected_line] ) { delete_entry ( rmpd->cmd_list[selected_line] ); // Clear the list. retv = RELOAD_DIALOG; run_mode_destroy ( sw ); run_mode_init ( sw ); } return retv; } static char *_get_display_value ( const Mode *sw, unsigned int selected_line, G_GNUC_UNUSED int *state, G_GNUC_UNUSED GList **list, int get_entry ) { const RunModePrivateData *rmpd = (const RunModePrivateData *) sw->private_data; return get_entry ? g_strdup ( rmpd->cmd_list[selected_line] ) : NULL; } static int run_token_match ( const Mode *sw, rofi_int_matcher **tokens, unsigned int index ) { const RunModePrivateData *rmpd = (const RunModePrivateData *) sw->private_data; return helper_token_match ( tokens, rmpd->cmd_list[index] ); } #include "mode-private.h" Mode run_mode = { .name = "run", .cfg_name_key = "display-run", ._init = run_mode_init, ._get_num_entries = run_mode_get_num_entries, ._result = run_mode_result, ._destroy = run_mode_destroy, ._token_match = run_token_match, ._get_display_value = _get_display_value, ._get_completion = NULL, ._preprocess_input = NULL, .private_data = NULL, .free = NULL }; /*@}*/ rofi-1.5.0/source/dialogs/window.c0000664000175000017500000010167313234677115014015 00000000000000/* * rofi * * MIT/X11 License * Copyright © 2013-2017 Qball Cow * * 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. * */ #define G_LOG_DOMAIN "Dialogs.Window" #include #ifdef WINDOW_MODE #include #include #include #include #include #include #include #include #include #include #include #include #include "xcb-internal.h" #include "xcb.h" #include "rofi.h" #include "settings.h" #include "helper.h" #include "widgets/textbox.h" #include "dialogs/window.h" #include "timings.h" #define WINLIST 32 #define CLIENTSTATE 10 #define CLIENTWINDOWTYPE 10 // Fields to match in window mode typedef struct { char *field_name; gboolean enabled; } WinModeField; typedef enum { WIN_MATCH_FIELD_TITLE, WIN_MATCH_FIELD_CLASS, WIN_MATCH_FIELD_ROLE, WIN_MATCH_FIELD_NAME, WIN_MATCH_FIELD_DESKTOP, WIN_MATCH_NUM_FIELDS, } WinModeMatchingFields; static WinModeField matching_window_fields[WIN_MATCH_NUM_FIELDS] = { { .field_name = "title", .enabled = TRUE, }, { .field_name = "class", .enabled = TRUE, }, { .field_name = "role", .enabled = TRUE, }, { .field_name = "name", .enabled = TRUE, }, { .field_name = "desktop", .enabled = TRUE, } }; static gboolean window_matching_fields_parsed = FALSE; // a manageable window typedef struct { xcb_window_t window; xcb_get_window_attributes_reply_t xattr; char *title; char *class; char *name; char *role; int states; xcb_atom_t state[CLIENTSTATE]; int window_types; xcb_atom_t window_type[CLIENTWINDOWTYPE]; int active; int demands; long hint_flags; uint32_t wmdesktop; char *wmdesktopstr; cairo_surface_t *icon; gboolean icon_checked; } client; // window lists typedef struct { xcb_window_t *array; client **data; int len; } winlist; typedef struct { unsigned int id; winlist *ids; // Current window. unsigned int index; char *cache; unsigned int wmdn_len; unsigned int clf_len; unsigned int name_len; unsigned int title_len; unsigned int role_len; GRegex *window_regex; } ModeModePrivateData; winlist *cache_client = NULL; /** * Create a window list, pre-seeded with WINLIST entries. * * @returns A new window list. */ static winlist* winlist_new () { winlist *l = g_malloc ( sizeof ( winlist ) ); l->len = 0; l->array = g_malloc_n ( WINLIST + 1, sizeof ( xcb_window_t ) ); l->data = g_malloc_n ( WINLIST + 1, sizeof ( client* ) ); return l; } /** * @param l The winlist. * @param w The window to add. * @param d Data pointer. * * Add one entry. If Full, extend with WINLIST entries. * * @returns 0 if failed, 1 is successful. */ static int winlist_append ( winlist *l, xcb_window_t w, client *d ) { if ( l->len > 0 && !( l->len % WINLIST ) ) { l->array = g_realloc ( l->array, sizeof ( xcb_window_t ) * ( l->len + WINLIST + 1 ) ); l->data = g_realloc ( l->data, sizeof ( client* ) * ( l->len + WINLIST + 1 ) ); } // Make clang-check happy. // TODO: make clang-check clear this should never be 0. if ( l->data == NULL || l->array == NULL ) { return 0; } l->data[l->len] = d; l->array[l->len++] = w; return l->len - 1; } static void winlist_empty ( winlist *l ) { while ( l->len > 0 ) { client *c = l->data[--l->len]; if ( c != NULL ) { if ( c->icon ) { cairo_surface_destroy ( c->icon ); } g_free ( c->title ); g_free ( c->class ); g_free ( c->name ); g_free ( c->role ); g_free ( c->wmdesktopstr ); g_free ( c ); } } } /** * @param l The winlist entry * * Free the winlist. */ static void winlist_free ( winlist *l ) { if ( l != NULL ) { winlist_empty ( l ); g_free ( l->array ); g_free ( l->data ); g_free ( l ); } } /** * @param l The winlist. * @param w The window to find. * * Find the window in the list, and return the array entry. * * @returns -1 if failed, index is successful. */ static int winlist_find ( winlist *l, xcb_window_t w ) { // iterate backwards. Theory is: windows most often accessed will be // nearer the end. Testing with kcachegrind seems to support this... int i; for ( i = ( l->len - 1 ); i >= 0; i-- ) { if ( l->array[i] == w ) { return i; } } return -1; } /** * Create empty X11 cache for windows and windows attributes. */ static void x11_cache_create ( void ) { if ( cache_client == NULL ) { cache_client = winlist_new (); } } /** * Free the cache. */ static void x11_cache_free ( void ) { winlist_free ( cache_client ); cache_client = NULL; } /** * @param d Display connection to X server * @param w window * * Get window attributes. * This functions uses caching. * * @returns a XWindowAttributes */ static xcb_get_window_attributes_reply_t * window_get_attributes ( xcb_window_t w ) { xcb_get_window_attributes_cookie_t c = xcb_get_window_attributes ( xcb->connection, w ); xcb_get_window_attributes_reply_t *r = xcb_get_window_attributes_reply ( xcb->connection, c, NULL ); if ( r ) { return r; } return NULL; } // _NET_WM_STATE_* static int client_has_state ( client *c, xcb_atom_t state ) { for ( int i = 0; i < c->states; i++ ) { if ( c->state[i] == state ) { return 1; } } return 0; } static int client_has_window_type ( client *c, xcb_atom_t type ) { for ( int i = 0; i < c->window_types; i++ ) { if ( c->window_type[i] == type ) { return 1; } } return 0; } static client* window_client ( ModeModePrivateData *pd, xcb_window_t win ) { if ( win == XCB_WINDOW_NONE ) { return NULL; } int idx = winlist_find ( cache_client, win ); if ( idx >= 0 ) { return cache_client->data[idx]; } // if this fails, we're up that creek xcb_get_window_attributes_reply_t *attr = window_get_attributes ( win ); if ( !attr ) { return NULL; } client *c = g_malloc0 ( sizeof ( client ) ); c->window = win; // copy xattr so we don't have to care when stuff is freed memmove ( &c->xattr, attr, sizeof ( xcb_get_window_attributes_reply_t ) ); xcb_get_property_cookie_t cky = xcb_ewmh_get_wm_state ( &xcb->ewmh, win ); xcb_ewmh_get_atoms_reply_t states; if ( xcb_ewmh_get_wm_state_reply ( &xcb->ewmh, cky, &states, NULL ) ) { c->states = MIN ( CLIENTSTATE, states.atoms_len ); memcpy ( c->state, states.atoms, MIN ( CLIENTSTATE, states.atoms_len ) * sizeof ( xcb_atom_t ) ); xcb_ewmh_get_atoms_reply_wipe ( &states ); } cky = xcb_ewmh_get_wm_window_type ( &xcb->ewmh, win ); if ( xcb_ewmh_get_wm_window_type_reply ( &xcb->ewmh, cky, &states, NULL ) ) { c->window_types = MIN ( CLIENTWINDOWTYPE, states.atoms_len ); memcpy ( c->window_type, states.atoms, MIN ( CLIENTWINDOWTYPE, states.atoms_len ) * sizeof ( xcb_atom_t ) ); xcb_ewmh_get_atoms_reply_wipe ( &states ); } c->title = window_get_text_prop ( c->window, xcb->ewmh._NET_WM_NAME ); if ( c->title == NULL ) { c->title = window_get_text_prop ( c->window, XCB_ATOM_WM_NAME ); } pd->title_len = MAX ( c->title ? g_utf8_strlen ( c->title, -1 ) : 0, pd->title_len ); c->role = window_get_text_prop ( c->window, netatoms[WM_WINDOW_ROLE] ); pd->role_len = MAX ( c->role ? g_utf8_strlen ( c->role, -1 ) : 0, pd->role_len ); cky = xcb_icccm_get_wm_class ( xcb->connection, c->window ); xcb_icccm_get_wm_class_reply_t wcr; if ( xcb_icccm_get_wm_class_reply ( xcb->connection, cky, &wcr, NULL ) ) { c->class = rofi_latin_to_utf8_strdup ( wcr.class_name, -1 ); c->name = rofi_latin_to_utf8_strdup ( wcr.instance_name, -1 ); pd->name_len = MAX ( c->name ? g_utf8_strlen ( c->name, -1 ) : 0, pd->name_len ); xcb_icccm_get_wm_class_reply_wipe ( &wcr ); } xcb_get_property_cookie_t cc = xcb_icccm_get_wm_hints ( xcb->connection, c->window ); xcb_icccm_wm_hints_t r; if ( xcb_icccm_get_wm_hints_reply ( xcb->connection, cc, &r, NULL ) ) { c->hint_flags = r.flags; } winlist_append ( cache_client, c->window, c ); g_free ( attr ); return c; } static int window_match ( const Mode *sw, rofi_int_matcher **tokens, unsigned int index ) { ModeModePrivateData *rmpd = (ModeModePrivateData *) mode_get_private_data ( sw ); int match = 1; const winlist *ids = ( winlist * ) rmpd->ids; // Want to pull directly out of cache, X calls are not thread safe. int idx = winlist_find ( cache_client, ids->array[index] ); g_assert ( idx >= 0 ); client *c = cache_client->data[idx]; if ( tokens ) { for ( int j = 0; match && tokens != NULL && tokens[j] != NULL; j++ ) { int test = 0; // Dirty hack. Normally helper_token_match does _all_ the matching, // Now we want it to match only one item at the time. // If hack not in place it would not match queries spanning multiple fields. // e.g. when searching 'title element' and 'class element' rofi_int_matcher *ftokens[2] = { tokens[j], NULL }; if ( c->title != NULL && c->title[0] != '\0' && matching_window_fields[WIN_MATCH_FIELD_TITLE].enabled ) { test = helper_token_match ( ftokens, c->title ); } if ( test == tokens[j]->invert && c->class != NULL && c->class[0] != '\0' && matching_window_fields[WIN_MATCH_FIELD_CLASS].enabled ) { test = helper_token_match ( ftokens, c->class ); } if ( test == tokens[j]->invert && c->role != NULL && c->role[0] != '\0' && matching_window_fields[WIN_MATCH_FIELD_ROLE].enabled ) { test = helper_token_match ( ftokens, c->role ); } if ( test == tokens[j]->invert && c->name != NULL && c->name[0] != '\0' && matching_window_fields[WIN_MATCH_FIELD_NAME].enabled ) { test = helper_token_match ( ftokens, c->name ); } if ( test == tokens[j]->invert && c->wmdesktopstr != NULL && c->wmdesktopstr[0] != '\0' && matching_window_fields[WIN_MATCH_FIELD_DESKTOP].enabled ) { test = helper_token_match ( ftokens, c->wmdesktopstr ); } if ( test == 0 ) { match = 0; } } } return match; } static void window_mode_parse_fields () { window_matching_fields_parsed = TRUE; char *savept = NULL; // Make a copy, as strtok will modify it. char *switcher_str = g_strdup ( config.window_match_fields ); const char * const sep = ",#"; // Split token on ','. This modifies switcher_str. for ( unsigned int i = 0; i < WIN_MATCH_NUM_FIELDS; i++ ) { matching_window_fields[i].enabled = FALSE; } for ( char *token = strtok_r ( switcher_str, sep, &savept ); token != NULL; token = strtok_r ( NULL, sep, &savept ) ) { if ( strcmp ( token, "all" ) == 0 ) { for ( unsigned int i = 0; i < WIN_MATCH_NUM_FIELDS; i++ ) { matching_window_fields[i].enabled = TRUE; } break; } else { gboolean matched = FALSE; for ( unsigned int i = 0; i < WIN_MATCH_NUM_FIELDS; i++ ) { const char * field_name = matching_window_fields[i].field_name; if ( strcmp ( token, field_name ) == 0 ) { matching_window_fields[i].enabled = TRUE; matched = TRUE; } } if ( !matched ) { g_warning ( "Invalid window field name :%s", token ); } } } // Free string that was modified by strtok_r g_free ( switcher_str ); } static unsigned int window_mode_get_num_entries ( const Mode *sw ) { const ModeModePrivateData *pd = (const ModeModePrivateData *) mode_get_private_data ( sw ); return pd->ids ? pd->ids->len : 0; } /** * Small helper function to find the right entry in the ewmh reply. * Is there a call for this? */ static const char * _window_name_list_entry ( const char *str, uint32_t length, int entry ) { uint32_t offset = 0; int index = 0; while ( index < entry && offset < length ) { if ( str[offset] == 0 ) { index++; } offset++; } return &str[offset]; } static void _window_mode_load_data ( Mode *sw, unsigned int cd ) { ModeModePrivateData *pd = (ModeModePrivateData *) mode_get_private_data ( sw ); // find window list int nwins = 0; xcb_window_t wins[100]; xcb_window_t curr_win_id; // Create cache x11_cache_create (); xcb_get_property_cookie_t c = xcb_ewmh_get_active_window ( &( xcb->ewmh ), xcb->screen_nbr ); if ( !xcb_ewmh_get_active_window_reply ( &xcb->ewmh, c, &curr_win_id, NULL ) ) { curr_win_id = 0; } // Get the current desktop. unsigned int current_desktop = 0; c = xcb_ewmh_get_current_desktop ( &xcb->ewmh, xcb->screen_nbr ); if ( !xcb_ewmh_get_current_desktop_reply ( &xcb->ewmh, c, ¤t_desktop, NULL ) ) { current_desktop = 0; } c = xcb_ewmh_get_client_list_stacking ( &xcb->ewmh, 0 ); xcb_ewmh_get_windows_reply_t clients; if ( xcb_ewmh_get_client_list_stacking_reply ( &xcb->ewmh, c, &clients, NULL ) ) { nwins = MIN ( 100, clients.windows_len ); memcpy ( wins, clients.windows, nwins * sizeof ( xcb_window_t ) ); xcb_ewmh_get_windows_reply_wipe ( &clients ); } else { c = xcb_ewmh_get_client_list ( &xcb->ewmh, xcb->screen_nbr ); if ( xcb_ewmh_get_client_list_reply ( &xcb->ewmh, c, &clients, NULL ) ) { nwins = MIN ( 100, clients.windows_len ); memcpy ( wins, clients.windows, nwins * sizeof ( xcb_window_t ) ); xcb_ewmh_get_windows_reply_wipe ( &clients ); } } if ( nwins > 0 ) { int i; // windows we actually display. May be slightly different to _NET_CLIENT_LIST_STACKING // if we happen to have a window destroyed while we're working... pd->ids = winlist_new (); xcb_get_property_cookie_t c = xcb_ewmh_get_desktop_names ( &xcb->ewmh, xcb->screen_nbr ); xcb_ewmh_get_utf8_strings_reply_t names; int has_names = FALSE; if ( xcb_ewmh_get_desktop_names_reply ( &xcb->ewmh, c, &names, NULL ) ) { has_names = TRUE; } // calc widths of fields for ( i = nwins - 1; i > -1; i-- ) { client *c = window_client ( pd, wins[i] ); if ( ( c != NULL ) && !c->xattr.override_redirect && !client_has_window_type ( c, xcb->ewmh._NET_WM_WINDOW_TYPE_DOCK ) && !client_has_window_type ( c, xcb->ewmh._NET_WM_WINDOW_TYPE_DESKTOP ) && !client_has_state ( c, xcb->ewmh._NET_WM_STATE_SKIP_PAGER ) && !client_has_state ( c, xcb->ewmh._NET_WM_STATE_SKIP_TASKBAR ) ) { pd->clf_len = MAX ( pd->clf_len, ( c->class != NULL ) ? ( g_utf8_strlen ( c->class, -1 ) ) : 0 ); if ( client_has_state ( c, xcb->ewmh._NET_WM_STATE_DEMANDS_ATTENTION ) ) { c->demands = TRUE; } if ( ( c->hint_flags & XCB_ICCCM_WM_HINT_X_URGENCY ) != 0 ) { c->demands = TRUE; } if ( c->window == curr_win_id ) { c->active = TRUE; } // find client's desktop. xcb_get_property_cookie_t cookie; xcb_get_property_reply_t *r; c->wmdesktop = 0xFFFFFFFF; cookie = xcb_get_property ( xcb->connection, 0, c->window, xcb->ewmh._NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 0, 1 ); r = xcb_get_property_reply ( xcb->connection, cookie, NULL ); if ( r ) { if ( r->type == XCB_ATOM_CARDINAL ) { c->wmdesktop = *( (uint32_t *) xcb_get_property_value ( r ) ); } free ( r ); } if ( c->wmdesktop != 0xFFFFFFFF ) { if ( has_names ) { if ( ( current_window_manager & WM_PANGO_WORKSPACE_NAMES ) == WM_PANGO_WORKSPACE_NAMES ) { char *output = NULL; if ( pango_parse_markup ( _window_name_list_entry ( names.strings, names.strings_len, c->wmdesktop ), -1, 0, NULL, &output, NULL, NULL ) ) { c->wmdesktopstr = output; } else { c->wmdesktopstr = g_strdup ( "Invalid name" ); } } else { c->wmdesktopstr = g_strdup ( _window_name_list_entry ( names.strings, names.strings_len, c->wmdesktop ) ); } } else { c->wmdesktopstr = g_strdup_printf ( "%u", (uint32_t) c->wmdesktop ); } } else { c->wmdesktopstr = g_strdup ( "" ); } pd->wmdn_len = MAX ( pd->wmdn_len, g_utf8_strlen ( c->wmdesktopstr, -1 ) ); if ( cd && c->wmdesktop != current_desktop ) { continue; } winlist_append ( pd->ids, c->window, NULL ); } } if ( has_names ) { xcb_ewmh_get_utf8_strings_reply_wipe ( &names ); } } } static int window_mode_init ( Mode *sw ) { if ( mode_get_private_data ( sw ) == NULL ) { ModeModePrivateData *pd = g_malloc0 ( sizeof ( *pd ) ); pd->window_regex = g_regex_new ( "{[-\\w]+(:-?[0-9]+)?}", 0, 0, NULL ); mode_set_private_data ( sw, (void *) pd ); _window_mode_load_data ( sw, FALSE ); if ( !window_matching_fields_parsed ) { window_mode_parse_fields (); } } return TRUE; } static int window_mode_init_cd ( Mode *sw ) { if ( mode_get_private_data ( sw ) == NULL ) { ModeModePrivateData *pd = g_malloc0 ( sizeof ( *pd ) ); pd->window_regex = g_regex_new ( "{[-\\w]+(:-?[0-9]+)?}", 0, 0, NULL ); mode_set_private_data ( sw, (void *) pd ); _window_mode_load_data ( sw, TRUE ); if ( !window_matching_fields_parsed ) { window_mode_parse_fields (); } } return TRUE; } static inline int act_on_window ( xcb_window_t window ) { int retv = TRUE; char **args = NULL; int argc = 0; char window_regex[100]; /* We are probably safe here */ g_snprintf ( window_regex, sizeof window_regex, "%d", window ); helper_parse_setup ( config.window_command, &args, &argc, "{window}", window_regex, (char *) 0 ); GError *error = NULL; g_spawn_async ( NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error ); if ( error != NULL ) { char *msg = g_strdup_printf ( "Failed to execute action for window: '%s'\nError: '%s'", window_regex, error->message ); rofi_view_error_dialog ( msg, FALSE ); g_free ( msg ); // print error. g_error_free ( error ); retv = FALSE; } // Free the args list. g_strfreev ( args ); return retv; } static ModeMode window_mode_result ( Mode *sw, int mretv, G_GNUC_UNUSED char **input, unsigned int selected_line ) { ModeModePrivateData *rmpd = (ModeModePrivateData *) mode_get_private_data ( sw ); ModeMode retv = MODE_EXIT; if ( mretv & MENU_NEXT ) { retv = NEXT_DIALOG; } else if ( mretv & MENU_PREVIOUS ) { retv = PREVIOUS_DIALOG; } else if ( ( mretv & MENU_QUICK_SWITCH ) == MENU_QUICK_SWITCH ) { retv = ( mretv & MENU_LOWER_MASK ); } else if ( ( mretv & ( MENU_OK ) ) ) { if ( mretv & MENU_CUSTOM_ACTION ) { act_on_window ( rmpd->ids->array[selected_line] ); } else { rofi_view_hide (); if ( ( current_window_manager & WM_DO_NOT_CHANGE_CURRENT_DESKTOP ) == 0 ) { // Get the desktop of the client to switch to uint32_t wmdesktop = 0; xcb_get_property_cookie_t cookie; xcb_get_property_reply_t *r; // Get the current desktop. unsigned int current_desktop = 0; xcb_get_property_cookie_t c = xcb_ewmh_get_current_desktop ( &xcb->ewmh, xcb->screen_nbr ); if ( !xcb_ewmh_get_current_desktop_reply ( &xcb->ewmh, c, ¤t_desktop, NULL ) ) { current_desktop = 0; } cookie = xcb_get_property ( xcb->connection, 0, rmpd->ids->array[selected_line], xcb->ewmh._NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 0, 1 ); r = xcb_get_property_reply ( xcb->connection, cookie, NULL ); if ( r && r->type == XCB_ATOM_CARDINAL ) { wmdesktop = *( (uint32_t *) xcb_get_property_value ( r ) ); } if ( r && r->type != XCB_ATOM_CARDINAL ) { // Assume the client is on all desktops. wmdesktop = current_desktop; } free ( r ); // If we have to switch the desktop, do if ( wmdesktop != current_desktop ) { xcb_ewmh_request_change_current_desktop ( &xcb->ewmh, xcb->screen_nbr, wmdesktop, XCB_CURRENT_TIME ); } } // Activate the window xcb_ewmh_request_change_active_window ( &xcb->ewmh, xcb->screen_nbr, rmpd->ids->array[selected_line], XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER, XCB_CURRENT_TIME, rofi_view_get_window () ); xcb_flush ( xcb->connection ); } } else if ( ( mretv & ( MENU_ENTRY_DELETE ) ) == MENU_ENTRY_DELETE ) { xcb_ewmh_request_close_window ( &( xcb->ewmh ), xcb->screen_nbr, rmpd->ids->array[selected_line], XCB_CURRENT_TIME, XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER ); xcb_flush ( xcb->connection ); } return retv; } static void window_mode_destroy ( Mode *sw ) { ModeModePrivateData *rmpd = (ModeModePrivateData *) mode_get_private_data ( sw ); if ( rmpd != NULL ) { winlist_free ( rmpd->ids ); x11_cache_free (); g_free ( rmpd->cache ); g_regex_unref ( rmpd->window_regex ); g_free ( rmpd ); mode_set_private_data ( sw, NULL ); } } struct arg { const ModeModePrivateData *pd; client *c; }; static void helper_eval_add_str ( GString *str, const char *input, int l, int max_len ) { // g_utf8 does not work with NULL string. const char *input_nn = input ? input : ""; // Both l and max_len are in characters, not bytes. int nc = g_utf8_strlen ( input_nn, -1 ); int spaces = 0; if ( l == 0 ) { spaces = MAX ( 0, max_len - nc ); g_string_append ( str, input_nn ); } else { if ( nc > l ) { int bl = g_utf8_offset_to_pointer ( input_nn, l ) - input_nn; g_string_append_len ( str, input_nn, bl ); } else { spaces = l - nc; g_string_append ( str, input_nn ); } } while ( spaces-- ) { g_string_append_c ( str, ' ' ); } } static gboolean helper_eval_cb ( const GMatchInfo *info, GString *str, gpointer data ) { struct arg *d = (struct arg *) data; gchar *match; // Get the match match = g_match_info_fetch ( info, 0 ); if ( match != NULL ) { int l = 0; if ( match[2] == ':' ) { l = (int) g_ascii_strtoll ( &match[3], NULL, 10 ); if ( l < 0 && config.menu_width < 0 ) { l = -config.menu_width + l; } if ( l < 0 ) { l = 0; } } if ( match[1] == 'w' ) { helper_eval_add_str ( str, d->c->wmdesktopstr, l, d->pd->wmdn_len ); } else if ( match[1] == 'c' ) { helper_eval_add_str ( str, d->c->class, l, d->pd->clf_len ); } else if ( match[1] == 't' ) { helper_eval_add_str ( str, d->c->title, l, d->pd->title_len ); } else if ( match[1] == 'n' ) { helper_eval_add_str ( str, d->c->name, l, d->pd->name_len ); } else if ( match[1] == 'r' ) { helper_eval_add_str ( str, d->c->role, l, d->pd->role_len ); } g_free ( match ); } return FALSE; } static char * _generate_display_string ( const ModeModePrivateData *pd, client *c ) { struct arg d = { pd, c }; char *res = g_regex_replace_eval ( pd->window_regex, config.window_format, -1, 0, 0, helper_eval_cb, &d, NULL ); return g_strchomp ( res ); } static char *_get_display_value ( const Mode *sw, unsigned int selected_line, int *state, G_GNUC_UNUSED GList **list, int get_entry ) { ModeModePrivateData *rmpd = mode_get_private_data ( sw ); client *c = window_client ( rmpd, rmpd->ids->array[selected_line] ); if ( c == NULL ) { return get_entry ? g_strdup ( "Window has fanished" ) : NULL; } if ( c->demands ) { *state |= URGENT; } if ( c->active ) { *state |= ACTIVE; } return get_entry ? _generate_display_string ( rmpd, c ) : NULL; } /** * Icon code borrowed from https://github.com/olejorgenb/extract-window-icon */ static cairo_user_data_key_t data_key; /** Create a surface object from this image data. * \param width The width of the image. * \param height The height of the image * \param data The image's data in ARGB format, will be copied by this function. */ static cairo_surface_t * draw_surface_from_data ( int width, int height, uint32_t *data ) { unsigned long int len = width * height; unsigned long int i; uint32_t *buffer = g_new0 ( uint32_t, len ); cairo_surface_t *surface; /* Cairo wants premultiplied alpha, meh :( */ for ( i = 0; i < len; i++ ) { uint8_t a = ( data[i] >> 24 ) & 0xff; double alpha = a / 255.0; uint8_t r = ( ( data[i] >> 16 ) & 0xff ) * alpha; uint8_t g = ( ( data[i] >> 8 ) & 0xff ) * alpha; uint8_t b = ( ( data[i] >> 0 ) & 0xff ) * alpha; buffer[i] = ( a << 24 ) | ( r << 16 ) | ( g << 8 ) | b; } surface = cairo_image_surface_create_for_data ( (unsigned char *) buffer, CAIRO_FORMAT_ARGB32, width, height, width * 4 ); /* This makes sure that buffer will be freed */ cairo_surface_set_user_data ( surface, &data_key, buffer, g_free ); return surface; } static cairo_surface_t * ewmh_window_icon_from_reply ( xcb_get_property_reply_t *r, uint32_t preferred_size ) { uint32_t *data, *end, *found_data = 0; uint32_t found_size = 0; if ( !r || r->type != XCB_ATOM_CARDINAL || r->format != 32 || r->length < 2 ) { return 0; } data = (uint32_t *) xcb_get_property_value ( r ); if ( !data ) { return 0; } end = data + r->length; /* Goes over the icon data and picks the icon that best matches the size preference. * In case the size match is not exact, picks the closest bigger size if present, * closest smaller size otherwise. */ while ( data + 1 < end ) { /* check whether the data size specified by width and height fits into the array we got */ uint64_t data_size = (uint64_t) data[0] * data[1]; if ( data_size > (uint64_t) ( end - data - 2 ) ) { break; } /* use the greater of the two dimensions to match against the preferred size */ uint32_t size = MAX ( data[0], data[1] ); /* pick the icon if it's a better match than the one we already have */ gboolean found_icon_too_small = found_size < preferred_size; gboolean found_icon_too_large = found_size > preferred_size; gboolean icon_empty = data[0] == 0 || data[1] == 0; gboolean better_because_bigger = found_icon_too_small && size > found_size; gboolean better_because_smaller = found_icon_too_large && size >= preferred_size && size < found_size; if ( !icon_empty && ( better_because_bigger || better_because_smaller || found_size == 0 ) ) { found_data = data; found_size = size; } data += data_size + 2; } if ( !found_data ) { return 0; } return draw_surface_from_data ( found_data[0], found_data[1], found_data + 2 ); } /** Get NET_WM_ICON. */ static cairo_surface_t * get_net_wm_icon ( xcb_window_t xid, uint32_t preferred_size ) { xcb_get_property_cookie_t cookie = xcb_get_property_unchecked ( xcb->connection, FALSE, xid, xcb->ewmh._NET_WM_ICON, XCB_ATOM_CARDINAL, 0, UINT32_MAX ); xcb_get_property_reply_t *r = xcb_get_property_reply ( xcb->connection, cookie, NULL ); cairo_surface_t *surface = ewmh_window_icon_from_reply ( r, preferred_size ); free ( r ); return surface; } static cairo_surface_t *_get_icon ( const Mode *sw, unsigned int selected_line, int size ) { ModeModePrivateData *rmpd = mode_get_private_data ( sw ); client *c = window_client ( rmpd, rmpd->ids->array[selected_line] ); if ( c->icon_checked == FALSE ) { c->icon = get_net_wm_icon ( rmpd->ids->array[selected_line], size ); c->icon_checked = TRUE; } return c->icon; } #include "mode-private.h" Mode window_mode = { .name = "window", .cfg_name_key = "display-window", ._init = window_mode_init, ._get_num_entries = window_mode_get_num_entries, ._result = window_mode_result, ._destroy = window_mode_destroy, ._token_match = window_match, ._get_display_value = _get_display_value, ._get_icon = _get_icon, ._get_completion = NULL, ._preprocess_input = NULL, .private_data = NULL, .free = NULL }; Mode window_mode_cd = { .name = "windowcd", .cfg_name_key = "display-windowcd", ._init = window_mode_init_cd, ._get_num_entries = window_mode_get_num_entries, ._result = window_mode_result, ._destroy = window_mode_destroy, ._token_match = window_match, ._get_display_value = _get_display_value, ._get_icon = _get_icon, ._get_completion = NULL, ._preprocess_input = NULL, .private_data = NULL, .free = NULL }; #endif // WINDOW_MODE rofi-1.5.0/source/dialogs/drun.c0000664000175000017500000007740113234677115013457 00000000000000/* * rofi * * MIT/X11 License * Copyright © 2013-2017 Qball Cow * * 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. * */ #define G_LOG_DOMAIN "Dialogs.DRun" #include #ifdef ENABLE_DRUN #include #include #include #include #include #include #include #include #include #include #include #include #include "rofi.h" #include "settings.h" #include "helper.h" #include "timings.h" #include "widgets/textbox.h" #include "history.h" #include "dialogs/drun.h" #include "nkutils-xdg-theme.h" #include "xcb.h" #define DRUN_CACHE_FILE "rofi3.druncache" #define DRUN_GROUP_NAME "Desktop Entry" /** * Store extra information about the entry. * Currently the executable and if it should run in terminal. */ typedef struct { /* Root */ char *root; /* Path to desktop file */ char *path; /* Application id (.desktop filename) */ char *app_id; /* Desktop id */ char *desktop_id; /* Icon stuff */ char *icon_name; /* Icon size is used to indicate what size is requested by the gui. * secondary it indicates if the request for a lookup has been issued (0 not issued ) */ int icon_size; /* Surface holding the icon. */ cairo_surface_t *icon; /* Executable */ char *exec; /* Name of the Entry */ char *name; /* Generic Name */ char *generic_name; /* Categories */ char **categories; /* Comments */ char *comment; GKeyFile *key_file; gint sort_index; } DRunModeEntry; typedef struct { const char *entry_field_name; gboolean enabled; } DRunEntryField; typedef enum { DRUN_MATCH_FIELD_NAME, DRUN_MATCH_FIELD_GENERIC, DRUN_MATCH_FIELD_EXEC, DRUN_MATCH_FIELD_CATEGORIES, DRUN_MATCH_FIELD_COMMENT, DRUN_MATCH_NUM_FIELDS, } DRunMatchingFields; static DRunEntryField matching_entry_fields[DRUN_MATCH_NUM_FIELDS] = { { .entry_field_name = "name", .enabled = TRUE, }, { .entry_field_name = "generic", .enabled = TRUE, }, { .entry_field_name = "exec", .enabled = TRUE, }, { .entry_field_name = "categories", .enabled = TRUE, }, { .entry_field_name = "comment", .enabled = FALSE, } }; typedef struct { NkXdgThemeContext *xdg_context; DRunModeEntry *entry_list; unsigned int cmd_list_length; unsigned int cmd_list_length_actual; // List of disabled entries. GHashTable *disabled_entries; unsigned int disabled_entries_length; GThreadPool *pool; unsigned int expected_line_height; DRunModeEntry quit_entry; // Theme const gchar *icon_theme; // DE gchar **current_desktop_list; } DRunModePrivateData; struct RegexEvalArg { DRunModeEntry *e; gboolean success; }; static gboolean drun_helper_eval_cb ( const GMatchInfo *info, GString *res, gpointer data ) { // TODO quoting is not right? Find description not very clear, need to check. struct RegexEvalArg *e = (struct RegexEvalArg *) data; gchar *match; // Get the match match = g_match_info_fetch ( info, 0 ); if ( match != NULL ) { switch ( match[1] ) { // Unsupported case 'f': case 'F': case 'u': case 'U': case 'i': // Deprecated case 'd': case 'D': case 'n': case 'N': case 'v': case 'm': break; case 'k': if ( e->e->path ) { char *esc = g_shell_quote ( e->e->path ); g_string_append ( res, esc ); g_free ( esc ); } break; case 'c': if ( e->e->name ) { char *esc = g_shell_quote ( e->e->name ); g_string_append ( res, esc ); g_free ( esc ); } break; // Invalid, this entry should not be processed -> throw error. default: e->success = FALSE; g_free ( match ); return TRUE; } g_free ( match ); } // Continue replacement. return FALSE; } static void exec_cmd_entry ( DRunModeEntry *e ) { GError *error = NULL; GRegex *reg = g_regex_new ( "%[a-zA-Z]", 0, 0, &error ); if ( error != NULL ) { g_warning ( "Internal error, failed to create regex: %s.", error->message ); g_error_free ( error ); return; } struct RegexEvalArg earg = { .e = e, .success = TRUE }; char *str = g_regex_replace_eval ( reg, e->exec, -1, 0, 0, drun_helper_eval_cb, &earg, &error ); if ( error != NULL ) { g_warning ( "Internal error, failed replace field codes: %s.", error->message ); g_error_free ( error ); return; } g_regex_unref ( reg ); if ( earg.success == FALSE ) { g_warning ( "Invalid field code in Exec line: %s.", e->exec );; return; } if ( str == NULL ) { g_warning ( "Nothing to execute after processing: %s.", e->exec );; return; } const gchar *fp = g_strstrip ( str ); gchar *exec_path = g_key_file_get_string ( e->key_file, DRUN_GROUP_NAME, "Path", NULL ); if ( exec_path != NULL && strlen ( exec_path ) == 0 ) { // If it is empty, ignore this property. (#529) g_free ( exec_path ); exec_path = NULL; } RofiHelperExecuteContext context = { .name = e->name, .icon = e->icon_name, .app_id = e->app_id, }; gboolean sn = g_key_file_get_boolean ( e->key_file, DRUN_GROUP_NAME, "StartupNotify", NULL ); gchar *wmclass = NULL; if ( sn && g_key_file_has_key ( e->key_file, DRUN_GROUP_NAME, "StartupWMClass", NULL ) ) { context.wmclass = wmclass = g_key_file_get_string ( e->key_file, DRUN_GROUP_NAME, "StartupWMClass", NULL ); } // Returns false if not found, if key not found, we don't want run in terminal. gboolean terminal = g_key_file_get_boolean ( e->key_file, DRUN_GROUP_NAME, "Terminal", NULL ); if ( helper_execute_command ( exec_path, fp, terminal, sn ? &context : NULL ) ) { char *path = g_build_filename ( cache_dir, DRUN_CACHE_FILE, NULL ); // Store it based on the unique identifiers (desktop_id). history_set ( path, e->desktop_id ); g_free ( path ); } g_free ( wmclass ); g_free ( exec_path ); g_free ( str ); } /** * This function absorbs/freeś path, so this is no longer available afterwards. */ static gboolean read_desktop_file ( DRunModePrivateData *pd, const char *root, const char *path, const gchar *basename ) { // Create ID on stack. // We know strlen (path ) > strlen(root)+1 const ssize_t id_len = strlen ( path ) - strlen ( root ); char id[id_len]; g_strlcpy ( id, &( path[strlen ( root ) + 1] ), id_len ); for ( int index = 0; index < id_len; index++ ) { if ( id[index] == '/' ) { id[index] = '-'; } } // Check if item is on disabled list. if ( g_hash_table_contains ( pd->disabled_entries, id ) ) { g_debug ( "[%s] [%s] Skipping, was previously seen.", id, path ); return TRUE; } GKeyFile *kf = g_key_file_new (); GError *error = NULL; gboolean res = g_key_file_load_from_file ( kf, path, 0, &error ); // If error, skip to next entry if ( !res ) { g_debug ( "[%s] [%s] Failed to parse desktop file because: %s.", id, path, error->message ); g_error_free ( error ); g_key_file_free ( kf ); return FALSE; } // Skip non Application entries. gchar *key = g_key_file_get_string ( kf, DRUN_GROUP_NAME, "Type", NULL ); if ( key == NULL ) { // No type? ignore. g_debug ( "[%s] [%s] Invalid desktop file: No type indicated", id, path ); g_key_file_free ( kf ); return FALSE; } if ( g_strcmp0 ( key, "Application" ) ) { g_debug ( "[%s] [%s] Skipping desktop file: Not of type application (%s)", id, path, key ); g_free ( key ); g_key_file_free ( kf ); return FALSE; } g_free ( key ); // Name key is required. if ( !g_key_file_has_key ( kf, DRUN_GROUP_NAME, "Name", NULL ) ) { g_debug ( "[%s] [%s] Invalid desktop file: no 'Name' key present.", id, path ); g_key_file_free ( kf ); return FALSE; } // Skip hidden entries. if ( g_key_file_get_boolean ( kf, DRUN_GROUP_NAME, "Hidden", NULL ) ) { g_debug ( "[%s] [%s] Adding desktop file to disabled list: 'Hidden' key is true", id, path ); g_key_file_free ( kf ); g_hash_table_add ( pd->disabled_entries, g_strdup ( id ) ); return FALSE; } if ( pd->current_desktop_list ) { gboolean show = TRUE; // If the DE is set, check the keys. if ( g_key_file_has_key ( kf, DRUN_GROUP_NAME, "OnlyShowIn", NULL ) ) { gsize llength = 0; show = FALSE; gchar **list = g_key_file_get_string_list ( kf, DRUN_GROUP_NAME, "OnlyShowIn", &llength, NULL ); if ( list ) { for ( gsize lcd = 0; !show && pd->current_desktop_list[lcd]; lcd++ ) { for ( gsize lle = 0; !show && lle < llength; lle++ ) { show = ( g_strcmp0 ( pd->current_desktop_list[lcd], list[lle] ) == 0 ); } } g_strfreev ( list ); } } if ( show && g_key_file_has_key ( kf, DRUN_GROUP_NAME, "NotShowIn", NULL ) ) { gsize llength = 0; gchar **list = g_key_file_get_string_list ( kf, DRUN_GROUP_NAME, "NotShowIn", &llength, NULL ); if ( list ) { for ( gsize lcd = 0; show && pd->current_desktop_list[lcd]; lcd++ ) { for ( gsize lle = 0; show && lle < llength; lle++ ) { show = !( g_strcmp0 ( pd->current_desktop_list[lcd], list[lle] ) == 0 ); } } g_strfreev ( list ); } } if ( !show ) { g_debug ( "[%s] [%s] Adding desktop file to disabled list: 'OnlyShowIn'/'NotShowIn' keys don't match current desktop", id, path ); g_key_file_free ( kf ); g_hash_table_add ( pd->disabled_entries, g_strdup ( id ) ); return FALSE; } } // Skip entries that have NoDisplay set. if ( g_key_file_get_boolean ( kf, DRUN_GROUP_NAME, "NoDisplay", NULL ) ) { g_debug ( "[%s] [%s] Adding desktop file to disabled list: 'NoDisplay' key is true", id, path ); g_key_file_free ( kf ); g_hash_table_add ( pd->disabled_entries, g_strdup ( id ) ); return FALSE; } // We need Exec, don't support DBusActivatable if ( !g_key_file_has_key ( kf, DRUN_GROUP_NAME, "Exec", NULL ) ) { g_debug ( "[%s] [%s] Unsupported desktop file: no 'Exec' key present.", id, path ); g_key_file_free ( kf ); return FALSE; } if ( g_key_file_has_key ( kf, DRUN_GROUP_NAME, "TryExec", NULL ) ) { char *te = g_key_file_get_string ( kf, DRUN_GROUP_NAME, "TryExec", NULL ); if ( !g_path_is_absolute ( te ) ) { char *fp = g_find_program_in_path ( te ); if ( fp == NULL ) { g_free ( te ); g_key_file_free ( kf ); return FALSE; } g_free ( fp ); } else { if ( g_file_test ( te, G_FILE_TEST_IS_EXECUTABLE ) == FALSE ) { g_free ( te ); g_key_file_free ( kf ); return FALSE; } } g_free ( te ); } size_t nl = ( ( pd->cmd_list_length ) + 1 ); if ( nl >= pd->cmd_list_length_actual ) { pd->cmd_list_length_actual += 256; pd->entry_list = g_realloc ( pd->entry_list, pd->cmd_list_length_actual * sizeof ( *( pd->entry_list ) ) ); } // Make sure order is preserved, this will break when cmd_list_length is bigger then INT_MAX. // This is not likely to happen. if ( G_UNLIKELY ( pd->cmd_list_length > INT_MAX ) ) { // Default to smallest value. pd->entry_list[pd->cmd_list_length].sort_index = INT_MIN; } else { pd->entry_list[pd->cmd_list_length].sort_index = -pd->cmd_list_length; } pd->entry_list[pd->cmd_list_length].icon_size = 0; pd->entry_list[pd->cmd_list_length].root = g_strdup ( root ); pd->entry_list[pd->cmd_list_length].path = g_strdup ( path ); pd->entry_list[pd->cmd_list_length].desktop_id = g_strdup ( id ); pd->entry_list[pd->cmd_list_length].app_id = g_strndup ( basename, strlen ( basename ) - strlen ( ".desktop" ) ); gchar *n = g_key_file_get_locale_string ( kf, DRUN_GROUP_NAME, "Name", NULL, NULL ); pd->entry_list[pd->cmd_list_length].name = n; gchar *gn = g_key_file_get_locale_string ( kf, DRUN_GROUP_NAME, "GenericName", NULL, NULL ); pd->entry_list[pd->cmd_list_length].generic_name = gn; if ( matching_entry_fields[DRUN_MATCH_FIELD_CATEGORIES].enabled ) { pd->entry_list[pd->cmd_list_length].categories = g_key_file_get_locale_string_list ( kf, DRUN_GROUP_NAME, "Categories", NULL, NULL, NULL ); } else { pd->entry_list[pd->cmd_list_length].categories = NULL; } pd->entry_list[pd->cmd_list_length].exec = g_key_file_get_string ( kf, DRUN_GROUP_NAME, "Exec", NULL ); if ( matching_entry_fields[DRUN_MATCH_FIELD_COMMENT].enabled ) { pd->entry_list[pd->cmd_list_length].comment = g_key_file_get_locale_string ( kf, DRUN_GROUP_NAME, "Comment", NULL, NULL ); } else { pd->entry_list[pd->cmd_list_length].comment = NULL; } if ( config.show_icons ) { pd->entry_list[pd->cmd_list_length].icon_name = g_key_file_get_locale_string ( kf, DRUN_GROUP_NAME, "Icon", NULL, NULL ); } else{ pd->entry_list[pd->cmd_list_length].icon_name = NULL; } pd->entry_list[pd->cmd_list_length].icon = NULL; // Keep keyfile around. pd->entry_list[pd->cmd_list_length].key_file = kf; // We don't want to parse items with this id anymore. g_hash_table_add ( pd->disabled_entries, g_strdup ( id ) ); g_debug ( "[%s] Using file %s.", id, path ); ( pd->cmd_list_length )++; return TRUE; } /** * Internal spider used to get list of executables. */ static void walk_dir ( DRunModePrivateData *pd, const char *root, const char *dirname ) { DIR *dir; g_debug ( "Checking directory %s for desktop files.", dirname ); dir = opendir ( dirname ); if ( dir == NULL ) { return; } struct dirent *file; gchar *filename = NULL; struct stat st; while ( ( file = readdir ( dir ) ) != NULL ) { if ( file->d_name[0] == '.' ) { continue; } switch ( file->d_type ) { case DT_LNK: case DT_REG: case DT_DIR: case DT_UNKNOWN: filename = g_build_filename ( dirname, file->d_name, NULL ); break; default: continue; } // On a link, or if FS does not support providing this information // Fallback to stat method. if ( file->d_type == DT_LNK || file->d_type == DT_UNKNOWN ) { file->d_type = DT_UNKNOWN; if ( stat ( filename, &st ) == 0 ) { if ( S_ISDIR ( st.st_mode ) ) { file->d_type = DT_DIR; } else if ( S_ISREG ( st.st_mode ) ) { file->d_type = DT_REG; } } } switch ( file->d_type ) { case DT_REG: // Skip files not ending on .desktop. if ( g_str_has_suffix ( file->d_name, ".desktop" ) ) { read_desktop_file ( pd, root, filename, file->d_name ); } break; case DT_DIR: walk_dir ( pd, root, filename ); break; default: break; } g_free ( filename ); } closedir ( dir ); } /** * @param entry The command entry to remove from history * * Remove command from history. */ static void delete_entry_history ( const DRunModeEntry *entry ) { char *path = g_build_filename ( cache_dir, DRUN_CACHE_FILE, NULL ); history_remove ( path, entry->desktop_id ); g_free ( path ); } static void get_apps_history ( DRunModePrivateData *pd ) { TICK_N ( "Start drun history" ); unsigned int length = 0; gchar *path = g_build_filename ( cache_dir, DRUN_CACHE_FILE, NULL ); gchar **retv = history_get_list ( path, &length ); for ( unsigned int index = 0; index < length; index++ ) { for ( size_t i = 0; i < pd->cmd_list_length; i++ ) { if ( g_strcmp0 ( pd->entry_list[i].desktop_id, retv[index] ) == 0 ) { unsigned int sort_index = length - index; if ( G_LIKELY ( sort_index < INT_MAX ) ) { pd->entry_list[i].sort_index = sort_index; } else { // This won't sort right anymore, but never gonna hit it anyway. pd->entry_list[i].sort_index = INT_MAX; } } } } g_strfreev ( retv ); g_free ( path ); TICK_N ( "Stop drun history" ); } static gint drun_int_sort_list ( gconstpointer a, gconstpointer b, G_GNUC_UNUSED gpointer user_data ) { DRunModeEntry *da = (DRunModeEntry *) a; DRunModeEntry *db = (DRunModeEntry *) b; return db->sort_index - da->sort_index; } static void get_apps ( DRunModePrivateData *pd ) { TICK_N ( "Get Desktop apps (start)" ); gchar *dir; // First read the user directory. dir = g_build_filename ( g_get_user_data_dir (), "applications", NULL ); walk_dir ( pd, dir, dir ); g_free ( dir ); TICK_N ( "Get Desktop apps (user dir)" ); // Then read thee system data dirs. const gchar * const * sys = g_get_system_data_dirs (); for ( const gchar * const *iter = sys; *iter != NULL; ++iter ) { gboolean unique = TRUE; // Stupid duplicate detection, better then walking dir. for ( const gchar *const *iterd = sys; iterd != iter; ++iterd ) { if ( g_strcmp0 ( *iter, *iterd ) == 0 ) { unique = FALSE; } } // Check, we seem to be getting empty string... if ( unique && ( **iter ) != '\0' ) { dir = g_build_filename ( *iter, "applications", NULL ); walk_dir ( pd, dir, dir ); g_free ( dir ); } } TICK_N ( "Get Desktop apps (system dirs)" ); get_apps_history ( pd ); g_qsort_with_data ( pd->entry_list, pd->cmd_list_length, sizeof ( DRunModeEntry ), drun_int_sort_list, NULL ); TICK_N ( "Sorting done." ); } static void drun_icon_fetch ( gpointer data, gpointer user_data ) { g_debug ( "Starting up icon fetching thread." ); // as long as dr->icon is updated atomicly.. (is a pointer write atomic?) // this should be fine running in another thread. DRunModePrivateData *pd = (DRunModePrivateData *) user_data; DRunModeEntry *dr = (DRunModeEntry *) data; const gchar *themes[2] = { config.drun_icon_theme, NULL }; if ( dr->icon_name == NULL ) { return; } const gchar *icon_path; gchar *icon_path_ = NULL; if ( g_path_is_absolute ( dr->icon_name ) ) { icon_path = dr->icon_name; } else { icon_path = icon_path_ = nk_xdg_theme_get_icon ( pd->xdg_context, themes, NULL, dr->icon_name, dr->icon_size, 1, TRUE ); if ( icon_path_ == NULL ) { g_debug ( "Failed to get Icon %s(%d): n/a", dr->icon_name, dr->icon_size ); return; } else{ g_debug ( "Found Icon %s(%d): %s", dr->icon_name, dr->icon_size, icon_path ); } } cairo_surface_t *icon_surf = NULL; if ( g_str_has_suffix ( icon_path, ".png" ) ) { icon_surf = cairo_image_surface_create_from_png ( icon_path ); } else if ( g_str_has_suffix ( icon_path, ".svg" ) ) { icon_surf = cairo_image_surface_create_from_svg ( icon_path, dr->icon_size ); } else { g_debug ( "Icon type not yet supported: %s", icon_path ); } if ( icon_surf ) { // Check if surface is valid. if ( cairo_surface_status ( icon_surf ) != CAIRO_STATUS_SUCCESS ) { g_debug ( "Icon failed to open: %s(%d): %s", dr->icon_name, dr->icon_size, icon_path ); cairo_surface_destroy ( icon_surf ); icon_surf = NULL; } dr->icon = icon_surf; } g_free ( icon_path_ ); rofi_view_reload (); } static void drun_mode_parse_entry_fields () { char *savept = NULL; // Make a copy, as strtok will modify it. char *switcher_str = g_strdup ( config.drun_match_fields ); const char * const sep = ",#"; // Split token on ','. This modifies switcher_str. for ( unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++ ) { matching_entry_fields[i].enabled = FALSE; } for ( char *token = strtok_r ( switcher_str, sep, &savept ); token != NULL; token = strtok_r ( NULL, sep, &savept ) ) { if ( strcmp ( token, "all" ) == 0 ) { for ( unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++ ) { matching_entry_fields[i].enabled = TRUE; } break; } else { gboolean matched = FALSE; for ( unsigned int i = 0; i < DRUN_MATCH_NUM_FIELDS; i++ ) { const char * entry_name = matching_entry_fields[i].entry_field_name; if ( g_ascii_strcasecmp ( token, entry_name ) == 0 ) { matching_entry_fields[i].enabled = TRUE; matched = TRUE; } } if ( !matched ) { g_warning ( "Invalid entry name :%s", token ); } } } // Free string that was modified by strtok_r g_free ( switcher_str ); } static int drun_mode_init ( Mode *sw ) { if ( mode_get_private_data ( sw ) != NULL ) { return TRUE; } static const gchar * const drun_icon_fallback_themes[] = { "Adwaita", "gnome", NULL }; const gchar *themes[2] = { config.drun_icon_theme, NULL }; DRunModePrivateData *pd = g_malloc0 ( sizeof ( *pd ) ); pd->disabled_entries = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, NULL ); mode_set_private_data ( sw, (void *) pd ); // current destkop const char *current_desktop = g_getenv ( "XDG_CURRENT_DESKTOP" ); pd->current_desktop_list = current_desktop ? g_strsplit ( current_desktop, ":", 0 ) : NULL; // Theme pd->xdg_context = nk_xdg_theme_context_new ( drun_icon_fallback_themes, NULL ); nk_xdg_theme_preload_themes_icon ( pd->xdg_context, themes ); drun_mode_parse_entry_fields (); get_apps ( pd ); return TRUE; } static void drun_entry_clear ( DRunModeEntry *e ) { g_free ( e->root ); g_free ( e->path ); g_free ( e->app_id ); g_free ( e->desktop_id ); if ( e->icon != NULL ) { cairo_surface_destroy ( e->icon ); } g_free ( e->icon_name ); g_free ( e->exec ); g_free ( e->name ); g_free ( e->generic_name ); g_free ( e->comment ); g_strfreev ( e->categories ); g_key_file_free ( e->key_file ); } static ModeMode drun_mode_result ( Mode *sw, int mretv, char **input, unsigned int selected_line ) { DRunModePrivateData *rmpd = (DRunModePrivateData *) mode_get_private_data ( sw ); ModeMode retv = MODE_EXIT; gboolean run_in_term = ( ( mretv & MENU_CUSTOM_ACTION ) == MENU_CUSTOM_ACTION ); if ( mretv & MENU_NEXT ) { retv = NEXT_DIALOG; } else if ( mretv & MENU_PREVIOUS ) { retv = PREVIOUS_DIALOG; } else if ( mretv & MENU_QUICK_SWITCH ) { retv = ( mretv & MENU_LOWER_MASK ); } else if ( ( mretv & MENU_OK ) ) { exec_cmd_entry ( &( rmpd->entry_list[selected_line] ) ); } else if ( ( mretv & MENU_CUSTOM_INPUT ) && *input != NULL && *input[0] != '\0' ) { RofiHelperExecuteContext context = { .name = NULL }; // FIXME: We assume startup notification in terminals, not in others helper_execute_command ( NULL, *input, run_in_term, run_in_term ? &context : NULL ); } else if ( ( mretv & MENU_ENTRY_DELETE ) && selected_line < rmpd->cmd_list_length ) { // Possitive sort index means it is in history. if ( rmpd->entry_list[selected_line].sort_index >= 0 ) { if ( rmpd->pool ) { g_thread_pool_free ( rmpd->pool, TRUE, TRUE ); rmpd->pool = NULL; } delete_entry_history ( &( rmpd->entry_list[selected_line] ) ); drun_entry_clear ( &( rmpd->entry_list[selected_line] ) ); memmove ( &( rmpd->entry_list[selected_line] ), &rmpd->entry_list[selected_line + 1], sizeof ( DRunModeEntry ) * ( rmpd->cmd_list_length - selected_line - 1 ) ); rmpd->cmd_list_length--; } retv = RELOAD_DIALOG; } return retv; } static void drun_mode_destroy ( Mode *sw ) { DRunModePrivateData *rmpd = (DRunModePrivateData *) mode_get_private_data ( sw ); if ( rmpd != NULL ) { if ( rmpd->pool ) { g_thread_pool_free ( rmpd->pool, TRUE, TRUE ); rmpd->pool = NULL; } for ( size_t i = 0; i < rmpd->cmd_list_length; i++ ) { drun_entry_clear ( &( rmpd->entry_list[i] ) ); } g_hash_table_destroy ( rmpd->disabled_entries ); g_free ( rmpd->entry_list ); nk_xdg_theme_context_free ( rmpd->xdg_context ); g_strfreev ( rmpd->current_desktop_list ); g_free ( rmpd ); mode_set_private_data ( sw, NULL ); } } static char *_get_display_value ( const Mode *sw, unsigned int selected_line, int *state, G_GNUC_UNUSED GList **list, int get_entry ) { DRunModePrivateData *pd = (DRunModePrivateData *) mode_get_private_data ( sw ); *state |= MARKUP; if ( !get_entry ) { return NULL; } if ( pd->entry_list == NULL ) { // Should never get here. return g_strdup ( "Failed" ); } /* Free temp storage. */ DRunModeEntry *dr = &( pd->entry_list[selected_line] ); if ( dr->generic_name == NULL ) { return g_markup_printf_escaped ( "%s", dr->name ); } else { return g_markup_printf_escaped ( "%s (%s)", dr->name, dr->generic_name ); } } static cairo_surface_t *_get_icon ( const Mode *sw, unsigned int selected_line, int height ) { DRunModePrivateData *pd = (DRunModePrivateData *) mode_get_private_data ( sw ); g_return_val_if_fail ( pd->entry_list != NULL, NULL ); DRunModeEntry *dr = &( pd->entry_list[selected_line] ); if ( pd->pool == NULL ) { /* TODO: 4 threads good? */ pd->pool = g_thread_pool_new ( drun_icon_fetch, pd, 4, FALSE, NULL ); } if ( dr->icon_size == 0 ) { dr->icon_size = height; //g_async_queue_push ( pd->icon_fetch_queue, dr ); g_thread_pool_push ( pd->pool, dr, NULL ); } return dr->icon; } static char *drun_get_completion ( const Mode *sw, unsigned int index ) { DRunModePrivateData *pd = (DRunModePrivateData *) mode_get_private_data ( sw ); /* Free temp storage. */ DRunModeEntry *dr = &( pd->entry_list[index] ); if ( dr->generic_name == NULL ) { return g_strdup ( dr->name ); } else { return g_strdup_printf ( "%s", dr->name ); } } static int drun_token_match ( const Mode *data, rofi_int_matcher **tokens, unsigned int index ) { DRunModePrivateData *rmpd = (DRunModePrivateData *) mode_get_private_data ( data ); int match = 1; if ( tokens ) { for ( int j = 0; match && tokens != NULL && tokens[j] != NULL; j++ ) { int test = 0; rofi_int_matcher *ftokens[2] = { tokens[j], NULL }; // Match name if ( matching_entry_fields[DRUN_MATCH_FIELD_NAME].enabled ) { if ( rmpd->entry_list[index].name ) { test = helper_token_match ( ftokens, rmpd->entry_list[index].name ); } } if ( matching_entry_fields[DRUN_MATCH_FIELD_GENERIC].enabled ) { // Match generic name if ( test == tokens[j]->invert && rmpd->entry_list[index].generic_name ) { test = helper_token_match ( ftokens, rmpd->entry_list[index].generic_name ); } } if ( matching_entry_fields[DRUN_MATCH_FIELD_EXEC].enabled ) { // Match executable name. if ( test == tokens[j]->invert ) { test = helper_token_match ( ftokens, rmpd->entry_list[index].exec ); } } if ( matching_entry_fields[DRUN_MATCH_FIELD_CATEGORIES].enabled ) { // Match against category. if ( test == tokens[j]->invert ) { gchar **list = rmpd->entry_list[index].categories; for ( int iter = 0; test == tokens[j]->invert && list && list[iter]; iter++ ) { test = helper_token_match ( ftokens, list[iter] ); } } } if ( matching_entry_fields[DRUN_MATCH_FIELD_COMMENT].enabled ) { // Match executable name. if ( test == tokens[j]->invert && rmpd->entry_list[index].comment ) { test = helper_token_match ( ftokens, rmpd->entry_list[index].comment ); } } if ( test == 0 ) { match = 0; } } } return match; } static unsigned int drun_mode_get_num_entries ( const Mode *sw ) { const DRunModePrivateData *pd = (const DRunModePrivateData *) mode_get_private_data ( sw ); return pd->cmd_list_length; } #include "mode-private.h" Mode drun_mode = { .name = "drun", .cfg_name_key = "display-drun", ._init = drun_mode_init, ._get_num_entries = drun_mode_get_num_entries, ._result = drun_mode_result, ._destroy = drun_mode_destroy, ._token_match = drun_token_match, ._get_completion = drun_get_completion, ._get_display_value = _get_display_value, ._get_icon = _get_icon, ._preprocess_input = NULL, .private_data = NULL, .free = NULL }; #endif // ENABLE_DRUN rofi-1.5.0/source/dialogs/help-keys.c0000664000175000017500000001041613234677115014401 00000000000000/* * rofi * * MIT/X11 License * Copyright © 2013-2017 Qball Cow * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include "rofi.h" #include "settings.h" #include "helper.h" #include "xrmoptions.h" #include "dialogs/help-keys.h" #include "widgets/textbox.h" typedef struct { char **messages; unsigned int messages_length; } KeysHelpModePrivateData; static void get_apps ( KeysHelpModePrivateData *pd ) { pd->messages = config_parser_return_display_help ( &( pd->messages_length ) ); } static int help_keys_mode_init ( Mode *sw ) { if ( mode_get_private_data ( sw ) == NULL ) { KeysHelpModePrivateData *pd = g_malloc0 ( sizeof ( *pd ) ); mode_set_private_data ( sw, (void *) pd ); get_apps ( pd ); } return TRUE; } static ModeMode help_keys_mode_result ( G_GNUC_UNUSED Mode *sw, int mretv, G_GNUC_UNUSED char **input, G_GNUC_UNUSED unsigned int selected_line ) { ModeMode retv = MODE_EXIT; if ( mretv & MENU_NEXT ) { retv = NEXT_DIALOG; } else if ( mretv & MENU_PREVIOUS ) { retv = PREVIOUS_DIALOG; } else if ( mretv & MENU_QUICK_SWITCH ) { retv = ( mretv & MENU_LOWER_MASK ); } return retv; } static void help_keys_mode_destroy ( Mode *sw ) { KeysHelpModePrivateData *rmpd = (KeysHelpModePrivateData *) mode_get_private_data ( sw ); if ( rmpd != NULL ) { g_strfreev ( rmpd->messages ); g_free ( rmpd ); mode_set_private_data ( sw, NULL ); } } static char *_get_display_value ( const Mode *sw, unsigned int selected_line, int *state, G_GNUC_UNUSED GList **list, int get_entry ) { KeysHelpModePrivateData *pd = (KeysHelpModePrivateData *) mode_get_private_data ( sw ); *state |= MARKUP; if ( !get_entry ) { return NULL; } return g_strdup ( pd->messages[selected_line] ); } static int help_keys_token_match ( const Mode *data, rofi_int_matcher **tokens, unsigned int index ) { KeysHelpModePrivateData *rmpd = (KeysHelpModePrivateData *) mode_get_private_data ( data ); return helper_token_match ( tokens, rmpd->messages[index] ); } static unsigned int help_keys_mode_get_num_entries ( const Mode *sw ) { const KeysHelpModePrivateData *pd = (const KeysHelpModePrivateData *) mode_get_private_data ( sw ); return pd->messages_length; } #include "mode-private.h" Mode help_keys_mode = { .name = "keys", .cfg_name_key = "display-keys", ._init = help_keys_mode_init, ._get_num_entries = help_keys_mode_get_num_entries, ._result = help_keys_mode_result, ._destroy = help_keys_mode_destroy, ._token_match = help_keys_token_match, ._get_completion = NULL, ._get_display_value = _get_display_value, .private_data = NULL, .free = NULL }; rofi-1.5.0/source/dialogs/script.c0000664000175000017500000002461513234677115014012 00000000000000/* * rofi * * MIT/X11 License * Copyright © 2013-2017 Qball Cow * * 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. * */ #define G_LOG_DOMAIN "Dialogs.Script" #include #include #include #include #include #include #include #include #include #include "rofi.h" #include "dialogs/script.h" #include "helper.h" #include "widgets/textbox.h" #include "mode-private.h" typedef struct { /** ID of the current script. */ unsigned int id; /** List of visible items. */ char **cmd_list; /** length list of visible items. */ unsigned int cmd_list_length; /** Urgent list */ struct rofi_range_pair * urgent_list; unsigned int num_urgent_list; /** Active list */ struct rofi_range_pair * active_list; unsigned int num_active_list; /** Configuration settings. */ char *message; char *prompt; gboolean do_markup; } ScriptModePrivateData; static void parse_header_entry ( Mode *sw, char *line, ssize_t length ) { ScriptModePrivateData *pd = (ScriptModePrivateData *) sw->private_data; ssize_t length_key = 0;//strlen ( line ); while ( length_key <= length && line[length_key] != '\x1f' ) { length_key++; } if ( length_key < length ) { line[length_key] = '\0'; char *value = line + length_key + 1; if ( strcasecmp ( line, "message" ) == 0 ) { g_free ( pd->message ); pd->message = g_strdup ( value ); } else if ( strcasecmp ( line, "prompt" ) == 0 ) { g_free ( pd->prompt ); pd->prompt = g_strdup ( value ); sw->display_name = pd->prompt; } else if ( strcasecmp ( line, "markup-rows" ) == 0 ) { pd->do_markup = ( strcasecmp ( value, "true" ) == 0 ); } else if ( strcasecmp ( line, "urgent" ) == 0 ) { parse_ranges ( value, &( pd->urgent_list ), &( pd->num_urgent_list ) ); } else if ( strcasecmp ( line, "active" ) == 0 ) { parse_ranges ( value, &( pd->active_list ), &( pd->num_active_list ) ); } } } static char **get_script_output ( Mode *sw, char *command, char *arg, unsigned int *length ) { int fd = -1; GError *error = NULL; char **retv = NULL; char **argv = NULL; int argc = 0; *length = 0; if ( g_shell_parse_argv ( command, &argc, &argv, &error ) ) { argv = g_realloc ( argv, ( argc + 2 ) * sizeof ( char* ) ); argv[argc] = g_strdup ( arg ); argv[argc + 1] = NULL; g_spawn_async_with_pipes ( NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL, &fd, NULL, &error ); } if ( error != NULL ) { char *msg = g_strdup_printf ( "Failed to execute: '%s'\nError: '%s'", command, error->message ); rofi_view_error_dialog ( msg, FALSE ); g_free ( msg ); // print error. g_error_free ( error ); fd = -1; } if ( fd >= 0 ) { FILE *inp = fdopen ( fd, "r" ); if ( inp ) { char *buffer = NULL; size_t buffer_length = 0; ssize_t read_length = 0; size_t actual_size = 0; while ( ( read_length = getline ( &buffer, &buffer_length, inp ) ) > 0 ) { // Filter out line-end. if ( buffer[read_length - 1] == '\n' ) { buffer[read_length - 1] = '\0'; } if ( buffer[0] == '\0' ) { parse_header_entry ( sw, &buffer[1], read_length - 1 ); } else { if ( actual_size < ( ( *length ) + 2 ) ) { actual_size += 256; retv = g_realloc ( retv, ( actual_size ) * sizeof ( char* ) ); } retv[( *length )] = g_strdup ( buffer ); retv[( *length ) + 1] = NULL; ( *length )++; } } if ( buffer ) { free ( buffer ); } if ( fclose ( inp ) != 0 ) { g_warning ( "Failed to close stdout off executor script: '%s'", g_strerror ( errno ) ); } } } return retv; } static char **execute_executor ( Mode *sw, char *result, unsigned int *length ) { char **retv = get_script_output ( sw, sw->ed, result, length ); return retv; } static void script_switcher_free ( Mode *sw ) { if ( sw == NULL ) { return; } g_free ( sw->name ); g_free ( sw->ed ); g_free ( sw ); } static int script_mode_init ( Mode *sw ) { if ( sw->private_data == NULL ) { ScriptModePrivateData *pd = g_malloc0 ( sizeof ( *pd ) ); sw->private_data = (void *) pd; pd->cmd_list = get_script_output ( sw, (char *) sw->ed, NULL, &( pd->cmd_list_length ) ); } return TRUE; } static unsigned int script_mode_get_num_entries ( const Mode *sw ) { const ScriptModePrivateData *rmpd = (const ScriptModePrivateData *) sw->private_data; return rmpd->cmd_list_length; } static ModeMode script_mode_result ( Mode *sw, int mretv, char **input, unsigned int selected_line ) { ScriptModePrivateData *rmpd = (ScriptModePrivateData *) sw->private_data; ModeMode retv = MODE_EXIT; char **new_list = NULL; unsigned int new_length = 0; if ( ( mretv & MENU_NEXT ) ) { retv = NEXT_DIALOG; } else if ( ( mretv & MENU_PREVIOUS ) ) { retv = PREVIOUS_DIALOG; } else if ( ( mretv & MENU_QUICK_SWITCH ) ) { retv = ( mretv & MENU_LOWER_MASK ); } else if ( ( mretv & MENU_OK ) && rmpd->cmd_list[selected_line] != NULL ) { new_list = execute_executor ( sw, rmpd->cmd_list[selected_line], &new_length ); } else if ( ( mretv & MENU_CUSTOM_INPUT ) && *input != NULL && *input[0] != '\0' ) { new_list = execute_executor ( sw, *input, &new_length ); } // If a new list was generated, use that an loop around. if ( new_list != NULL ) { g_strfreev ( rmpd->cmd_list ); rmpd->cmd_list = new_list; rmpd->cmd_list_length = new_length; retv = RESET_DIALOG; } return retv; } static void script_mode_destroy ( Mode *sw ) { ScriptModePrivateData *rmpd = (ScriptModePrivateData *) sw->private_data; if ( rmpd != NULL ) { g_strfreev ( rmpd->cmd_list ); g_free ( rmpd->message ); g_free ( rmpd->prompt ); g_free ( rmpd->urgent_list ); g_free ( rmpd->active_list ); g_free ( rmpd ); sw->private_data = NULL; } } static char *_get_display_value ( const Mode *sw, unsigned int selected_line, G_GNUC_UNUSED int *state, G_GNUC_UNUSED GList **list, int get_entry ) { ScriptModePrivateData *pd = sw->private_data; for ( unsigned int i = 0; i < pd->num_active_list; i++ ) { if ( selected_line >= pd->active_list[i].start && selected_line <= pd->active_list[i].stop ) { *state |= ACTIVE; } } for ( unsigned int i = 0; i < pd->num_urgent_list; i++ ) { if ( selected_line >= pd->urgent_list[i].start && selected_line <= pd->urgent_list[i].stop ) { *state |= URGENT; } } if ( pd->do_markup ) { *state |= MARKUP; } return get_entry ? g_strdup ( pd->cmd_list[selected_line] ) : NULL; } static int script_token_match ( const Mode *sw, rofi_int_matcher **tokens, unsigned int index ) { ScriptModePrivateData *rmpd = sw->private_data; return helper_token_match ( tokens, rmpd->cmd_list[index] ); } static char *script_get_message ( const Mode *sw ) { ScriptModePrivateData *pd = sw->private_data; return g_strdup ( pd->message ); } #include "mode-private.h" Mode *script_switcher_parse_setup ( const char *str ) { Mode *sw = g_malloc0 ( sizeof ( *sw ) ); char *endp = NULL; char *parse = g_strdup ( str ); unsigned int index = 0; const char *const sep = ":"; for ( char *token = strtok_r ( parse, sep, &endp ); token != NULL; token = strtok_r ( NULL, sep, &endp ) ) { if ( index == 0 ) { sw->name = g_strdup ( token ); } else if ( index == 1 ) { sw->ed = (void *) rofi_expand_path ( token ); } index++; } g_free ( parse ); if ( index == 2 ) { sw->free = script_switcher_free; sw->_init = script_mode_init; sw->_get_num_entries = script_mode_get_num_entries; sw->_result = script_mode_result; sw->_destroy = script_mode_destroy; sw->_token_match = script_token_match; sw->_get_message = script_get_message; sw->_get_completion = NULL, sw->_preprocess_input = NULL, sw->_get_display_value = _get_display_value; return sw; } fprintf ( stderr, "The script command '%s' has %u options, but needs 2: :