├── .gitignore ├── AUTHORS ├── LICENSE ├── Makefile.am ├── NEWS ├── README.md ├── TODO ├── autogen.sh ├── configure.ac ├── css └── default.css ├── glade └── xfce4-finder.glade ├── scripts ├── build-default-css-h └── build-glade-h └── src ├── .dirstamp ├── custom-css.h └── xfce4-finder.cc /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | *.smod 19 | 20 | # Compiled Static libraries 21 | *.lai 22 | *.la 23 | *.a 24 | *.lib 25 | 26 | # Executables 27 | *.exe 28 | *.out 29 | *.app 30 | 31 | Makefile 32 | Makefile.in 33 | aclocal.m4 34 | autom4te.cache/ 35 | config.h 36 | config.h.in 37 | config.log 38 | config.status 39 | configure 40 | depcomp 41 | glade/* 42 | install-sh 43 | missing 44 | src/.deps/ 45 | src/default-css.h 46 | src/glade.h 47 | stamp-h1 48 | xfce4-finder 49 | 50 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Jason Graves 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Jason Graves 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | AUTOMAKE_OPTIONS = subdir-objects foreign 2 | ACLOCAL_AMFLAGS = $(ACLOCAL_FLAGS) 3 | 4 | BUILT_SOURCES = glade.h default-css.h 5 | CLEANFILES = src/glade.h src/default-css.h 6 | 7 | glade.h: 8 | $(srcdir)/scripts/build-glade-h 9 | 10 | default-css.h: 11 | $(srcdir)/scripts/build-default-css-h 12 | 13 | bin_PROGRAMS = xfce4-finder 14 | xfce4_finder_CPPFLAGS = $(GTKMM_CFLAGS) $(XFCONF_CFLAGS) $(GARCON_CFLAGS) $(LIBXFCE4UTIL_CFLAGS) $(LIBXFCE4UI_CFLAGS) $(GLIB_CFLAGS) $(GIO_CFLAGS) 15 | xfce4_finder_LDADD = $(GTKMM_LIBS) $(XFCONF_LIBS) $(GARCON_LIBS) $(LIBXFCE4UTIL_LIBS) $(LIBXFCE4_LIBS) $(GLIB_LIBS) $(GIO_LIBS) 16 | xfce4_finder_SOURCES = config.h \ 17 | src/glade.h \ 18 | src/default-css.h \ 19 | src/custom-css.h \ 20 | src/xfce4-finder.cc 21 | 22 | 23 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | ===== 3 | - First initial release with full theme and customization support. 4 | - Current support for settings requires using xfconf-query 5 | - /application-directories - defaults to "/usr/share/applications", "~/local/share/applications" 6 | - /file-extension - defaults to ".desktop" 7 | - /web-search - defaults to "http://www.google.com/#safe=on&q=%s" 8 | - /theme - defaults to "default" 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # xfce4-finder 3 | Smart and intuitive application finder, complete with theme and customization support. 4 | 5 | ![Screenshot](https://cloud.githubusercontent.com/assets/7003154/20498215/3f256b5c-affa-11e6-9f6f-9fcdc8b94f08.png) 6 | 7 | ## Key Features 8 | - Blazingly fast realtime application finding 9 | - Inline application argument launching using tab 10 | - Begin by typing an application name, hit TAB_KEY to lock it in, begin typing arguments to that application *(ie. "mail" TAB_KEY "someone@someplace.com" ENTER_KEY)*. 11 | - Customizable UI and theme using CSS 12 | - Intuitive Web/Desktop Integration 13 | - Just type what you're looking for, the application will determine whether or not an application should be launched or if the context requires an online web search. 14 | - Direct command invocation 15 | - Utilize terminal commands directly by typing the executable name *(ie. "gedit" ENTER_KEY)*. 16 | - Supports tab autocomplete for local application and filenames 17 | - Supports CONTROL_KEY + ENTER_KEY for domain name completion *(ie. google => www.google.com, google.com => www.google.com, www.google => www.google.com)*. 18 | 19 | ## Customizable Settings 20 | Application settings can be controlled using xfconf-query. 21 | 22 | Available settings: 23 | - /file-extension - defaults to ".desktop", controls the filenames to use for parsing. 24 | - /web-search - defaults to "https://www.google.com/#safe=on&q=%s", controls the url format to use when applying a web search, %s is replaced by the query to be used. 25 | - /theme = defaults to "default", controls the CSS file to use for the application *(Default location is: ~/.config/xfce4/finder/default.css)*. 26 | - /application-directories - defaults to /usr/share/applications and ~/.local/share/applications, specifies the directories in which to look for application files with matching /file-extension. 27 | 28 | List all available settings: 29 | 30 | xfconf-query -c xfce4-finder -l 31 | 32 | List a setting: 33 | 34 | xfconf-query -c xfce4-finder -p /web-search 35 | 36 | Change a setting: 37 | 38 | xfconf-query -c xfce4-finder -p /web-search -s https://www.bing.com?q=%s 39 | 40 | Set array values: 41 | 42 | xfconf-query -c xfce4-finder -p /application-directories -s /usr/share/applications -s /usr/local/share/applications 43 | 44 | The above sets the application-directories array property which is used by xfce4-finder to location application files to /usr/share/applications and /usr/local/share/applications. You may need to update this setting if your applications directory is not included by default. 45 | 46 | ## Installation 47 | This application is available under the Arch Linux AUR repository as a [PKGBUILD](https://aur.archlinux.org/packages/xfce4-finder/). To install using [yay](https://aur.archlinux.org/packages/yay/), use the following command: 48 | 49 | yay xfce4-finder 50 | 51 | ## Building 52 | To build the application you will need to have the following dependencies installed. 53 | - [Glib 2.0](https://developer.gnome.org/glib/) 54 | - [Gio 2.0](https://developer.gnome.org/gio/) 55 | - [Gtkmm 3.0](http://www.gtkmm.org/en/) 56 | - [Garcon](http://www.linuxfromscratch.org/blfs/view/svn/xfce/garcon.html) 57 | - [libxfconf](http://www.linuxfromscratch.org/blfs/view/systemd/xfce/xfconf.html) 58 | - [libxfce4util](http://www.linuxfromscratch.org/blfs/view/7.9/xfce/libxfce4util.html) 59 | - [libxfce4ui](http://www.linuxfromscratch.org/blfs/view/systemd/xfce/libxfce4ui.html) 60 | - [automake](https://www.gnu.org/software/automake/) 61 | - [autoconf](https://www.gnu.org/software/autoconf/autoconf.html) 62 | - [xfce4-dev-tools](http://www.xfce.org/) 63 | 64 | On Arch Linux: 65 | 66 | sudo pacman -S glib2 gtkmm garcon xfconf libxfce4util libxfce4ui automake autoconf xfce4-dev-tools 67 | 68 | 69 | 70 | Building the application: 71 | 72 | cd xfce4-finder/ 73 | ./autogen.sh 74 | make 75 | make install 76 | 77 | ## Running 78 | Open xfce4-keyboard-settings and bind a key combination to use for the xfce4-finder such as META_KEY + SPACE_KEY or ALT_KEY + SPACE_KEY and invoke /usr/bin/xfce4-finder. 79 | 80 | Once the application is displayed, begin typing. Execute commands with either the ENTER_KEY or by mouse clicking a selection in the autocomplete list. 81 | 82 | ## Command Arguments 83 | 84 | xfce4-finder 1.0 85 | 86 | Usage: 87 | xfce4-finder [OPTION...] 88 | 89 | Help Options: 90 | -h, --help Show help options 91 | -v, --verbose Show verbose text 92 | 93 | ## Features/Issues 94 | Feel free add feature requests or report issues: [Features/Issues](https://github.com/godlikemouse/xfce4-finder/issues). 95 | 96 | ## Troubleshooting 97 | 98 | - The application won't run resulting the error: Error: Could not open directory /home/USER/.local/share/applications 99 | 100 | * Update the application-directories setting using the Set array values example listed under [Customizable Settings](#customizable-settings). 101 | 102 | 103 | ## Community 104 | 105 | Keep track of development and community news. 106 | 107 | * Follow [@Collaboradev on Twitter](https://twitter.com/collaboradev). 108 | * Follow the [Collaboradev Blog](http://www.collaboradev.com). 109 | 110 | ## License 111 | 112 | xfce4-finder is released under the MIT License. 113 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | - Add internationalization support. 2 | - Add user customizable regex support to get_search_entry_type actions. 3 | - Add settings dialog for customization to xfconf. 4 | - Investigate integration of desktop file actions. 5 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2002-2011 The Xfce development team. All rights reserved. 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License along 16 | # with this program; if not, write to the Free Software Foundation, Inc., 17 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | # 19 | # Written for Xfce by Benedikt Meurer . 20 | # 21 | 22 | 23 | 24 | (type xdt-autogen) >/dev/null 2>&1 || { 25 | cat >&2 < 2 | 3 | 4 | 5 | 6 | window 7 | True 8 | False 9 | False 10 | xfce4-finder 11 | 12 | 13 | True 14 | False 15 | True 16 | vertical 17 | top 18 | 19 | 20 | True 21 | False 22 | 23 | 24 | search-icon 25 | True 26 | False 27 | gtk-discard 28 | 31 | 32 | 33 | False 34 | True 35 | 0 36 | 37 | 38 | 39 | 40 | search-text 41 | True 42 | True 43 | True 44 | False 45 | False 46 | Search for an application... 47 | 50 | 51 | 52 | False 53 | True 54 | 1 55 | 56 | 57 | 58 | 59 | False 60 | 61 | 62 | search-arguments-icon 63 | True 64 | False 65 | gtk-discard 66 | 69 | 70 | 71 | False 72 | True 73 | 0 74 | 75 | 76 | 77 | 78 | search-application 79 | True 80 | True 81 | False 82 | True 83 | none 84 | True 85 | 88 | 89 | 90 | False 91 | True 92 | 1 93 | 94 | 95 | 96 | 97 | search-arguments 98 | True 99 | True 100 | True 101 | 104 | 105 | 106 | False 107 | True 108 | 2 109 | 110 | 111 | 112 | 113 | False 114 | True 115 | 2 116 | 117 | 118 | 119 | 120 | False 121 | True 122 | 0 123 | 124 | 125 | 126 | 127 | autocomplete-overlay 128 | True 129 | False 130 | True 131 | True 132 | 133 | 134 | autocomplete-scroll 135 | True 136 | False 137 | True 138 | True 139 | in 140 | 141 | 142 | autocomplete 143 | True 144 | False 145 | True 146 | False 147 | False 148 | False 149 | True 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -1 158 | 159 | 160 | 161 | 162 | False 163 | True 164 | 1 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /scripts/build-default-css-h: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | FILE="src/default-css.h" 4 | echo "#ifndef __DEFAULT_CSS_H" > $FILE 5 | echo "#define __DEFAULT_CSS_H" >> $FILE 6 | echo "#include " >> $FILE 7 | echo >> $FILE 8 | echo -n "const Glib::ustring DEFAULT_CSS(\"" >> $FILE 9 | head -c -1 -q css/default.css | sed 's/\"/\\"/gm' | sed ':a;N;$!ba;s/\n/\\\n/g' >> $FILE 10 | echo -n "\");" >> $FILE 11 | echo >> $FILE 12 | echo "#endif" >> $FILE 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /scripts/build-glade-h: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "#ifndef __GLADE_H" > src/glade.h 4 | echo "#define __GLADE_H" >> src/glade.h 5 | echo "#include " >> src/glade.h 6 | echo >> src/glade.h 7 | echo -n "const Glib::ustring GLADE_XML(\"" >> src/glade.h 8 | head -c -1 -q glade/xfce4-finder.glade | sed 's/\"/\\"/gm' | sed ':a;N;$!ba;s/\n//g' >> src/glade.h 9 | echo -n "\");" >> src/glade.h 10 | echo >> src/glade.h 11 | echo "#endif" >> src/glade.h 12 | -------------------------------------------------------------------------------- /src/.dirstamp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godlikemouse/xfce4-finder/d1f6383aa976d132fc6f8bcd44d602f8befac0ff/src/.dirstamp -------------------------------------------------------------------------------- /src/custom-css.h: -------------------------------------------------------------------------------- 1 | #ifndef __CUSTOM_CSS_H 2 | #define __CUSTOM_CSS_H 3 | 4 | class CustomCss : public Gtk::Widget { 5 | public: 6 | 7 | Gtk::StyleProperty width_property; 8 | int width; 9 | 10 | Gtk::StyleProperty height_property; 11 | int height; 12 | 13 | Gtk::StyleProperty results_property; 14 | int results; 15 | 16 | //constructor 17 | CustomCss() : 18 | Glib::ObjectBase("finder"), 19 | Gtk::Widget(), 20 | //-gtkmm__CustomObject_customcss-width 21 | width_property(*this, "width", 1000), 22 | width(1000), 23 | 24 | //-gtkmm__CustomObject_customcss-height 25 | height_property(*this, "height", 400), 26 | height(400), 27 | 28 | //-gtkmm__CustomObject_customcss-results 29 | results_property(*this, "results", 10), 30 | results(10) 31 | {} 32 | }; 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /src/xfce4-finder.cc: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_CONFIG_H 2 | #include 3 | #endif 4 | 5 | #ifdef HAVE_STDLIB_H 6 | #include 7 | #endif 8 | 9 | #ifdef HAVE_UNISTD_H 10 | #include 11 | #endif 12 | 13 | #ifdef HAVE_ERRNO_H 14 | #include 15 | #endif 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "glade.h" 31 | #include "default-css.h" 32 | #include "custom-css.h" 33 | 34 | Glib::RefPtr p_builder; 35 | Glib::RefPtr p_application; 36 | Glib::RefPtr p_list_store; 37 | Gtk::Overlay* p_autocomplete_overlay = nullptr; 38 | Gtk::Window * p_main_window = nullptr; 39 | Gtk::Entry * p_search_text = nullptr; 40 | Gtk::Image * p_search_icon = nullptr; 41 | Gtk::TreeView * p_autocomplete = nullptr; 42 | Gtk::Button * p_search_application = nullptr; 43 | Gtk::Image * p_search_arguments_icon = nullptr; 44 | Gtk::Entry * p_search_arguments = nullptr; 45 | Gtk::Box * p_arguments_layout = nullptr; 46 | std::vector directories; 47 | Glib::ustring file_extension(".desktop"); 48 | Glib::ustring web_search("https://www.google.com/#safe=on&q=%s"); 49 | Glib::ustring theme("default"); 50 | int results_limit; 51 | bool verbose = false; 52 | Glib::ustring default_browser; 53 | Glib::ustring default_browser_icon; 54 | 55 | struct SearchEntry { 56 | Glib::ustring filename; 57 | Glib::ustring directory; 58 | std::size_t position; 59 | Glib::ustring name; 60 | Glib::ustring description; 61 | Glib::ustring exec; 62 | Glib::ustring icon; 63 | }; 64 | 65 | struct ExecSpec { 66 | Glib::ustring cmd; 67 | std::vector args; 68 | }; 69 | 70 | class SearchEntryType { 71 | public: 72 | bool is_url; 73 | bool is_ftp; 74 | bool is_search; 75 | bool is_command; 76 | bool is_file; 77 | bool is_directory; 78 | bool is_application; 79 | Glib::ustring text; 80 | 81 | SearchEntryType(){ 82 | is_url = 83 | is_ftp = 84 | is_search = 85 | is_application = 86 | is_command = 87 | is_file = 88 | is_directory = false; 89 | } 90 | 91 | SearchEntryType(Glib::ustring& s) : SearchEntryType() { 92 | text = s; 93 | } 94 | }; 95 | 96 | class AutocompleteColumns : public Gtk::TreeModel::ColumnRecord { 97 | 98 | public: 99 | Gtk::TreeModelColumn< Glib::RefPtr > icon; 100 | Gtk::TreeModelColumn name; 101 | Gtk::TreeModelColumn description; 102 | 103 | Gtk::TreeModelColumn data; 104 | 105 | AutocompleteColumns(){ 106 | add(icon); 107 | add(name); 108 | add(description); 109 | add(data); 110 | } 111 | 112 | }; 113 | 114 | const Glib::ustring HOME_DIRECTORY = getenv("HOME"); 115 | const AutocompleteColumns autocomplete_columns; 116 | 117 | //method for loading configured channel settings and writing defaults 118 | inline void load_channel_settings(){ 119 | 120 | auto p_channel = xfconf_channel_get("xfce4-finder"); 121 | 122 | //load directory settings 123 | GPtrArray * p_directories = nullptr; 124 | const char * APPLICATION_DIRECTORIES_PATH = "/application-directories"; 125 | 126 | if(xfconf_channel_has_property(p_channel, APPLICATION_DIRECTORIES_PATH)){ 127 | p_directories = xfconf_channel_get_arrayv(p_channel, APPLICATION_DIRECTORIES_PATH); 128 | } 129 | else { 130 | p_directories = g_ptr_array_new(); 131 | 132 | std::vector default_directories = { 133 | "/usr/share/applications", 134 | "/usr/share/applications/kde4", 135 | HOME_DIRECTORY + "/.local/share/applications" 136 | }; 137 | 138 | for(auto iter = default_directories.begin(); 139 | iter < default_directories.end(); 140 | ++iter){ 141 | 142 | GValue * p_value = new GValue(); 143 | *p_value = G_VALUE_INIT; 144 | g_value_init(p_value, G_TYPE_STRING); 145 | g_value_set_string(p_value, (*iter).data()); 146 | g_ptr_array_add(p_directories, p_value); 147 | } 148 | 149 | xfconf_channel_set_arrayv(p_channel, APPLICATION_DIRECTORIES_PATH, p_directories); 150 | } 151 | 152 | //set configured directories 153 | for(int i=0; ilen; i++){ 154 | Glib::ustring directory( g_value_get_string( (GValue*) g_ptr_array_index(p_directories, i) ) ); 155 | directories.push_back(directory); 156 | } 157 | 158 | xfconf_array_free(p_directories); 159 | 160 | //get/set file extension 161 | const char * FILE_EXTENSION_PATH = "/file-extension"; 162 | 163 | if(xfconf_channel_has_property(p_channel, FILE_EXTENSION_PATH)){ 164 | file_extension = xfconf_channel_get_string(p_channel, FILE_EXTENSION_PATH, file_extension.data()); 165 | } 166 | else{ 167 | xfconf_channel_set_string(p_channel, FILE_EXTENSION_PATH, file_extension.data()); 168 | } 169 | 170 | //get/set web search 171 | const char * WEB_SEARCH_PATH = "/web-search"; 172 | 173 | if(xfconf_channel_has_property(p_channel, WEB_SEARCH_PATH)){ 174 | web_search = xfconf_channel_get_string(p_channel, WEB_SEARCH_PATH, web_search.data()); 175 | } 176 | else{ 177 | xfconf_channel_set_string(p_channel, WEB_SEARCH_PATH, web_search.data()); 178 | } 179 | 180 | //get/set theme 181 | const char * THEME_PATH = "/theme"; 182 | 183 | if(xfconf_channel_has_property(p_channel, THEME_PATH)){ 184 | theme = xfconf_channel_get_string(p_channel, THEME_PATH, theme.data()); 185 | } 186 | else{ 187 | xfconf_channel_set_string(p_channel, THEME_PATH, theme.data()); 188 | } 189 | } 190 | 191 | //method for replacing all occurances of a string 192 | inline Glib::ustring replace_all(const Glib::ustring& haystack, const Glib::ustring& needle, const Glib::ustring& replace) { 193 | 194 | Glib::ustring str = haystack; 195 | Glib::ustring::size_type pos; 196 | Glib::ustring::size_type offset = 0; 197 | while( (pos = str.find(needle, offset)) != Glib::ustring::npos ){ 198 | offset = pos + replace.size(); 199 | str = str.replace(pos, needle.size(), replace); 200 | } 201 | 202 | return str; 203 | } 204 | 205 | //method for beautifying the default css file 206 | inline Glib::ustring pretty_print_css(const Glib::ustring& content){ 207 | 208 | Glib::ustring c = content; 209 | c = replace_all(c, "}", "}\n\n"); 210 | c = replace_all(c, "{", "{\n"); 211 | c = replace_all(c, ";", ";\n"); 212 | 213 | return c; 214 | } 215 | 216 | //method for splitting a string from a delimiter 217 | inline std::vector split(const Glib::ustring& value, const char delim){ 218 | std::stringstream splitstream(value.data()); 219 | std::string segment; 220 | std::vector list; 221 | 222 | while(std::getline(splitstream, segment, delim)){ 223 | list.push_back(Glib::ustring(segment)); 224 | } 225 | 226 | return list; 227 | } 228 | 229 | //method for joining an array into a string by delimeter 230 | inline Glib::ustring join(const std::vector& value, const char delim){ 231 | Glib::ustring str; 232 | int index = 0; 233 | for(auto iter = value.begin(); 234 | iter < value.end(); 235 | ++iter){ 236 | 237 | str += *iter; 238 | 239 | if(++index < value.size()) 240 | str += delim; 241 | } 242 | 243 | return str; 244 | } 245 | 246 | //method for left trimming a string 247 | inline Glib::ustring ltrim(const Glib::ustring& s){ 248 | Glib::ustring str = s; 249 | 250 | bool done = false; 251 | for(auto iter = str.begin(); 252 | iter < str.end() && !done; 253 | ++iter){ 254 | 255 | switch(*iter){ 256 | case 32: //space 257 | case 10: //tab 258 | str.erase(iter); 259 | break; 260 | 261 | case 0: //null 262 | continue; 263 | 264 | default: 265 | done = true; 266 | break; 267 | } 268 | } 269 | 270 | return str; 271 | } 272 | 273 | //method for right trimming a string 274 | inline Glib::ustring rtrim(const Glib::ustring& s){ 275 | Glib::ustring str = s; 276 | 277 | bool done = false; 278 | for(auto iter = str.end(); 279 | iter > str.begin() && !done; 280 | --iter){ 281 | 282 | switch(*iter){ 283 | case 32: //space 284 | case 10: //tab 285 | str.erase(iter); 286 | break; 287 | 288 | case 0: //null 289 | continue; 290 | 291 | default: 292 | done = true; 293 | break; 294 | } 295 | } 296 | 297 | return str; 298 | } 299 | 300 | //method for trimming a string 301 | inline Glib::ustring trim(const Glib::ustring& s){ 302 | return ltrim(rtrim(s)); 303 | } 304 | 305 | //method for retrieving the current query text type 306 | inline SearchEntryType get_search_entry_type(const Glib::ustring& text){ 307 | 308 | SearchEntryType search_type; 309 | search_type.text = text; 310 | 311 | //get current selection 312 | Glib::RefPtr selection = p_autocomplete->get_selection(); 313 | Gtk::TreeModel::Row row = *(selection->get_selected()); 314 | if(row){ 315 | search_type.is_application = true; 316 | return search_type; 317 | } 318 | 319 | //detect web url 320 | //if text doesn't have any spaces and it contains at least one period 321 | search_type.text = trim(search_type.text); 322 | if(search_type.text.find(" ") == Glib::ustring::npos && 323 | search_type.text.find(".") != Glib::ustring::npos){ 324 | 325 | search_type.is_url = true; 326 | } 327 | 328 | if(search_type.is_url){ 329 | if(search_type.text.find("ftp.") == 0) 330 | search_type.text = "ftp://" + search_type.text; 331 | 332 | if(search_type.text.find("ftp://") != Glib::ustring::npos) 333 | search_type.is_ftp = true; 334 | } 335 | 336 | //web search 337 | if(!row && search_type.text.find(" ") != Glib::ustring::npos){ 338 | search_type.is_search = true; 339 | } 340 | 341 | //check file type 342 | struct stat file_info; 343 | Glib::ustring location = replace_all(search_type.text, "~", HOME_DIRECTORY); 344 | if(stat(location.data(), &file_info) != -1){ 345 | switch(file_info.st_mode & S_IFMT){ 346 | case S_IFDIR: 347 | search_type.is_directory = true; 348 | search_type.is_url = false; 349 | break; 350 | 351 | case S_IFREG: 352 | search_type.is_file = true; 353 | search_type.is_url = false; 354 | break; 355 | } 356 | } 357 | 358 | //fallback to command 359 | if(!search_type.is_url && 360 | !search_type.is_ftp && 361 | !search_type.is_search && 362 | !search_type.is_directory && 363 | !search_type.is_file && 364 | !row && 365 | text.size()){ 366 | 367 | search_type.is_command = true; 368 | } 369 | 370 | return search_type; 371 | } 372 | 373 | //method for retrieving the current theme icon by name 374 | inline Glib::RefPtr get_theme_icon(const Glib::ustring& icon_name){ 375 | 376 | Glib::RefPtr icon; 377 | 378 | try{ 379 | Glib::RefPtr theme = Gtk::IconTheme::get_default(); 380 | int lookup_flags = Gtk::ICON_LOOKUP_USE_BUILTIN | Gtk::ICON_LOOKUP_FORCE_SIZE; 381 | icon = theme->load_icon(icon_name, lookup_flags); 382 | } 383 | catch(Glib::Error& ex){} 384 | 385 | return icon; 386 | } 387 | 388 | //method for launching an external process 389 | inline void launch_process(const Glib::ustring cmd, std::vector args){ 390 | 391 | pid_t pid; 392 | char * argv[args.size() + 1]; 393 | 394 | if(verbose) 395 | std::cout << "Launching: " << cmd; 396 | 397 | //convert vector to const char * array 398 | int index = 0; 399 | for(auto iter = args.begin(); 400 | iter != args.end(); 401 | ++iter){ 402 | 403 | if(index > 0 && verbose) 404 | std::cout << " " << *iter; 405 | 406 | if( (*iter).size() ) 407 | argv[index++] = const_cast((*iter).data()); 408 | } 409 | argv[index] = NULL; 410 | 411 | if(verbose) 412 | std::cout << std::endl; 413 | 414 | posix_spawn(&pid, cmd.data(), NULL, NULL, argv, environ); 415 | } 416 | 417 | //method for sorting matches based on position 418 | inline bool sort_position_matches(const SearchEntry& a, const SearchEntry& b){ 419 | return a.position < b.position; 420 | } 421 | 422 | //method for sorting matches based on name if positions are the same 423 | inline bool sort_name_matches(const SearchEntry& a, const SearchEntry& b){ 424 | if(a.position == b.position) 425 | return a.name.compare(b.name) < 0; 426 | return a.position < b.position; 427 | } 428 | 429 | //method for sorting directory entries 430 | inline bool sort_directory(const Glib::ustring& a, const Glib::ustring& b){ 431 | return a.compare(b) < 0; 432 | } 433 | 434 | //method for verifying if a file exists 435 | inline bool file_exists(const Glib::ustring filename) { 436 | if (FILE *file = fopen(filename.data(), "r")) { 437 | fclose(file); 438 | return true; 439 | } 440 | 441 | return false; 442 | } 443 | 444 | //method for writing to a file 445 | inline bool write_file(const Glib::ustring filename, const Glib::ustring contents){ 446 | if(FILE *file = fopen(filename.data(), "w")) { 447 | fputs(contents.data(), file); 448 | fclose(file); 449 | return true; 450 | } 451 | 452 | return false; 453 | } 454 | 455 | //method for executing a command and retrieving the output 456 | Glib::ustring passthru(const Glib::ustring& cmd) { 457 | char buffer[128]; 458 | Glib::ustring result = ""; 459 | std::shared_ptr pipe(popen(cmd.data(), "r"), pclose); 460 | if (!pipe) throw std::runtime_error("popen() failed!"); 461 | while (!feof(pipe.get())) { 462 | if (fgets(buffer, 128, pipe.get()) != NULL) 463 | result += buffer; 464 | } 465 | return trim(result); 466 | } 467 | 468 | //method for creating an ExecSpec from an exec command string 469 | inline ExecSpec parse_exec_path(const Glib::ustring& exec_path){ 470 | 471 | //autocomplete parsing 472 | 473 | //replace ~ with home 474 | Glib::ustring search_arguments = replace_all(p_search_arguments->get_text(), "~", HOME_DIRECTORY); 475 | 476 | //break into command and arguments 477 | std::vector exec_parts = split(exec_path, ' '); 478 | 479 | ExecSpec spec; 480 | 481 | for(auto iter = exec_parts.begin(); iter != exec_parts.end(); ++iter){ 482 | if(iter == exec_parts.begin()){ 483 | spec.cmd = *iter; 484 | spec.args.push_back(spec.cmd); 485 | } 486 | else{ 487 | 488 | /* 489 | %f A single file name, even if multiple files are selected. The system reading the desktop entry should recognize that the program in question cannot handle multiple file arguments, and it should should probably spawn and execute multiple copies of a program for each selected file if the program is not able to handle additional file arguments. If files are not on the local file system (i.e. are on HTTP or FTP locations), the files will be copied to the local file system and %f will be expanded to point at the temporary file. Used for programs that do not understand the URL syntax. 490 | %F A list of files. Use for apps that can open several local files at once. Each file is passed as a separate argument to the executable program. 491 | %u A single URL. Local files may either be passed as file: URLs or as file path. 492 | %U A list of URLs. Each URL is passed as a separate argument to the executable program. Local files may either be passed as file: URLs or as file path. 493 | %i The Icon key of the desktop entry expanded as two arguments, first --icon and then the value of the Icon key. Should not expand to any arguments if the Icon key is empty or missing. 494 | %c The translated name of the application as listed in the appropriate Name key in the desktop entry. 495 | %k The location of the desktop file as either a URI (if for example gotten from the vfolder system) or a local filename or empty if no location is known. 496 | */ 497 | 498 | //replace execution variables 499 | *iter = replace_all(*iter, "%u", search_arguments); 500 | *iter = replace_all(*iter, "%U", search_arguments); 501 | *iter = replace_all(*iter, "%f", search_arguments); 502 | *iter = replace_all(*iter, "%F", search_arguments); 503 | *iter = replace_all(*iter, "%s", search_arguments); 504 | 505 | spec.args.push_back(*iter); 506 | } 507 | } 508 | 509 | return spec; 510 | } 511 | 512 | //method for displaying selection details 513 | inline void display_selection_details(){ 514 | Glib::RefPtr selection = p_autocomplete->get_selection(); 515 | Gtk::TreeModel::Row row = *(selection->get_selected()); 516 | 517 | if(row){ 518 | SearchEntry search_entry = row[autocomplete_columns.data]; 519 | p_search_icon->set(row[autocomplete_columns.icon]); 520 | p_search_arguments_icon->set(row[autocomplete_columns.icon]); 521 | } 522 | } 523 | 524 | //method for retrieving directory list 525 | inline std::vector list_directory(const Glib::ustring& directory){ 526 | 527 | //complete, perform lookup under this location if possible 528 | DIR * p_directory = NULL; 529 | struct dirent * p_entry = NULL; 530 | std::vector entries; 531 | 532 | if( (p_directory = opendir(directory.data())) != NULL){ 533 | 534 | //populate entries 535 | while( (p_entry = readdir(p_directory)) != NULL ){ 536 | 537 | if(strcmp(p_entry->d_name, ".") == 0 || 538 | strcmp(p_entry->d_name, "..") == 0) 539 | continue; 540 | 541 | entries.push_back(p_entry->d_name); 542 | } 543 | 544 | closedir(p_directory); 545 | } 546 | 547 | //sort matches based on name 548 | std::sort(entries.begin(), entries.end(), sort_directory); 549 | 550 | return entries; 551 | } 552 | 553 | //method for auto tab completion of local directory/file lookups 554 | inline Glib::ustring auto_tab(const Glib::ustring& location){ 555 | Glib::ustring complete = location; 556 | std::vector parts = split(complete, '/'); 557 | Glib::ustring last = parts[parts.size()-1]; 558 | 559 | //is last complete yet 560 | SearchEntryType search_type = get_search_entry_type(replace_all(complete, "~", HOME_DIRECTORY)); 561 | if( (search_type.is_file || search_type.is_directory) && 562 | last.compare(".") != 0 && 563 | last.compare("..") != 0){ 564 | 565 | //get directory listing 566 | std::vector entries = list_directory(search_type.text); 567 | 568 | //add first entry 569 | for(auto iter = entries.begin(); 570 | iter < entries.end(); 571 | ++iter){ 572 | 573 | parts.push_back( *iter ); 574 | break; 575 | } 576 | 577 | //reconstruct string 578 | complete = join(parts, '/'); 579 | } 580 | else { 581 | 582 | //incomplete, perform lookup from last parent and match 583 | parts.pop_back(); 584 | 585 | Glib::ustring parent_directory = replace_all(join(parts, '/'), "~", HOME_DIRECTORY); 586 | 587 | if(parent_directory.size() == 0) 588 | parent_directory = "/"; 589 | 590 | //get directory listing 591 | std::vector entries = list_directory(parent_directory.data()); 592 | 593 | //iterate directories 594 | if(!entries.size()){ 595 | 596 | //current location is not directory, exit 597 | return location; 598 | } 599 | 600 | bool found = false; 601 | for(auto iter = entries.begin(); 602 | iter < entries.end(); 603 | ++iter){ 604 | 605 | Glib::ustring entry_name(*iter); 606 | if(entry_name.find(last) == 0){ 607 | 608 | //found match 609 | parts.push_back(entry_name); 610 | found = true; 611 | break; 612 | } 613 | } 614 | 615 | if(!found) 616 | parts.push_back(last); 617 | 618 | complete = join(parts, '/'); 619 | } 620 | 621 | //add last / if directory 622 | search_type = get_search_entry_type(replace_all(complete, "~", HOME_DIRECTORY)); 623 | if(complete.at(complete.size()-1) != '/' && search_type.is_directory) 624 | complete += "/"; 625 | 626 | return complete; 627 | } 628 | 629 | //method for setting the search icon 630 | inline void set_icon(const Glib::RefPtr& icon){ 631 | p_search_icon->set(icon); 632 | p_search_arguments_icon->set(icon); 633 | } 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | //method for handling search text icon clicks 642 | void on_search_text_icon_press(Gtk::EntryIconPosition icon_position, const GdkEventButton* button){ 643 | 644 | if(icon_position == Gtk::ENTRY_ICON_SECONDARY){ 645 | 646 | } 647 | } 648 | 649 | //method for handling parsing errors 650 | void on_parsing_error(const Glib::RefPtr& section, const Glib::Error& error){ 651 | std::cerr << "CSS Parsing Error: " << error.what() << std::endl; 652 | if (section){ 653 | std::cerr << "file: " << section->get_file()->get_uri() << std::endl; 654 | std::cerr << "line: (" << section->get_start_line()+1 655 | << ", " << section->get_end_line()+1 << ")" << std::endl; 656 | std::cerr << "position: (" << section->get_start_position() 657 | << ", " << section->get_end_position() << ")" << std::endl; 658 | } 659 | } 660 | 661 | //method for handling on tree selection change events 662 | void on_tree_selection_changed(){ 663 | display_selection_details(); 664 | } 665 | 666 | //handle activate events 667 | void on_activate(){ 668 | 669 | //get current selection 670 | Glib::RefPtr selection = p_autocomplete->get_selection(); 671 | Gtk::TreeModel::Row row = *(selection->get_selected()); 672 | 673 | SearchEntry search_entry; 674 | if(!row){ 675 | //fallback to raw execution of search text 676 | search_entry.exec = p_search_text->get_text(); 677 | } 678 | else{ 679 | //retrieve from list selection and launch with exo 680 | search_entry = row[autocomplete_columns.data]; 681 | } 682 | 683 | //exit if search entry is empty 684 | if(search_entry.exec.size() == 0){ 685 | p_application->quit(); 686 | return; 687 | } 688 | 689 | if(verbose) 690 | std::cout << "Activating: " << search_entry.exec << std::endl; 691 | 692 | ExecSpec spec = parse_exec_path(search_entry.exec); 693 | Glib::ustring search_text = p_search_text->get_text(); 694 | 695 | if(verbose) 696 | std::cout << "Text: " << search_text << std::endl; 697 | 698 | SearchEntryType search_type = get_search_entry_type(search_text); 699 | 700 | //if not full execution path, find execution path and modify spec.cmd 701 | if(spec.cmd.at(0) != '/' && 702 | spec.cmd.at(0) != '~' && 703 | spec.cmd.at(0) != '.'){ 704 | 705 | if(verbose) 706 | std::cout << "Relative execution found." << std::endl; 707 | 708 | std::vector paths = split(getenv("PATH"), ':'); 709 | 710 | for(auto iter = paths.begin(); 711 | iter != paths.end(); 712 | ++iter){ 713 | 714 | if(file_exists(*iter + "/" + spec.cmd)){ 715 | spec.cmd = *iter + "/" + spec.cmd; 716 | break; 717 | } 718 | } 719 | 720 | if(verbose) 721 | std::cout << "Absolute execution: " << spec.cmd << std::endl; 722 | } 723 | else { 724 | 725 | if(verbose) 726 | std::cout << "Absolute execution found." << std::endl; 727 | 728 | Glib::ustring location = trim(p_search_text->get_text()); 729 | 730 | if(location.at(0) == '~') 731 | location = replace_all(location, "~", HOME_DIRECTORY); 732 | 733 | if(verbose) 734 | std::cout << "Using location: " << location << std::endl; 735 | 736 | if(search_type.is_directory){ 737 | spec.args.clear(); 738 | spec.cmd = Glib::ustring("/usr/bin/exo-open"); 739 | spec.args.push_back(Glib::ustring("exo-open")); 740 | spec.args.push_back(Glib::ustring("--launch")); 741 | spec.args.push_back(Glib::ustring("FileManager")); 742 | spec.args.push_back(location); 743 | } 744 | else if(search_type.is_file){ 745 | spec.args.clear(); 746 | spec.cmd = Glib::ustring("/usr/bin/exo-open"); 747 | spec.args.push_back(Glib::ustring("exo-open")); 748 | spec.args.push_back(location); 749 | } 750 | } 751 | 752 | //verify executable 753 | if(!file_exists(spec.cmd) && !search_type.is_application){ 754 | 755 | if(verbose) 756 | std::cout << "Executing web browser command." << std::endl; 757 | 758 | //web browser 759 | spec.args.clear(); 760 | spec.cmd = Glib::ustring("/usr/bin/exo-open"); 761 | spec.args.push_back(Glib::ustring("exo-open")); 762 | spec.args.push_back(Glib::ustring("--launch")); 763 | 764 | if(search_type.is_ftp){ 765 | 766 | //add url to file manager launch 767 | spec.args.push_back(Glib::ustring("FileManager")); 768 | spec.args.push_back(search_type.text); 769 | } 770 | else if(search_type.is_url){ 771 | 772 | //add url to browser launch 773 | spec.args.push_back(Glib::ustring("WebBrowser")); 774 | spec.args.push_back(search_type.text); 775 | } 776 | else{ 777 | 778 | if(verbose) 779 | std::cout << "Web browser search." << std::endl; 780 | 781 | spec.args.push_back(Glib::ustring("WebBrowser")); 782 | 783 | //replace %s character in search engine itegration 784 | spec.args.push_back(replace_all(web_search, "%s", search_type.text)); 785 | } 786 | } 787 | 788 | if(verbose) 789 | std::cout << "Calling launch process: " << spec.cmd << std::endl; 790 | 791 | //launch external process 792 | launch_process(spec.cmd, spec.args); 793 | 794 | //terminate current application 795 | p_application->quit(); 796 | } 797 | 798 | //method for handling tree row activated events 799 | void on_row_activated(const Gtk::TreePath& tree_path, Gtk::TreeViewColumn * column){ 800 | 801 | //forward event to window handler 802 | on_activate(); 803 | } 804 | 805 | //handle search application button clicks 806 | void on_search_application_clicked(){ 807 | p_search_arguments->set_text(""); 808 | p_search_icon->show(); 809 | p_search_text->show(); 810 | p_arguments_layout->hide(); 811 | p_search_text->grab_focus_without_selecting(); 812 | } 813 | 814 | //handle search arcuments key press events 815 | bool on_search_arguments_key_press(const GdkEventKey * key_event){ 816 | 817 | if(key_event->keyval == GDK_KEY_Escape){ 818 | 819 | p_application->quit(); 820 | return true; 821 | } 822 | 823 | if(key_event->keyval == GDK_KEY_Tab){ 824 | 825 | //perform auto complete on local directory and files 826 | Glib::ustring argumentsText = p_search_arguments->get_text(); 827 | if(argumentsText.at(0) == '/' || 828 | argumentsText.at(0) == '~' || 829 | argumentsText.at(0) == '.'){ 830 | 831 | //perform autocomplete lookup 832 | p_search_arguments->set_text(auto_tab(argumentsText)); 833 | p_search_arguments->set_position(p_search_arguments->get_text().size()); 834 | } 835 | return true; 836 | 837 | } 838 | 839 | //reverse application lock 840 | if(key_event->keyval == GDK_KEY_BackSpace && p_search_arguments->get_position() == 0){ 841 | 842 | on_search_application_clicked(); 843 | return true; 844 | } 845 | 846 | return false; 847 | } 848 | 849 | //handle main window keypress events 850 | bool on_search_text_key_press(const GdkEventKey * key_event){ 851 | 852 | //auto add www..com on control + enter 853 | if(key_event->state & Gdk::CONTROL_MASK && key_event->keyval == GDK_KEY_Return){ 854 | Glib::ustring search_text = p_search_text->get_text(); 855 | if(search_text.find("www.") == Glib::ustring::npos){ 856 | p_search_text->set_text("www." + search_text); 857 | } 858 | 859 | search_text = p_search_text->get_text(); 860 | if(search_text.find(".com") == Glib::ustring::npos){ 861 | p_search_text->set_text(search_text + ".com"); 862 | } 863 | } 864 | 865 | if(key_event->keyval == GDK_KEY_Tab){ 866 | 867 | //perform auto complete on local directory and files 868 | Glib::ustring search_text = p_search_text->get_text(); 869 | if(search_text.at(0) == '/' || 870 | search_text.at(0) == '~' || 871 | search_text.at(0) == '.'){ 872 | 873 | //perform autocomplete lookup 874 | p_search_text->set_text(auto_tab(search_text)); 875 | p_search_text->set_position(p_search_text->get_text().size()); 876 | } 877 | else { 878 | Glib::RefPtr selection = p_autocomplete->get_selection(); 879 | Gtk::TreeModel::Row row = *(selection->get_selected()); 880 | 881 | if(!row) 882 | return true; 883 | 884 | SearchEntry search_entry = row[autocomplete_columns.data]; 885 | p_search_text->set_text(search_entry.name); 886 | p_search_text->set_position(search_entry.name.size()); 887 | 888 | p_search_icon->hide(); 889 | p_search_text->hide(); 890 | p_arguments_layout->show(); 891 | 892 | p_search_application->set_label(search_entry.name); 893 | 894 | p_search_arguments->grab_focus(); 895 | } 896 | 897 | return true; 898 | 899 | } 900 | 901 | if(key_event->keyval == GDK_KEY_Escape){ 902 | 903 | p_application->quit(); 904 | return true; 905 | } 906 | 907 | if(key_event->keyval == GDK_KEY_Up){ 908 | 909 | Glib::RefPtr selection = p_autocomplete->get_selection(); 910 | auto iter = selection->get_selected(); 911 | Gtk::TreeModel::Children children = p_list_store->children(); 912 | 913 | if(!iter) 914 | return false; 915 | 916 | if(iter != children.begin()) 917 | iter--; 918 | 919 | if(!iter) 920 | return false; 921 | 922 | selection->select(iter); 923 | } 924 | 925 | if(key_event->keyval == GDK_KEY_Down){ 926 | 927 | Glib::RefPtr selection = p_autocomplete->get_selection(); 928 | auto iter = selection->get_selected(); 929 | Gtk::TreeModel::Children children = p_list_store->children(); 930 | 931 | if(!iter) 932 | return false; 933 | 934 | if(iter != children.end()) 935 | iter++; 936 | 937 | if(!iter) 938 | return false; 939 | 940 | selection->select(iter); 941 | } 942 | 943 | return false; 944 | } 945 | 946 | //handle SearchText change events 947 | void on_search_text_change(){ 948 | 949 | Glib::ustring search_text = p_search_text->get_text(); 950 | std::vector matches; 951 | std::unordered_map registry; 952 | 953 | //reset tree view store 954 | p_list_store->clear(); 955 | 956 | //if a character has been entered, begin search 957 | if(search_text.size()){ 958 | 959 | for(auto dir_iter = directories.begin(); 960 | dir_iter < directories.end(); 961 | ++dir_iter){ 962 | 963 | Glib::ustring directory_text = *dir_iter; 964 | 965 | 966 | DIR * p_directory = NULL; 967 | struct dirent * p_entry = NULL; 968 | 969 | //iterate directories 970 | if( (p_directory = opendir(directory_text.data())) == NULL){ 971 | std::cerr << "Error: Could not open directory " << directory_text << std::endl; 972 | continue; 973 | } 974 | 975 | while( (p_entry = readdir(p_directory)) != NULL ){ 976 | 977 | //verify file extension 978 | Glib::ustring filename = directory_text + "/" + p_entry->d_name; 979 | std::size_t found = filename.rfind(file_extension); 980 | 981 | if(found == std::string::npos){ 982 | //incorrect file extension found 983 | continue; 984 | } 985 | 986 | //push onto stack 987 | matches.push_back(SearchEntry()); 988 | SearchEntry * p_search_entry = &(matches[matches.size()-1]); 989 | p_search_entry->filename = p_entry->d_name; 990 | p_search_entry->directory = directory_text; 991 | 992 | //search file contents 993 | 994 | //open desktop file 995 | Glib::KeyFile file; 996 | try{ 997 | if(!file.load_from_file(filename)){ 998 | std::cerr << "Error: Could not open application file " << filename << std::endl; 999 | 1000 | //remove from stack 1001 | matches.pop_back(); 1002 | continue; 1003 | } 1004 | } 1005 | catch(const Glib::Error& ex){ 1006 | std::cerr << "Error: could not parse key file: " << filename << std::endl; 1007 | std::cerr << ex.what() << std::endl; 1008 | 1009 | //remove from stack 1010 | matches.pop_back(); 1011 | continue; 1012 | } 1013 | 1014 | //Glib::ArrayHandle groups = file.get_groups(); 1015 | 1016 | Glib::ustring group_name("Desktop Entry"); 1017 | 1018 | //check name 1019 | try{ 1020 | p_search_entry->name = file.get_locale_string( 1021 | group_name, Glib::ustring("Name") 1022 | ); 1023 | } 1024 | catch(const Glib::Error& ex){ 1025 | 1026 | //remove from stack 1027 | matches.pop_back(); 1028 | continue; 1029 | } 1030 | 1031 | p_search_entry->name = trim(p_search_entry->name); 1032 | 1033 | //verify entry doesn't already exist in registry 1034 | if( registry[ p_search_entry->name.data() ] ){ 1035 | 1036 | //remove from stack 1037 | matches.pop_back(); 1038 | continue; 1039 | } 1040 | 1041 | //check description 1042 | try{ 1043 | p_search_entry->description = file.get_locale_string( 1044 | group_name, Glib::ustring("Comment") 1045 | ); 1046 | } 1047 | catch(const Glib::Error& ex){ 1048 | p_search_entry->description = ""; 1049 | } 1050 | 1051 | //get exec 1052 | try{ 1053 | p_search_entry->exec = file.get_locale_string( 1054 | group_name, Glib::ustring("Exec") 1055 | ); 1056 | } 1057 | catch(const Glib::Error& ex){ 1058 | 1059 | //remove from stack 1060 | matches.pop_back(); 1061 | continue; 1062 | } 1063 | 1064 | //get icon 1065 | try{ 1066 | p_search_entry->icon = file.get_locale_string( 1067 | group_name, Glib::ustring("Icon") 1068 | ); 1069 | } 1070 | catch(const Glib::Error& ex){ 1071 | p_search_entry->icon = "applications-other"; 1072 | } 1073 | 1074 | if(default_browser.compare(trim(p_entry->d_name)) == 0 && !default_browser_icon.size()){ 1075 | default_browser_icon = p_search_entry->icon; 1076 | if(verbose) 1077 | std::cout << "Default browser set to: " << p_search_entry->icon << ":" << std::endl; 1078 | } 1079 | 1080 | //search name case insensitive 1081 | p_search_entry->position = p_search_entry->name.lowercase().find( search_text.lowercase() ); 1082 | 1083 | if(p_search_entry->position == Glib::ustring::npos){ 1084 | //match not found 1085 | 1086 | //remove from stack 1087 | matches.pop_back(); 1088 | continue; 1089 | } 1090 | 1091 | //add entry to registry 1092 | registry[ p_search_entry->name.data() ] = true; 1093 | } 1094 | 1095 | closedir(p_directory); 1096 | } 1097 | 1098 | //sort matches based on position 1099 | std::sort(matches.begin(), matches.end(), sort_position_matches); 1100 | 1101 | //resize to display limit 1102 | if(matches.size() > results_limit) 1103 | matches.resize(results_limit); 1104 | 1105 | //sort matches based on name 1106 | std::sort(matches.begin(), matches.end(), sort_name_matches); 1107 | 1108 | //add matched files to tree view model 1109 | for(auto iter = matches.begin(); iter != matches.end(); iter++){ 1110 | SearchEntry * p_entry = &(*iter); 1111 | Gtk::TreeModel::Row row = *(p_list_store->append()); 1112 | 1113 | if(p_entry->icon.size()){ 1114 | 1115 | Glib::RefPtr theme = Gtk::IconTheme::get_default(); 1116 | Glib::RefPtr icon; 1117 | int lookup_flags = Gtk::ICON_LOOKUP_USE_BUILTIN | Gtk::ICON_LOOKUP_FORCE_SIZE; 1118 | 1119 | try{ 1120 | 1121 | if(p_entry->icon.at(0) == '/'){ 1122 | //file lookup 1123 | try{ 1124 | icon = Gdk::Pixbuf::create_from_file(p_entry->icon.data()); 1125 | } 1126 | catch(Glib::FileError& ex){ 1127 | throw Gdk::PixbufError(Gdk::PixbufError::Code::FAILED, ex.what()); 1128 | } 1129 | } 1130 | else { 1131 | //icon lookup 1132 | try{ 1133 | icon = theme->load_icon(p_entry->icon, lookup_flags); 1134 | } 1135 | catch(Glib::Error& ex){ 1136 | throw Gdk::PixbufError(Gdk::PixbufError::Code::FAILED, ex.what()); 1137 | } 1138 | } 1139 | 1140 | } 1141 | catch(Gdk::PixbufError& ex){ 1142 | icon = theme->load_icon("applications-other", lookup_flags); 1143 | } 1144 | 1145 | row[autocomplete_columns.icon] = icon->scale_simple(24, 24, Gdk::INTERP_TILES); 1146 | } 1147 | 1148 | row[autocomplete_columns.name] = p_entry->name; 1149 | row[autocomplete_columns.description] = p_entry->description; 1150 | row[autocomplete_columns.data] = *p_entry; 1151 | } 1152 | 1153 | 1154 | p_autocomplete->set_cursor(Gtk::TreePath("0")); 1155 | } 1156 | else { 1157 | set_icon(get_theme_icon("discard")); 1158 | } 1159 | 1160 | //show/hide if results found 1161 | if(matches.size()){ 1162 | p_autocomplete_overlay->show(); 1163 | } 1164 | else{ 1165 | p_autocomplete_overlay->hide(); 1166 | } 1167 | 1168 | //show type specific icons 1169 | SearchEntryType search_type = get_search_entry_type(p_search_text->get_text()); 1170 | 1171 | if(search_type.is_ftp){ 1172 | set_icon(get_theme_icon("folder-remote")); 1173 | } 1174 | else if(search_type.is_url || search_type.is_search){ 1175 | if(default_browser_icon.size()) 1176 | set_icon(get_theme_icon(default_browser_icon)); 1177 | else 1178 | set_icon(get_theme_icon("web-browser")); 1179 | } 1180 | else if(search_type.is_command){ 1181 | set_icon(get_theme_icon("applications-other")); 1182 | } 1183 | else if(search_type.is_file){ 1184 | set_icon(get_theme_icon("text-x-generic")); 1185 | } 1186 | else if(search_type.is_directory){ 1187 | set_icon(get_theme_icon("folder")); 1188 | } 1189 | } 1190 | 1191 | //method for applying a css file 1192 | void apply_css(const Glib::ustring css_file){ 1193 | Glib::RefPtr p_css_provider = Gtk::CssProvider::create(); 1194 | Glib::RefPtr p_style_context = Gtk::StyleContext::create(); 1195 | p_css_provider->signal_parsing_error().connect( sigc::ptr_fun(&on_parsing_error) ); 1196 | 1197 | if(p_css_provider->load_from_path(css_file)){ 1198 | Glib::RefPtr screen = p_main_window->get_screen(); 1199 | p_style_context->add_provider_for_screen(screen, p_css_provider, GTK_STYLE_PROVIDER_PRIORITY_USER); 1200 | } 1201 | } 1202 | 1203 | //method for displaying command line help 1204 | void display_help(){ 1205 | std::cout << std::endl; 1206 | std::cout << PACKAGE_STRING << std::endl; 1207 | std::cout << std::endl; 1208 | std::cout << "Usage:" << std::endl; 1209 | std::cout << " " << PACKAGE_NAME << " [OPTION...]" << std::endl; 1210 | std::cout << std::endl; 1211 | std::cout << "Help Options:" << std::endl; 1212 | std::cout << " -h, --help\t\tShow help options" << std::endl; 1213 | std::cout << " -v, --verbose\t\tShow verbose text" << std::endl; 1214 | std::cout << std::endl; 1215 | } 1216 | 1217 | int main(int argc, char ** argv){ 1218 | 1219 | //get default web browser 1220 | //use: xdg-settings get default-web-browser 1221 | default_browser = passthru("xdg-settings get default-web-browser"); 1222 | 1223 | //iterate command line args 1224 | for(int i=0; imessage << std::endl; 1242 | return EXIT_FAILURE; 1243 | } 1244 | 1245 | //create application instance 1246 | p_application = Gtk::Application::create("com.collaboradev.xfce4-finder"); 1247 | 1248 | //create builder reference for glade 1249 | p_builder = Gtk::Builder::create(); 1250 | 1251 | //load glade 1252 | try{ 1253 | p_builder->add_from_string(GLADE_XML); 1254 | } 1255 | catch(const Glib::FileError& ex){ 1256 | std::cerr << "Glade FileError: " << ex.what() << std::endl; 1257 | return EXIT_FAILURE; 1258 | } 1259 | catch(const Glib::MarkupError& ex){ 1260 | std::cerr << "Glade Markup_error: " << ex.what() << std::endl; 1261 | return EXIT_FAILURE; 1262 | } 1263 | catch(const Gtk::BuilderError& ex){ 1264 | std::cerr << "Glade BuilderError: " << ex.what() << std::endl; 1265 | return EXIT_FAILURE; 1266 | } 1267 | 1268 | //retrieve pointer to main window 1269 | p_builder->get_widget("MainWindow", p_main_window); 1270 | 1271 | //retrive pointer to search text 1272 | p_builder->get_widget("SearchText", p_search_text); 1273 | p_search_text->signal_changed().connect( sigc::ptr_fun(&on_search_text_change) ); 1274 | p_search_text->signal_key_press_event().connect( sigc::ptr_fun(&on_search_text_key_press), false ); 1275 | p_search_text->signal_activate().connect( sigc::ptr_fun(&on_activate) ); 1276 | p_search_text->signal_icon_press().connect( sigc::ptr_fun(&on_search_text_icon_press) ); 1277 | 1278 | //retrieve pointer to search icon 1279 | p_builder->get_widget("SearchIcon", p_search_icon); 1280 | p_builder->get_widget("SearchArgumentsIcon", p_search_arguments_icon); 1281 | 1282 | //settings icon lookup 1283 | try{ 1284 | //Glib::RefPtr icon = get_theme_icon("applications-system"); 1285 | //p_search_text->set_icon_from_pixbuf(icon, Gtk::ENTRY_ICON_SECONDARY); 1286 | } 1287 | catch(Glib::Error& ex){} 1288 | 1289 | p_builder->get_widget("ArgumentsLayout", p_arguments_layout); 1290 | 1291 | p_builder->get_widget("SearchApplication", p_search_application); 1292 | p_search_application->signal_clicked().connect( sigc::ptr_fun(&on_search_application_clicked) ); 1293 | 1294 | //retrieve pointer to search arguments 1295 | p_builder->get_widget("SearchArguments", p_search_arguments); 1296 | p_search_arguments->signal_key_press_event().connect( sigc::ptr_fun(&on_search_arguments_key_press), false ); 1297 | p_search_arguments->signal_activate().connect( sigc::ptr_fun(&on_activate) ); 1298 | 1299 | //retrive pointer to autocomplete list view 1300 | p_builder->get_widget("Autocomplete", p_autocomplete); 1301 | 1302 | //retrive pointer to autocomplete overlay 1303 | p_builder->get_widget("AutocompleteOverlay", p_autocomplete_overlay); 1304 | p_autocomplete_overlay->hide(); 1305 | 1306 | //create list view store 1307 | p_list_store = Gtk::ListStore::create(autocomplete_columns); 1308 | p_autocomplete->set_model(p_list_store); 1309 | p_autocomplete->append_column("Icon", autocomplete_columns.icon); 1310 | p_autocomplete->append_column("Name", autocomplete_columns.name); 1311 | p_autocomplete->append_column("Description", autocomplete_columns.description); 1312 | 1313 | 1314 | p_autocomplete->signal_row_activated().connect( sigc::ptr_fun(&on_row_activated) ); 1315 | Glib::RefPtr p_tree_selection = p_autocomplete->get_selection(); 1316 | p_tree_selection->signal_changed().connect( sigc::ptr_fun(&on_tree_selection_changed) ); 1317 | 1318 | //add css support 1319 | Glib::ustring finder_directory = HOME_DIRECTORY + "/.config/xfce4/finder/"; 1320 | Glib::ustring css_file = finder_directory + theme + ".css"; 1321 | 1322 | if(file_exists(css_file)){ 1323 | apply_css(css_file); 1324 | } 1325 | else { 1326 | //install default CSS 1327 | if(!file_exists(finder_directory)) 1328 | mkdir(finder_directory.data(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); 1329 | 1330 | write_file(css_file, pretty_print_css(DEFAULT_CSS)); 1331 | apply_css(css_file); 1332 | } 1333 | 1334 | //use custom css 1335 | CustomCss css; 1336 | results_limit = css.results_property.get_value(); 1337 | p_main_window->resize(css.width_property.get_value(), css.height_property.get_value()); 1338 | p_main_window->set_position(Gtk::WIN_POS_CENTER_ALWAYS); 1339 | 1340 | //load default configuration settings 1341 | load_channel_settings(); 1342 | 1343 | //run main application 1344 | p_application->run(*p_main_window); 1345 | 1346 | xfconf_shutdown(); 1347 | 1348 | return EXIT_SUCCESS; 1349 | } 1350 | 1351 | 1352 | --------------------------------------------------------------------------------