├── .cirrus.yml ├── .github └── cirrus-ci.sh ├── .gitignore ├── AUTHORS ├── ChangeLog ├── LICENSE ├── Makefile ├── README.md ├── configure ├── configure.project ├── data ├── Makefile ├── gmrun.1 ├── gmrun.desktop ├── gmrun.png └── gmrunrc ├── gtkcompat.h ├── po ├── Makefile ├── POTFILES ├── es.po ├── fr.po ├── gmrun.pot ├── he.po └── zzpo.sh ├── src ├── Makefile ├── config_prefs.c ├── config_prefs.h ├── gtkcompletionline.c ├── gtkcompletionline.h ├── history.c ├── history.h └── main.c └── w_conf ├── gettext └── gtk /.cirrus.yml: -------------------------------------------------------------------------------- 1 | env: 2 | CIRRUS_CLONE_DEPTH: 1 3 | 4 | #freebsd-11-4 5 | #freebsd-12-3 6 | #freebsd-13-0 7 | freebsd_task: 8 | freebsd_instance: 9 | image_family: freebsd-13-0 10 | cpu: 1 11 | memory: 1GB 12 | install_script: 13 | - .github/cirrus-ci.sh freebsd 14 | build_script: 15 | - .github/cirrus-ci.sh 16 | # test_script: 17 | # - uname -a 18 | 19 | 20 | # catalina-base 21 | # big-sur-base 22 | # monterey-base 23 | macos_task: 24 | macos_instance: 25 | image: monterey-base 26 | cpu: 1 27 | memory: 1GB 28 | install_script: 29 | - .github/cirrus-ci.sh macos 30 | build_script: 31 | - .github/cirrus-ci.sh 32 | -------------------------------------------------------------------------------- /.github/cirrus-ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$1" = "freebsd" ] ; then 4 | #export REPO_AUTOUPDATE=NO 5 | pkg install -y devel/pkgconf x11-toolkits/gtk20 6 | exit $? 7 | fi 8 | 9 | if [ "$1" = "macos" ] ; then 10 | #export HOMEBREW_NO_AUTO_UPDATE=1 11 | # pkg-config is already installed 12 | brew install gtk+3 13 | exit $? 14 | fi 15 | 16 | # ============================================ 17 | 18 | uname -a 19 | 20 | ./configure \ 21 | && make \ 22 | && make install 23 | exit_code=$? 24 | 25 | for i in config.h config.log config.mk 26 | do 27 | echo " 28 | =============================== 29 | $i 30 | =============================== 31 | " 32 | cat ${i} 33 | done 34 | 35 | exit ${exit_code} 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | *.o 3 | compile 4 | config.guess 5 | config.h* 6 | config.log 7 | config.mk 8 | config.rpath 9 | config.status 10 | config.sub 11 | autom4te.cache 12 | aclocal.m4 13 | depcomp 14 | install-sh 15 | libtool 16 | ltmain.sh 17 | m4 18 | missing 19 | stamp-h1 20 | .deps 21 | .libs 22 | src/gmrun 23 | build-aux 24 | autoconf 25 | autoconf-m4 26 | config.h 27 | config.sh 28 | *.mo 29 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Initial author: Mihai Bazon 2 | 3 | Icon: Andrea Soragna - https://icon-icons.com/icon/gtk-execute/36132 4 | 5 | CVS: 6 | ---- 7 | 8 | andreas99 9 | Henning Schild 10 | Makoto Nokata 11 | mishoo 12 | Paweł Błaszczyk 13 | PRESFIL 14 | R. Tyler Croy 15 | Samuel Bauer 16 | sonofkojak 17 | wdlkmpx 18 | GreenLunar 19 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | ========== 2 | gmrun 1.4w 3 | ========== 4 | - some optimizations and tweaks/fixes 5 | - NLS support 6 | - make dialog only horizontally resizable 7 | - fix handling of lines containing ':' 8 | 9 | ========== 10 | gmrun 1.3w 11 | ========== 12 | - Replaced autotools with a custom configure script and makefiles 13 | users shouldn't notice any difference, the script is supposed to work the same way 14 | - gmrun --version 15 | 16 | ========== 17 | gmrun 1.2w 18 | ========== 19 | - Fix some issues / tweaks 20 | - New configure option: --enable-xdg 21 | 'follow xdf spec for configuration and history file location' 22 | 23 | 24 | ========== 25 | gmrun 1.1w 26 | ========== 27 | 28 | PRESFIL (1): 29 | Fix xterm -e handling with multiword queryes 30 | 31 | wdlkmpx (7): 32 | restore system(3) call as the default option to run commands 33 | main.c: ext_check(): escape spaces only if USE_XDG = 1 34 | rename USE_XDG to USE_GLIB_XDG 35 | gmrunrc: EXT syntax-> EXT:ext,etc = handler '%s' 36 | main.c: main() gtk3.4+: hide cmd line args from GApplication 37 | main.c (gtk3.4+): G_APPLICATION_NON_UNIQUE 38 | merge data/zrelease into autogen.sh .. 39 | 40 | 41 | ========== 42 | gmrun 1.0w 43 | ========== 44 | 45 | Highlights: 46 | - GTK 3 support (default / use --enable-gtk2 to compile the gtk2 port) 47 | - should work with GTK 2.12+ (or even older GTK versions) 48 | - fixed broken CTRL-R / CTRL-S 49 | - gmrun is now a C application 50 | - added desktop file and icon 51 | - added manual 52 | - changed license to ISC (approved by @mishoo) 53 | - support Glib XDG handling (set USE_XDG = 1 in gmrunrc) 54 | - keep support for custom extension and url handlers 55 | (the app is a bit more complex than it should because of this) 56 | - restored --geometry param (it's a bit different) 57 | - gmrunrc: Geometry = (read comments) 58 | (this probably doesn't work with wayland and other protocols) 59 | 60 | changes since 0.9.5w 61 | 62 | Samuel Bauer (6): 63 | parse_tilda(): use only glib 64 | removed quote_string: use glib regex 65 | removed compilation warnings 66 | Cosmetic changes 67 | renamed main window 68 | gtkcompletionline.cc: don't use `goto` in generate_dir_list() 69 | 70 | mazes_80 (1): 71 | GtkCList to GtkTreeview 72 | 73 | wdlkmpx (124): 74 | update README.markdown (a bit) 75 | gtk_signal_* -> g_signal_* / GTK_OBJECT -> GTK_WIDGET 76 | Replace GDK_ with GDK_KEY_ 77 | minimum gtk version = 2.24.0 78 | gtkcompletionline.cc: use gtk-widget-get-allocation 79 | replace gtk_timeout_add() with g_timeout_add() [not sure what I'm doing] 80 | Replace GTK_CHECK_CAST with G_TYPE_CHECK_INSTANCE_CAST 81 | Replace gtk_widget_set_usize() with gtk_widget_set_size_request() 82 | gtkcompletionline.cc: comment out gtk_window_set_policy() 83 | WIP gtk3 support [--enable-gtk3] 84 | gtk2/3: remove titlebar from dialog (is this a good idea?) 85 | gtk2/3: set_info_text_color() [unify GtkStyle functions] 86 | update readme 87 | gmrunrc: better defaults 88 | gtk2/3: support "navigating" history with mouse wheel 89 | indentation style: use tabs 90 | Silence "GtkDialog mapped without a transient parent. This is discouraged." 91 | update ChangeLog & AUTHORS 92 | remove gmrun.glade 93 | update README a bit 94 | fix setting dialog border, name and title.. 95 | gmrunrc: remove unused options 96 | reimplement `--geometry WxH+X+Y` (cli param) [without libpopt] 97 | main.cc: properly free GError and GOptionContext... 98 | support gtk >= 2.14 99 | update autogen.sh 100 | gtkcompletionline.cc: hack for glibc < 2.10 (dirent) 101 | src: use gtk_box_new() 102 | src: (gtkcompat) use gtk_widget_set_halign/valign 103 | src: (gtkcompat) use gtk_widget_set_margin_start/end 104 | main.cc: (search) fix "Source ID was not found when attempting to remove it" 105 | gtkcompletionline.cc: avoid on_key_press callback when key is released 106 | completion: don't create extra column 107 | completion: use gtk_tree_selection_select_iter() 108 | completion: attemp to silence Gtk-WARNING **: Allocating size to Window ... 109 | completion: use gtk_tree_model_iter_n_children() to get row count 110 | gtk_completion_line_class_init(): cosmetic changes 111 | main.cc: main_vbox 112 | main.cc: gtk_widget_show_all (dialog) 113 | use G_OBJECT with g_signal_* 114 | run_the_command: use g_shell_parse_argv() and g_spawn_async() 115 | set_info_text_color: optimize a bit 116 | window icon: use 'system-run' or 'gtk-execute' 117 | remove src/main.h 118 | update gtkcompat.h 119 | gtkcompletionline.cc: use G_DEFINE_TYPE_EXTENDED() 120 | gtk_completion_line_init: 'self' instead of 'object' 121 | add gmrun.desktop / gmrun.png 122 | main.cc: gmrun_exit() 123 | main.cc: gmrun_activate() / parse_command_line() 124 | new configuration option: Geometry 125 | completion: TabTimeout: fix "Source ID was not found when attempting to remove it" 126 | *** handle configuration and history with C code *** 127 | completion: (search) cl->hist_word is now a char array 128 | src/main.cc: get rid of 'struct gigi' 129 | completionline.cc: fix Pango-CRITICAL **: pango_layout_get_cursor_pos ... 130 | main.cc: (history) show search text next to "Search:" label 131 | history.c: improvements 132 | completionline: remove handling of ctrl-e/g (what's this?) 133 | completionline.cc: make CTRL-R work as intended.. 134 | main.cc: fix calls to config_get_int() 135 | ext_check/url_check: don't include handler in history entry 136 | option to use glib XDG handling (disabled by default) 137 | fix build with ancient glib 138 | add data/zmtrace.sh 139 | *** gtkcompletionline.c: use C code *** 140 | *** gmrun is now a C application *** 141 | configure.ac: enable many gcc warnings 142 | gtkcompletionline.c: don't use gtk_tree_model_sort_new_with_model() 143 | gtkcompletionline.c: use spaces 144 | main.c: use spaces 145 | complete_line(): optimize a bit / fix some memory leaks 146 | gtkcompletionline.c: add debug code 147 | gtkcompletionline.c: remove complete_common() 148 | gtkcompletionline.c: fix complete_from_list / set_words / complete_line 149 | gtkcompletionline.c: avoid triggering on_cursor_changed() twice 150 | merge generate_completion_from_execs/dirlist() into complete_line() 151 | gtkcompletionline.c: improve logic to free glists 152 | tab_pressed(): don't call complete_line() if completion window exists 153 | gtkcompletionline.c: fix memory leak in complete_from_list() 154 | get_words(): add empty string if glist is empty 155 | gtkcompletionline: remove unused cl->first_key 156 | gtkcompletionline.c: fix segfault with gtk3 157 | gtkcompletionline.c: properly sort completion list 158 | gtkcompletionline: set/unset prefix in generate_execs_list/dirlist() 159 | gtkcompletionline.c: use only cl->cmpl to get filelist 160 | gtkcompletionline.c: remove unused GEN_COMP... 161 | src/history.c: fix memory leaks 162 | generate_dir_list(): don't use GString 163 | gtkcompletionline.c: simplify my_alphasort() 164 | gtkcompletionline.c: (scandir) use standard alphasort 165 | complete_line: items > 1: always use 1st item from GtkTreeView 166 | complete_from_list: fix memory leak 167 | update README a bit 168 | Relicense project to ISC 169 | gtkcompletionline.c: set completion window transient for main window 170 | main.c: gmrun [text] 171 | add gmrun.1 (manual) 172 | gtkcompletionline: get rid of cl->cmpl 173 | completionline.c: CTRL-S/CTRL-R: search matches anywhere in strings 174 | gtkcompletionline.c: restore bash-like search for CTRL-S/R 175 | config_prefs.c: use free, strdup, calloc.. 176 | gtkcompletionline.c: (scandir) don't use alphasort 177 | gtkcompletionline.c: (scandir) don't reverse GList 178 | gtkcompat.h 2020-10-11 179 | "!": history search mode that matches only the start of strings 180 | update gtkcompat 181 | search_off(): properly clear cl->hist_word .. 182 | CTRL-p: fetch the previous command from the history list 183 | CTRL-n: Fetch the next command from the history list 184 | CTRl-g (search mode): cancel search and clear text entry 185 | renamed: COPYING -> LICENSE 186 | remove .travis.yml 187 | move gmrunrc & zrelease to data/ dir 188 | update autogen.sh 189 | update README 190 | configure.ac: disable deprecated gtk2 stuff 191 | fix gcc9 warnings 192 | configure.ac: default to GTK3, fall back to GTK2 193 | update README 194 | history.c: use g_list_delete_link() 195 | gtkcompletionline: tweaks / don't declare var inside for loop.. 196 | use GtkApplication for GTK3 197 | add Makefile.true 198 | 199 | 200 | 201 | ============ 202 | gmrun 0.9.5w 203 | ============ changes since 0.9.4w: 204 | 205 | Henning Schild (2): 206 | gtk: replace deprecated gtk_type_new and _unique 207 | gtk: do not select any text to keep clipboard clean 208 | 209 | unknown (1): 210 | Handle more than 256 characters in the history 211 | 212 | wdlkmpx (3): 213 | add .travis.yml 214 | configure.ac: no need to check for glib 215 | gmrun 0.9.5w 216 | 217 | 218 | ============ 219 | gmrun 0.9.4w 220 | ============ changes since 0.9.2: 221 | 222 | Makoto Nokata (2): 223 | Rewritten feature which places gmrun window on monitor 224 | Updated default gmrun config 225 | 226 | Paweł Błaszczyk (2): 227 | Fix for "undefined reference to symbol 'XQueryPointer'" 228 | Use GtkDialog instead of GtkWindow. 229 | 230 | R. Tyler Ballance (1): 231 | Correct a few compiler errors due to missing imports under gcc4 232 | 233 | R. Tyler Croy (7): 234 | Remove generated config.h.in 235 | Add simplistic git ignore file 236 | Fix bug with running quoted commands in the terminal 237 | Correct improper function prototype for my_alphasort. 238 | Rename the readme for extra special GitHub consideration 239 | Center the window on the screen with the mouse pointer 240 | Add a README symlink for kicks 241 | 242 | wdlkmpx (15): 243 | clean up/update build system 244 | always use libc's system() function to run commands 245 | configure.ac: remove unneded AM_PROG_AR 246 | bump version to 0.9.3 247 | use gtk_window_set_position() 248 | remove commented out code 249 | remove libpopt dep / --geometry param 250 | remove gmrun.spec.in... 251 | configure.ac: update AM_INIT_AUTOMAKE 252 | update .gitignore 253 | remove unused screen_contains_pointer() 254 | add 'make-release' script 255 | configure.ac: remove unused XQueryPointer stuff 256 | simplify / use @sysconfdir@ for gmrunrc 257 | bump version to 0.9.4w 258 | 259 | 260 | =========== 261 | gmrun 0.9.2 262 | =========== 263 | 264 | andreas99 (3): 265 | - merged gtk-2 branch to main branch 266 | - UTF-8 support - libpopt support 267 | changes for 0.9.2 268 | 269 | mishoo (73): 270 | Initial revision 271 | These files are generated. 272 | little bug fixed in gtkcompletionline.cc (the prev. version works with an older compiler) 273 | Removed useless files 274 | jump to new version 275 | removed unconditional debug output. 276 | added drop-down list (nice) 277 | switch to new version (added drop-down list functionality) 278 | *** empty log message *** 279 | some nice changes - fixed bug, completion window is displayed even if we're not completing the last word. Also, cursor now marks two words for completion, even if there's no space between. 280 | some bugs fixes, support for CTRL-ENTER starts command in terminal. 281 | new version 282 | Added support for configuration file 283 | fix compilation prob. 284 | configure -- switch to new version gtkcompletionline -- much smoother completion.. select completed-text.. etc, really better.. prefs -- added support for $(Variable) expansion main -- added 3 config options: Top, Left (opper-left corner) 285 | End, Escape -- clear the selection 286 | -- swithced to new version -- more intuitive tab completion (hit tab only once, and the list is shown) -- fixed bug in prefs (made it recursive, so can replace more than one variable on a line) -- ... and other nice things :) hopefully 287 | fixed bug in gtkcompletionline.cc -- crashed when no completion found.. 288 | case-insensitive std::string-like class 289 | *** empty log message *** 290 | default configuration file 291 | added support for default configuration file 292 | switched to new version 293 | revert to normal version (0.5.3-2 not good for building rpms..) 294 | [main.cc] set widget names [history.cc] history size moved to config file (not hard coded) [gmrunrc] set history size in default config file 295 | changed gdk_window_set_position to gtk_widget_set_uposition -- got rid of flicker.. :) 296 | updated 297 | *** empty log message *** 298 | trying to add CTRL-R feature from bash... 299 | updated code to support backward / forward searching through history using CTRL-R / CTRL-S as in bash / Emacs. 300 | fixed bug in CTRL-R/S searching. added code for backspace in history search mode. 301 | switched to new version (0.5.4) 302 | search phrase gets nice selected :) 303 | various bug fixes 304 | bug fixing / improvement.. 305 | *** empty log message *** 306 | rpm spec file 307 | hmm... 308 | added "!" to complete from history with the last command beginning with the entered text 309 | cosmetic... 310 | updated 311 | new version 312 | - bug fixes - nice window, no titlebar 313 | fixed bug 314 | added URL-handling ability (check the config file) 315 | modified for URL-s also, added some small comments to make content clearer for user. 316 | changed version 317 | README -- improved documentation. configure -- hmm... this should be removed from CVS, but... later. 318 | removed. moved to gmrun.spec.in -- to be generated by configure. 319 | generates gmrun.spec when configure is run 320 | support for creating gmrun.spec 321 | fixed bug 322 | programs are now executed with exec*p 323 | fixed bug 324 | another bug fix... :) 325 | changes related to generation of gmrun.spec 326 | fixed some bugs; new features: 327 | updated for new configuration parameters 328 | updated for the new release 329 | fixed bug 330 | fixed bugs, new configuration parameter: ShowLast (1 / 0) to determine if gmrun should display the last history item as selected by default. (maybe some users don't like that...) 331 | added nice frame around the completion window 332 | *** empty log message *** 333 | included some patches from Michal Politowski (thanks!); 334 | bug fixes: - file names containing white-space now (hopefully) behave correctly - others, I don't remember 335 | they're kind of old, but.. 336 | these are automatically generated, duh.. 337 | preparing for a new version 338 | finally, I write news :) 339 | new bugfix version 340 | bugfixes: END/HOME (C-E/C-A) behavior is now much better, some code cleanup (mainly reindentation :)), removed some warnings, etc. Major bug fixed is that previously (with 0.8) you could not edit a .doc file with soffice if you had .doc extension handler set as AbiWord :) 341 | 0.8.1 news 342 | bug fixed (the "e"/"E" character doesn't clear selection) 343 | 344 | sonofkojak (1): 345 | Added support for STLPort. 346 | 347 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Mihai Bazon 2 | 3 | Permission to use, copy, modify, and/or distribute this software 4 | for any purpose with or without fee is hereby granted, provided that 5 | the above copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALLWARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES 9 | OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 10 | BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR 11 | ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 12 | WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 13 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | SUBDIRS = data po src 3 | 4 | DISTCLEAN_FILES = 5 | 6 | # =========================================================== 7 | 8 | include config.mk 9 | 10 | DISTCLEAN_FILES += config.h config.mk config.log config.sh 11 | 12 | MAKEFLAGS += --no-print-directory 13 | 14 | all: 15 | @for dir in ${SUBDIRS} ; do \ 16 | echo "$(MAKE): Entering directory [$${dir}]"; \ 17 | $(MAKE) -C $${dir} $@ || exit 1; \ 18 | echo "$(MAKE): Leaving directory [$${dir}]"; \ 19 | done 20 | 21 | strip: 22 | @for dir in ${SUBDIRS} ; do \ 23 | echo "$(MAKE): Entering directory [$${dir}]"; \ 24 | $(MAKE) -C $${dir} $@ || exit 1; \ 25 | echo "$(MAKE): Leaving directory [$${dir}]"; \ 26 | done 27 | 28 | clean: 29 | @for dir in ${SUBDIRS} ; do \ 30 | echo "$(MAKE): Entering directory [$${dir}]"; \ 31 | $(MAKE) -C $${dir} $@ || exit 1; \ 32 | echo "$(MAKE): Leaving directory [$${dir}]"; \ 33 | done 34 | 35 | distclean: 36 | @for dir in ${SUBDIRS} ; do \ 37 | echo "$(MAKE): Entering directory [$${dir}]"; \ 38 | $(MAKE) -C $${dir} $@ || exit 1; \ 39 | echo "$(MAKE): Leaving directory [$${dir}]"; \ 40 | done 41 | -rm -f $(DISTCLEAN_FILES) 42 | 43 | install: 44 | @for dir in ${SUBDIRS} ; do \ 45 | echo "$(MAKE): Entering directory [$${dir}]"; \ 46 | $(MAKE) -C $${dir} $@ || exit 1; \ 47 | echo "$(MAKE): Leaving directory [$${dir}]"; \ 48 | done 49 | 50 | install-strip: 51 | @for dir in ${SUBDIRS} ; do \ 52 | echo "$(MAKE): Entering directory [$${dir}]"; \ 53 | $(MAKE) -C $${dir} $@ || exit 1; \ 54 | echo "$(MAKE): Leaving directory [$${dir}]"; \ 55 | done 56 | 57 | uninstall: 58 | @for dir in ${SUBDIRS} ; do \ 59 | echo "$(MAKE): Entering directory [$${dir}]"; \ 60 | $(MAKE) -C $${dir} $@ || exit 1; \ 61 | echo "$(MAKE): Leaving directory [$${dir}]"; \ 62 | done 63 | 64 | check: 65 | 66 | distcheck: 67 | 68 | installcheck: 69 | 70 | dist: 71 | sh configure release dist 72 | 73 | .PHONY: subdirs $(SUBDIRS) 74 | 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GMRUN 2 | ----- 3 | A run utiliy featuring a slim design and bash style auto-completion. 4 | 5 | Features 6 | --------- 7 | 8 | * Tilda completion (~/ <==> $HOME/) 9 | 10 | * Completion works for separate words (e.g. you can type em which 11 | turns to emacs, type a SPACE, and write the file you want to open using 12 | completion). 13 | 14 | * Configuration file: ~/.gmrunrc or /etc/gmrunrc. 15 | Check one of them, configuration is very simple. From that file you 16 | can change window position and width, history size, terminal, URL 17 | handlers, etc. 18 | 19 | * CTRL-Enter runs the command in a terminal. 20 | * CTRL-Enter without any command starts a new terminal. 21 | 22 | * History is maintained in the file "~/.gmrun_history". 23 | 24 | * CTRL-R to search backwards through history. 25 | * CTRL-S to search forward through history. 26 | * "!" enters a special search mode, matching only the start of strings. 27 | -- Esc to cancel search (only once). 28 | -- CTRL-G to cancel search and clear the text entry 29 | 30 | * URL handlers allowing you to enter lines like "http://www.google.com" 31 | to start your favorite browser on www.google.com. 32 | The URL-s are configurable from the configuration file. 33 | 34 | * Extension handlers. Basically you can run, for instance, 35 | a ".txt" file, assuming that you have configured a handler for it 36 | in the configuration file. 37 | 38 | 39 | Requirements 40 | ------------- 41 | 42 | * GTK 2/3 43 | 44 | 45 | Compilation, installation 46 | -------------------------- 47 | 48 | ./configure --prefix=/usr --sysconfdir=/etc 49 | make 50 | make install 51 | 52 | By default it will use the GTK3 ui if it's available. 53 | 54 | Pass `--enable-gtk2` to `./configure` to build the gtk2 ui 55 | 56 | Optionally you can configure your window manager to call gmrun 57 | with WinKey + R or something. 58 | 59 | 60 | Tips and tricks 61 | --------------- 62 | 63 | 1. Everything that doesn't start with "/" or "~" gets completed from $PATH 64 | environment var. More exactly, all files from $PATH directories are 65 | subjects to completion. 66 | 67 | Pressing TAB once when no text is entered opens the completion window, 68 | which will contain ALL files under $PATH. 69 | 70 | 2. For instance you use TAB to complete from "nets" to "netscape-navigator". 71 | A small window appears, allowing you to select from: 72 | 73 | - netscape 74 | - netscape-navigator 75 | - netstat 76 | 77 | That is because all these executables have the same prefix, "nets". 78 | 79 | You can use UP / DOWN arrows to select the right completion. 80 | You can use CTRL-P / CTRL-N or TAB to select the right completion. 81 | 82 | 3. - ESC closes the completion window, leaving the selected text in the entry. 83 | - HOME / END - the same, but clears the selection. 84 | - SPACE - the same, but clears the selection and appends one space. 85 | - Pressing ENTER (anytime) runs the command that is written in the entry. 86 | - Pressing CTRL+Enter (anytime) runs the command in a terminal (check your 87 | configuration file). But if the entry is empty (no text is present, or 88 | only whitespaces) then a fresh terminal will be started. 89 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is free and unencumbered software released into the public domain. 3 | # For more information, please refer to 4 | # 5 | 6 | if ! [ -f Makefile ] ; then 7 | echo 'Main Makefile is missing, cannot continue' 8 | exit 1 9 | fi 10 | 11 | if ! cd $(dirname "$0") ; then 12 | exit 1 13 | fi 14 | 15 | apps_to_check='' 16 | extra_env_vars='' 17 | make_extra_flags='' # config_mk_extra 18 | config_h_extra='' 19 | config_sh_extra='' 20 | extra_configure_opts='' 21 | config_h_have= 22 | config_mk_flags= 23 | w_files_in= 24 | sysroot= 25 | 26 | 27 | w_new_option() # $1=opt [$2=env_variable] 28 | { 29 | w_opts_configure="$w_opts_configure $1" 30 | w_opts_check="$w_opts_check $1" 31 | if [ -n "$2" ] ; then 32 | extra_env_vars="$extra_env_vars $2" 33 | fi 34 | } 35 | 36 | w_new_option_with() 37 | { 38 | extra_packages_opts="$extra_packages_opts $1" 39 | w_opts_check="$w_opts_check $1" 40 | } 41 | 42 | 43 | if [ -f configure.project ] ; then 44 | . ./configure.project 45 | fi 46 | 47 | if [ -z "$PROJECT_TYPE" ] ; then 48 | PROJECT_TYPE="C++" 49 | fi 50 | 51 | #========================================================== 52 | 53 | PACKAGE_NAME="${PACKAGE}" 54 | PACKAGE_STRING="${PACKAGE} ${VERSION}" 55 | PACKAGE_TARNAME="${PACKAGE}" 56 | PACKAGE_BUGREPORT="${PACKAGE_URL}" 57 | PACKAGE_VERSION="${VERSION}" 58 | 59 | #========================================================== 60 | # ./configure release [xz|gz|lz|bz2|zst] 61 | 62 | if [ "$1" = "release" ] ; then 63 | srcdir=$(pwd) 64 | outdir=${PACKAGE}-${VERSION} 65 | cd .. 66 | rm -rf ${outdir} 67 | if [ "$2" = "dist" ] ; then 68 | # force creation of tarfile, copy the whole dir 69 | shift 70 | shift 71 | cp -rf ${srcdir} ${outdir} 72 | cd ${outdir} 73 | git clean -dfx 74 | rm -rf .* 75 | cd .. 76 | else 77 | if command -v git >/dev/null; then 78 | printf "Run 'git clean -dfx'? [y/N]: " 79 | read anzwer 80 | case $anzwer in y|Y) 81 | cd ${srcdir} 82 | echo '# git clean -dfx' 83 | git clean -dfx 84 | cd .. 85 | ;; 86 | esac 87 | fi 88 | mkdir -p ${outdir} 89 | cp -rf ${srcdir}/* ${outdir}/ 90 | fi 91 | #-- 92 | echo "# tar -vcf ${outdir}.tar ${outdir}" 93 | tar -vcf ${outdir}.tar ${outdir} 94 | comp=$2 95 | if [ -z "$comp" ] ; then 96 | comp=xz 97 | fi 98 | case $comp in 99 | gz) cmd=gzip ;; 100 | lz) cmd=lzip ;; 101 | bz2) cmd=bzip2 ;; 102 | zst) cmd=zstd ;; 103 | *) cmd=${comp} ;; 104 | esac 105 | rm -f ${outdir}.tar.${comp} 106 | echo "# ${cmd} ${outdir}.tar" 107 | if ${cmd} ${outdir}.tar ; then 108 | echo "*** ${outdir}.tar.${comp} has been created..." 109 | fi 110 | rm -f ${outdir}.tar 111 | exit 112 | fi 113 | 114 | # w_conf/gettext: ./configure po|pot 115 | 116 | #========================================================== 117 | 118 | exit_error() { 119 | test "$1" && echo "$@" 120 | if [ -f config.log ] ; then 121 | echo "See config.log, there might be some clues there" 122 | fi 123 | rm -f config.h config.mk 124 | exit 1 125 | } 126 | 127 | 128 | check_command() # 1: finds $1 in $PATH 129 | { 130 | if [ -z "$xxpath" ] ; then 131 | xxpath="$(echo "$PATH" | tr ':' ' ')" 132 | fi 133 | for xcmdx in $@ 134 | do 135 | case "${xcmdx}" in /*) 136 | # a full path, check if file is executable 137 | if [ -x "${xcmdx}" ] ; then 138 | echo "${xcmdx}" 139 | return 0 140 | fi 141 | continue 142 | ;; 143 | esac 144 | #-- 145 | for zpathz in ${xxpath} 146 | do 147 | if [ -x "${zpathz}/${xcmdx}" ] ; then 148 | echo "${zpathz}/${xcmdx}" 149 | return 0 150 | fi 151 | done 152 | done 153 | return 1 154 | } 155 | 156 | 157 | set_cc() 158 | { 159 | CC_IS_FULL_PATH='' 160 | case $PROJECT_TYPE in 161 | C|C++) okw=1;; 162 | *) [ -z "$CC" ] && CC="cc" 163 | return ;; 164 | esac 165 | case $CC in /*) 166 | CC_IS_FULL_PATH=1 ;; 167 | esac 168 | GCC="no" 169 | if [ -n "$CC" ] ; then 170 | zz_list="$CC" 171 | else 172 | zz_list="cc gcc clang" 173 | fi 174 | printf "Checking for C compiler... " 175 | ZCC="$(check_command ${zz_list})" 176 | if test -z "$ZCC" ; then 177 | exit_error "$CC not found" 178 | fi 179 | CC="$ZCC" 180 | echo "$CC" 181 | if [ -n "$CC" ] && [ -z "$CC_IS_FULL_PATH" ] ; then 182 | CC="$(basename $CC)" # not a full path, keep binary name 183 | fi 184 | } 185 | 186 | 187 | set_cxx() 188 | { 189 | CXX_IS_FULL_PATH='' 190 | if [ "$PROJECT_TYPE" != "C++" ] ; then 191 | [ -z "$CXX" ] && CXX="c++" 192 | return 193 | fi 194 | case $CXX in /*) 195 | CXX_IS_FULL_PATH=1 ;; 196 | esac 197 | #-- 198 | printf "Checking for C++ compiler... " 199 | if [ -n "$CXX" ] ; then 200 | zz_list="$CXX" 201 | else 202 | zz_list="c++ g++ clang++" 203 | fi 204 | ZXX="$(check_command ${zz_list})" 205 | if test -z "$ZXX" ; then 206 | if [ -z "$CXX" ] ; then 207 | echo "not found" # not a fatal error 208 | else 209 | exit_error "$CXX not found" 210 | fi 211 | fi 212 | CXX="$ZXX" 213 | echo "$CXX" 214 | if [ -n "$CXX" ] && [ -z "$CXX_IS_FULL_PATH" ] ; then 215 | CXX="$(basename $CXX)" # not a full path, keep binary name 216 | fi 217 | } 218 | 219 | 220 | set_w_pkg_config() 221 | { 222 | printf "Checking for pkg-config... " 223 | W_PKG_CONFIG='' 224 | if [ -n "${PKG_CONFIG}" ] ; then 225 | W_PKG_CONFIG="$(check_command ${PKG_CONFIG})" 226 | if [ -n "$W_PKG_CONFIG" ] ; then 227 | echo "$W_PKG_CONFIG (environment)" 228 | return 0 229 | fi 230 | fi 231 | W_PKG_CONFIG="$(check_command pkg-config pkgconf)" 232 | if [ -n "$W_PKG_CONFIG" ] ; then 233 | echo "$W_PKG_CONFIG [$($W_PKG_CONFIG --version 2>/dev/null)]" 234 | else 235 | echo "no" 236 | echo "WARNING: missing pkg-config: this a potentially fatal error" 237 | fi 238 | } 239 | 240 | 241 | run_pkg_config() 242 | { 243 | if [ -n "$static_link" ] ; then 244 | pkgconfig_s='--static' 245 | else 246 | pkgconfig_s='' 247 | fi 248 | if test -n "$W_PKG_CONFIG" ; then 249 | ${W_PKG_CONFIG} ${pkgconfig_s} "$@" 250 | else 251 | pkg-config ${pkgconfig_s} "$@" 252 | fi 253 | } 254 | 255 | w_pkgconfig_run() 256 | { 257 | run_pkg_config "$@" 258 | } 259 | 260 | 261 | w_pkgconfig_check() # $1= $2=[min-version] 262 | { 263 | # returns 0=ok, use pkg-config 264 | pc_pkg="$1" 265 | pc_pkg_min_ver="$2" 266 | if [ -n "$pc_pkg_min_ver" ] ; then 267 | printf "Checking for %s >= %s... " "${pc_pkg}.pc" "$pc_pkg_min_ver" 268 | else 269 | printf "Checking for %s... " "${pc_pkg}.pc" 270 | fi 271 | if [ -z "$W_PKG_CONFIG" ] ; then 272 | echo "no (!!pkg-config is missing!!)" 273 | return 1 274 | fi 275 | #-- 276 | if [ -n "$pc_pkg_min_ver" ] ; then 277 | run_pkg_config ${pc_pkg} --exists --atleast-version=${pc_pkg_min_ver} 278 | else 279 | run_pkg_config ${pc_pkg} --exists 280 | fi 281 | xret=$? 282 | if [ $xret -eq 0 ] ; then 283 | echo "yes [$(run_pkg_config ${pc_pkg} --modversion)]" 284 | else 285 | if [ -n "$pc_pkg_min_ver" ] ; then 286 | xxzzfound=$(run_pkg_config ${pc_pkg} --modversion 2>/dev/null) 287 | if [ -n "$xxzzfound" ] ; then 288 | echo "no [found $xxzzfound]" 289 | else 290 | echo "no" 291 | fi 292 | else 293 | echo "no" 294 | fi 295 | fi 296 | return $xret 297 | } 298 | 299 | 300 | w_compile_c_code() # $1:code $2:extra-CFLAGS $3:extra-LDFLAGS 301 | { 302 | if [ -z "$1" ] ; then 303 | exit_error 'w_compile: code to compile is an empty string' 304 | fi 305 | conftest=.wxconftest$$ 306 | printf "$1" > ${conftest}.c 307 | printf "\n$1" >> config.log 308 | echo "$CC $CFLAGS $2 ${conftest}.c -o ${conftest}.o $LDFLAGS $3" >>config.log 309 | $CC $CFLAGS $2 ${conftest}.c -o ${conftest}.o $LDFLAGS $3 2>>config.log 310 | zzzzretxx=$? 311 | rm -f ${conftest}.c ${conftest}.o 312 | return ${zzzzretxx} 313 | } 314 | 315 | 316 | w_check_header_and_libs() # 1:
2-:[libs] is optional 317 | { 318 | xheaderx=$1 319 | shift 320 | xlibsx=$@ 321 | xretval=1 322 | if [ -n "$xlibsx" ] ; then 323 | printf "Checking for ${xheaderx} + ${xlibsx}... " 324 | else 325 | printf "Checking for ${xheaderx}... " 326 | fi 327 | ccode="#include 328 | #include 329 | #include 330 | #include <${xheaderx}> 331 | int main (int argc, char **argv) { return 0; } 332 | " 333 | w_compile_c_code "$ccode" "" "${xlibsx}" 334 | if [ $? -eq 0 ] ; then 335 | w_headers="$w_headers ${xheaderx}" 336 | echo "yes" 337 | xretval=0 338 | else 339 | echo "no" 340 | xretval=1 341 | fi 342 | return ${xretval} 343 | } 344 | 345 | 346 | w_check_header_and_libs_required() 347 | { 348 | zzheaderzz=$1 349 | if [ -z "$2" ] ; then 350 | exit_error "w_check_header_and_libs_required: 2nd paramater is required" 351 | fi 352 | shift 353 | zzlibszz=$@ 354 | w_check_header_and_libs ${zzheaderzz} ${zzlibszz} 355 | if [ $? -eq 0 ] ; then 356 | LIBS="$LIBS ${zzlibszz}" 357 | else 358 | exit_error "ERROR: ${zzheaderzz} + ${zzlibszz} is required" 359 | fi 360 | } 361 | 362 | w_check_headers() 363 | { 364 | for zzheaderzz in $@ 365 | do 366 | w_check_header_and_libs ${zzheaderzz} 367 | done 368 | } 369 | 370 | w_check_headers_required() 371 | { 372 | for zzheaderzz in $@ 373 | do 374 | w_check_header_and_libs ${zzheaderzz} 375 | if [ $? -ne 0 ] ; then 376 | exit_error "ERROR: ${zzheaderzz} is required" 377 | fi 378 | done 379 | } 380 | 381 | 382 | w_check_cflag() 383 | { 384 | printf "Checking if $1 is supported... " 385 | c_code='int main (int argc, char **argv) { return 0; }' 386 | w_compile_c_code "$ccode" ${1} 387 | if [ $? -eq 0 ] ; then 388 | echo "yes" 389 | return 0 390 | else 391 | echo "no" 392 | return 1 393 | fi 394 | } 395 | 396 | 397 | w_check_command() # sets $wtmp_cmd 398 | { 399 | printf "Checking for $1... " 400 | wtmp_cmd="$(check_command "$1")" 401 | if [ -n "$wtmp_cmd" ] ; then 402 | echo "$wtmp_cmd" 403 | return 0 404 | else 405 | echo "no" 406 | return 1 407 | fi 408 | } 409 | 410 | w_check_commands() 411 | { 412 | for wcmdw in $@ ; do 413 | w_check_command ${wcmdw} 414 | done 415 | } 416 | 417 | w_check_commands_required() 418 | { 419 | for wcmdw in $@ ; do 420 | w_check_command ${wcmdw} 421 | if [ $? -ne 0 ] ; then 422 | exit_error "ERROR: $wcmdw is required" 423 | fi 424 | done 425 | } 426 | 427 | 428 | w_check_command_find_first() # sets $wtmp_cmd 429 | { 430 | wtmp_cmd='' 431 | for wcmdw in $@ 432 | do 433 | w_check_command ${wcmdw} 434 | if [ $? -eq 0 ] ; then 435 | return 436 | fi 437 | done 438 | wtmp_cmd='' 439 | return 1 440 | } 441 | 442 | 443 | w_prefer_gnu_make() # param: require 444 | { 445 | printf "Checking for GNU make... " 446 | if [ -n "$MAKE" ] && "$MAKE" --version 1>/dev/null 2>&1 ; then 447 | echo "$MAKE (environment)" 448 | W_GNU_MAKE="MAKE=$MAKE" 449 | elif make --version 1>/dev/null 2>&1 ; then 450 | echo "make" 451 | W_GNU_MAKE='MAKE=make' 452 | elif gmake --version 1>/dev/null 2>&1 ; then 453 | echo "gmake" 454 | W_GNU_MAKE='MAKE=gmake' 455 | else 456 | echo "no" 457 | if [ -n "$1" ] ; then 458 | exit_error "ERROR: GNU make is required" 459 | fi 460 | fi 461 | } 462 | 463 | w_require_gnu_make() 464 | { 465 | w_prefer_gnu_make require 466 | } 467 | 468 | #========================================================== 469 | 470 | #test "$CC" || CC='gcc' 471 | #test "$CXX" || CXX='g++' 472 | test "$STRIP" || STRIP='strip' 473 | test "$AR" || AR='ar' 474 | test "$AS" || AS='as' 475 | test "$NM" || NM='nm' 476 | test "$RANLIB" || RANLIB='ranlib' 477 | test "$OBJCOPY" || OBJCOPY='objcopy' 478 | test "$OBJDUMP" || OBJDUMP='objdump' 479 | static_link='' 480 | # mingw 481 | test "$DLLTOOL" || DLLTOOL='dlltool' 482 | test "$WINDRES" || WINDRES='windres' 483 | test "$WINDMC" || WINDMC='windmc' 484 | 485 | prefix=/usr/local 486 | exec_prefix='${prefix}' 487 | libdir='${exec_prefix}/lib' 488 | bindir='${exec_prefix}/bin' 489 | sbindir='${exec_prefix}/sbin' 490 | libexecdir='${exec_prefix}/libexec' 491 | includedir='${prefix}/include' 492 | datarootdir='${prefix}/share' 493 | datadir='${datarootdir}' 494 | localstatedir='${prefix}/var' 495 | sysconfdir='${prefix}/etc' 496 | docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' 497 | infodir='${datarootdir}/info' 498 | mandir='${datarootdir}/man' 499 | localedir='${datarootdir}/locale' 500 | runstatedir='${localstatedir}/run' 501 | top_builddir="$(pwd)" 502 | top_srcdir="$(pwd)" 503 | builddir=. 504 | srcdir=. 505 | 506 | print_eval_var() 507 | { 508 | zzvareval= 509 | for i in $@ # perform up to 2 evals 510 | do 511 | zzvareval="$(eval echo $i)" 512 | zzvareval="$(eval echo $zzvareval)" 513 | done 514 | echo "$zzvareval" 515 | } 516 | 517 | #========================================================== 518 | 519 | help() 520 | { 521 | echo " 522 | Usage: ./configure [OPTION]... [VAR=VALUE]... 523 | 524 | To assign environment variables (e.g., CC, CFLAGS...), specify them as 525 | VAR=VALUE. See below for descriptions of some of the useful variables. 526 | 527 | Defaults for the options are specified in brackets. 528 | 529 | Installation directories: 530 | --prefix=PREFIX install architecture-independent files in PREFIX 531 | [${prefix}] 532 | --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX 533 | [${exec_prefix}] 534 | 535 | By default, 'make install' will install all the files in 536 | '/usr/local/bin', '/usr/local/lib' etc. You can specify 537 | an installation prefix other than '/usr/local' using '--prefix', 538 | for instance '--prefix=\$HOME'. 539 | 540 | For better control, use the options below. 541 | 542 | Fine tuning of the installation directories: 543 | --bindir=DIR user executables [${bindir}] 544 | --sbindir=DIR system admin executables [${sbindir}] 545 | --libexecdir=DIR program executables [${libexecdir}] 546 | --sysconfdir=DIR read-only single-machine data [${sysconfdir}] 547 | --localstatedir=DIR modifiable single-machine data [${localstatedir}] 548 | --runstatedir=DIR modifiable per-process data [${runstatedir}] 549 | --libdir=DIR object code libraries [${libdir}] 550 | --includedir=DIR C header files [${includedir}] 551 | --datarootdir=DIR read-only arch.-independent data root [${datarootdir}] 552 | --datadir=DIR read-only architecture-independent data [${datadir}] 553 | --infodir=DIR info documentation [${infodir}] 554 | --localedir=DIR locale-dependent data [${localedir}] 555 | --mandir=DIR man documentation [${mandir}] 556 | --docdir=DIR documentation root [${docdir}] 557 | 558 | Optional Features: 559 | (Use --disable-FEATURE or --enable-FEATURE) 560 | $(print_optional_features) 561 | Optional Packages: 562 | (Use --without-PACKAGE or --with-PACKAGE[=ARG] ) 563 | --with-sysroot[=DIR] Set DIR as the compiler's sysroot directory 564 | for headers and libraries 565 | $(print_optional_packages) 566 | Some influential environment variables: 567 | CC C compiler command 568 | CFLAGS C compiler flags 569 | CXXFLAGS C++ compiler flags 570 | LDFLAGS linker flags, e.g. -L if you have libraries in a 571 | nonstandard directory 572 | LIBS libraries to pass to the linker, e.g. -l 573 | CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if 574 | you have headers in a nonstandard directory 575 | PKG_CONFIG path to pkg-config utility 576 | PKG_CONFIG_PATH 577 | directories to add to pkg-config's search path 578 | PKG_CONFIG_LIBDIR 579 | path overriding pkg-config's built-in search path 580 | $(print_extra_env_variables) 581 | 582 | Use these variables to override the choices made by 'configure' or to help 583 | it to find libraries and programs with nonstandard names/locations. 584 | " 585 | exit 586 | } 587 | 588 | #========================================================== 589 | 590 | print_optional_features() 591 | { 592 | for confx in ${w_opts_configure} ${extra_configure_opts} 593 | do 594 | opt_print_${confx} 595 | done 596 | test -n "${extra_configure_opts}" || \ 597 | test -n "${w_opts_configure}" && printf " \n" 598 | } 599 | 600 | print_optional_packages() 601 | { 602 | for confx in ${extra_packages_opts} 603 | do 604 | opt_pkg_print_${confx} 605 | done 606 | printf " \n" 607 | } 608 | 609 | print_extra_env_variables() 610 | { 611 | for evar in ${extra_env_vars} 612 | do 613 | echo " ${evar}_CFLAGS compiler flags for ${evar}, overriding pkg-config" 614 | echo " ${evar}_LIBS linker flags for ${evar}, overriding pkg-config" 615 | done 616 | } 617 | 618 | #========================================================== 619 | 620 | getvalue() { 621 | echo $1 | cut -f 2 -d '=' 622 | } 623 | 624 | for i in $@ 625 | do 626 | case $i in 627 | --host=*) host="$(getvalue $i)" ;; 628 | CFLAGS=*) CFLAGS="$CFLAGS $(getvalue $i)" ;; 629 | CPPFLAGS=*) CPPFLAGS="$CPPFLAGS $(getvalue $i)" ;; 630 | LDFLAGS=*) LDFLAGS="$LDFLAGS $(getvalue $i)" ;; 631 | LXXFLAGS=*) LXXFLAGS="$LXXFLAGS $(getvalue $i)" ;; 632 | CXXFLAGS=*) CXXFLAGS="$CXXFLAGS $(getvalue $i)" ;; 633 | LIBS=*) LIBS="$LIBS $(getvalue $i)" ;; 634 | CC=*) CC="$(getvalue $i)" ;; 635 | CXX=*) CXX="$(getvalue $i)" ;; 636 | STRIP=*) STRIP="$(getvalue $i)" ;; 637 | AR=*) AR="$(getvalue $i)" ;; 638 | AS=*) AS="$(getvalue $i)" ;; 639 | NM=*) NM="$(getvalue $i)" ;; 640 | DLLTOOL=*) DLLTOOL="$(getvalue $i)" ;; 641 | WINDRES=*) WINDRES="$(getvalue $i)" ;; 642 | WINDMC=*) WINDMC="$(getvalue $i)" ;; 643 | 644 | --prefix=*) prefix="$(getvalue $i)" ;; 645 | --exec_prefix=*) exec_prefix="$(getvalue $i)" ;; 646 | --libdir=*) libdir="$(getvalue $i)" ;; 647 | --bindir=*) bindir="$(getvalue $i)" ;; 648 | --sbindir=*) sbindir="$(getvalue $i)" ;; 649 | --libexecdir=*) libexecdir="$(getvalue $i)" ;; 650 | --includedir=*) includedi="$(getvalue $i)" ;; 651 | --datarootdir=*) datarootdir="$(getvalue $i)" ;; 652 | --datadir=*) datadir="$(getvalue $i)" ;; 653 | --localstatedir=*) localstatedir="$(getvalue $i)" ;; 654 | --sysconfdir=*) sysconfdir="$(getvalue $i)" ;; 655 | --docdir=*) docdir="$(getvalue $i)" ;; 656 | --infodir=*) infodir="$(getvalue $i)" ;; 657 | --mandir=*) mandir="$(getvalue $i)" ;; 658 | --localedir=*) localedir="$(getvalue $i)" ;; 659 | --runstatedir=*) runstatedir="$(getvalue $i)" ;; 660 | --with-sysroot=*) sysroot="$(getvalue $i)" ;; 661 | --with-sysroot) sysroot='-print-sysroot' ;; 662 | --without-sysroot) sysroot='' ;; 663 | --enable-static-link) static_link=1 ;; 664 | --disable-static-link) static_link= ;; 665 | 666 | ---*) exit_error "Invalid opt: $i" ;; 667 | -h|--help) help ;; 668 | esac 669 | done 670 | 671 | if [ -n "$W_PKG_CONFIG_STATIC" ] ; then 672 | static_link=1 673 | fi 674 | 675 | for confx in ${w_opts_configure} ${extra_configure_opts} ${extra_packages_opts} 676 | do 677 | opt_configure_${confx} "$@" 678 | done 679 | 680 | # ==================================================== 681 | 682 | echo 683 | set_cc 684 | set_cxx 685 | set_w_pkg_config 686 | 687 | OS_BUILD=$(uname -s) 688 | OS_CROSS='' 689 | OS_TARGET='' 690 | 691 | if test -n "$host" ; then 692 | printf "Checking cross-compiler... " 693 | CROSS_IS_FULL_PATH='' 694 | CROSS_BASE_DIR='' 695 | OS_CROSS="$host" 696 | case $host in /*) 697 | CROSS_IS_FULL_PATH=1 698 | CROSS_BASE_DIR="$(dirname $host)/" 699 | OS_CROSS="$(basename $host)" 700 | ;; 701 | esac 702 | cross_base_dir='' 703 | w_c_cross_ok=$(check_command ${host}-cc ${host}-gcc ${host}-clang) 704 | w_cpp_cross_ok= 705 | if [ -n "$w_c_cross_ok" ] ; then 706 | CC="$w_c_cross_ok"; 707 | fi 708 | #-- 709 | if [ "$PROJECT_TYPE" = "C++" ] ; then 710 | w_cpp_cross_ok=$(check_command ${host}-c++ ${host}-g++ ${host}-clang++) 711 | if [ "$w_cpp_cross_ok" ] ; then 712 | CXX="$w_cpp_cross_ok"; 713 | fi 714 | else 715 | w_cpp_cross_ok=1 716 | CXX=${CROSS_BASE_DIR}${host}-c++ 717 | fi 718 | #-- 719 | if [ -n "$w_c_cross_ok" ] && [ -n "$w_cpp_cross_ok" ]; then 720 | LD=${CROSS_BASE_DIR}${host}-${LD} 721 | STRIP=${CROSS_BASE_DIR}${host}-${STRIP} 722 | AR=${CROSS_BASE_DIR}${host}-${AR} 723 | AS=${CROSS_BASE_DIR}${host}-${AS} 724 | NM=${CROSS_BASE_DIR}${host}-${NM} 725 | RANLIB=${CROSS_BASE_DIR}${host}-${RANLIB} 726 | OBJCOPY=${CROSS_BASE_DIR}${host}-${OBJCOPY} 727 | OBJDUMP=${CROSS_BASE_DIR}${host}-${OBJDUMP} 728 | #-- 729 | DLLTOOL=${CROSS_BASE_DIR}${host}-${DLLTOOL} 730 | WINDRES=${CROSS_BASE_DIR}${host}-${WINDRES} 731 | WINDMC=${CROSS_BASE_DIR}${host}-${WINDMC} 732 | #-- 733 | echo "$host" 734 | else 735 | exit_error "not found" 736 | fi 737 | if [ -z "$CROSS_IS_FULL_PATH" ] ; then 738 | # cross compiler is not a full path 739 | CC="$(basename $CC)" 740 | if [ -n "$CXX" ] ; then 741 | CXX="$(basename $CXX)" 742 | fi 743 | fi 744 | fi 745 | 746 | if [ -n "$static_link" ] ; then 747 | CC="$CC -static" 748 | CXX="$CXX -static" 749 | fi 750 | 751 | if [ -n "${sysroot}" ] ; then 752 | if [ "${sysroot}" = "-print-sysroot" ] ; then 753 | sysroot="$($CC -print-sysroot)" 754 | if [ -z "$sysroot" ] ; then 755 | exit_error "--with-sysroot ERROR: compiler doesn't support -print-sysroot" 756 | fi 757 | fi 758 | if ! [ -d "$sysroot" ] ; then 759 | exit_error "--with-sysroot ERROR: $sysroot is not a directory" 760 | fi 761 | CC="$CC --sysroot=${sysroot}" 762 | CXX="$CXX --sysroot=${sysroot}" 763 | if [ -n "$W_SYSROOT_EXTRA" ] ; then 764 | if [ -d ${sysroot}/include ] ; then 765 | CFLAGS="-I${sysroot}/include $CFLAGS" 766 | CXXFLAGS="-I${sysroot}/include $CXXFLAGS" 767 | fi 768 | if [ -d ${sysroot}/lib ] ; then 769 | LDFLAGS="-L${sysroot}/lib $LDFLAGS" 770 | LXXFLAGS="-L${sysroot}/lib $LXXFLAGS" 771 | fi 772 | fi 773 | fi 774 | 775 | #========================================================== 776 | 777 | case ${OS_BUILD} in 778 | CYGWIN*) OS_BUILD="Cygwin" ;; 779 | MINGW*) OS_BUILD="MinGW" ;; 780 | MSYS*) OS_BUILD="MinGW" ;; 781 | esac 782 | 783 | OS_TARGET=${OS_BUILD} 784 | 785 | if [ -n "$OS_CROSS" ] ; then 786 | # cross-compiling, reset OS_TARGET 787 | OS_TARGET='' 788 | # determine OS_TARGET again 789 | case $OS_CROSS in 790 | *-mingw*) OS_TARGET='MinGW' ;; 791 | *-darwin*) OS_TARGET='Darwin' ;; 792 | *-linux-*) OS_TARGET='Linux' ;; 793 | *-freebsd*) OS_TARGET='FreeBSD' ;; 794 | esac 795 | fi 796 | 797 | case $OS_TARGET in 798 | Linux) SYSDEFINE='__linux__=1' ;; 799 | MinGW) SYSDEFINE='__MINGW32__=1' ;; 800 | FreeBSD) SYSDEFINE='__FreeBSD__=1' ;; 801 | Darwub) SYSDEFINE='__APPLE__=1' ;; 802 | esac 803 | 804 | TARGET_OS="$OS_TARGET" 805 | 806 | #========================================================== 807 | 808 | CFLAGS="$CFLAGS $PROJECT_CFLAGS" 809 | CPPFLAGS="$CPPFLAGS $PROJECT_CPPFLAGS" 810 | CXXFLAGS="$CXXFLAGS $PROJECT_CXXFLAGS" 811 | LDFLAGS="$LDFLAGS $PROJECT_LDFLAGS" 812 | LXXFLAGS="$LXXFLAGS $PROJECT_LXXFLAGS" 813 | LIBS="$LIBS $PROJECT_LIBS" 814 | 815 | #========================================================== 816 | 817 | echo > config.log 818 | case $PROJECT_TYPE in 819 | C) WCVERW=$($CC --version 2>>config.log) ;; 820 | C++) WCVERW=$($CXX --version 2>>config.log) ;; 821 | esac 822 | echo "$WCVERW" >> config.log 823 | if [ -n "$(echo "$WCVERW" | grep "Free Software Foundation")" ] ; then 824 | printf "\n- GCC has been detected\n" >>config.log 825 | GCC='yes' 826 | fi 827 | 828 | w_headers='' 829 | #---------- 830 | w_main_func # main function where the user performs all sorts of tasks 831 | #---------- 832 | # apps to check, see configure.project (XXX_CFLAGS, XXX_LIBS) 833 | for appx in ${w_opts_check} ${apps_to_check} 834 | do 835 | opt_check_${appx} 836 | done 837 | # custom checks 838 | for xcheckx in ${w_opts_after_check} ${w_custom_checks} 839 | do 840 | ${xcheckx} 841 | done 842 | 843 | #========================================================== 844 | # config.mk 845 | #========================================================== 846 | 847 | # ensure -DHAVE_CONFIG_H 848 | CFLAGS="-DHAVE_CONFIG_H $CFLAGS " 849 | CXXFLAGS="-DHAVE_CONFIG_H $CXXFLAGS" 850 | 851 | cat > config.mk < config.sh <> config.mk 1006 | #-- 1007 | echo "${env}_CFLAGS=\"$(eval echo \$${env}_CFLAGS)\" 1008 | ${env}_LIBS=\"$(eval echo \$${env}_LIBS)\"" >> config.sh 1009 | done 1010 | fi 1011 | 1012 | 1013 | #========================================================== 1014 | # Makefiles.in 1015 | #========================================================== 1016 | 1017 | if [ -n "$W_MAKEFILES_IN" ] ; then 1018 | w_makefiles=$(find . -name Makefile.in | sed -e 's%\.in%%') 1019 | for makefile in ${w_makefiles} 1020 | do 1021 | cat config.mk ${makefile}.in > ${makefile} 1022 | done 1023 | fi 1024 | 1025 | #========================================================== 1026 | 1027 | . ./config.sh 1028 | 1029 | #========================================================== 1030 | # config.h 1031 | #========================================================== 1032 | 1033 | cat > config.h <> config.h 1067 | fi 1068 | 1069 | if [ -n "${config_h_have}" ] ; then 1070 | ( 1071 | for feat in ${config_h_have} 1072 | do 1073 | echo "#define HAVE_${feat} 1" 1074 | done 1075 | ) >> config.h 1076 | fi 1077 | 1078 | #========================================================== 1079 | 1080 | for infile in ${w_infiles} 1081 | do 1082 | ./w_conf/00_standard_infile.sh ${infile} 1083 | done 1084 | 1085 | #========================================================== 1086 | 1087 | w_finish_func 1088 | 1089 | echo "${w_config_h_extra_lines} 1090 | #endif /* __W_CONFIGURE_CONFIG_H__ */" >> config.h 1091 | 1092 | #========================================================== 1093 | 1094 | echo 1095 | echo "* Now run 'make' (or 'make clean' first if the app is already compiled)" 1096 | echo 1097 | -------------------------------------------------------------------------------- /configure.project: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Project name and version 4 | PACKAGE="gmrun" 5 | VERSION="1.4w" 6 | PACKAGE_URL='' 7 | 8 | # Flags that will be used in all makefiles that compile code... 9 | PROJECT_CFLAGS='-Wall -g -O2' # -Wextra -Wno-unused-parameter -Wno-missing-field-initializers 10 | PROJECT_CXXFLAGS="${PROJECT_CFLAGS}" 11 | PROJECT_CPPFLAGS='' 12 | PROJECT_LDFLAGS='' 13 | PROJECT_LXXFLAGS='' 14 | PROJECT_LIBS='' 15 | 16 | # Project type, use C to avoid checking for C++ compiler and stuff 17 | # supported values: C, C++ ("" = C++) 18 | PROJECT_TYPE=C 19 | 20 | # config.h: custom extra lines 21 | w_config_h_extra_lines="" 22 | 23 | # config.mk: custom extra lines 24 | w_config_mk_extra_lines="" 25 | 26 | 27 | # configure the project using the commands provided 28 | # by `configure` and other scripts and commands/tools 29 | w_main_func() 30 | { 31 | # GCC=yes if gcc has been detected 32 | # if the Makefiles are only compatible with GNU make, use w_require_gnu_make 33 | 34 | # check required headers and exit on error 35 | w_check_headers_required stdio.h 36 | } 37 | 38 | w_finish_func() 39 | { 40 | printf "" 41 | } 42 | 43 | 44 | #===================================================================== 45 | # special scripts for optional features and various tasks 46 | # checks are run after w_main_func 47 | # 48 | # - source w_conf/xxx script to support a specific feature 49 | # - these scripts are meant to be generic, so any project can add them 50 | # - most of these scripts can handle --enable/--disable cmd options 51 | # to make it easier to replace autoconf 52 | # 53 | # In most cases, if a feature is enabled, it will be defined in config.h 54 | # #define HAVE_ZLIB 1 55 | # And config.mk 56 | # ZLIB_CFLAGS = 57 | # ZLIB_LIBS = 58 | 59 | GETTEXT_PACKAGE="$PACKAGE" 60 | . w_conf/gettext 61 | 62 | #W_GTK_IS_OPTIONAL=yes 63 | ##-- 64 | #W_GTK_DEFAULT_VERSION=3 65 | #W_GTK2_MIN_VERSION='2.14' 66 | #W_GTK3_MIN_VERSION='3.14' 67 | #W_GTK4_MIN_VERSION='' 68 | ##-- support only 1 version 69 | #W_GTK_ONLY_VERSION=2 70 | #W_GTK_MIN_VERSION=2.24 71 | . w_conf/gtk 72 | 73 | 74 | # ============== 75 | # gmrun specific 76 | extra_configure_opts="$extra_configure_opts gmrun" 77 | 78 | opt_print_gmrun() 79 | { 80 | echo ' --enable-debug produce debug build' 81 | echo ' --enable-xdg follow xdg spec for configuration and history files location' 82 | } 83 | 84 | opt_configure_gmrun() 85 | { 86 | enable_debug=no 87 | enable_xdg=no 88 | 89 | for i in $@ 90 | do 91 | case $i in 92 | --enable-debug) enable_debug='yes' ;; 93 | --disable-debug) enable_debug='no' ;; 94 | --enable-xdg) enable_xdg='yes' ;; 95 | --disable-xdg) enable_xdg='no' ;; 96 | esac 97 | done 98 | 99 | if [ "$enable_debug" = "yes" ] ; then 100 | CFLAGS="$CFLAGS -g -DDEBUG" 101 | fi 102 | 103 | if [ "$enable_xdg" = "yes" ] ; then 104 | CFLAGS="$CFLAGS -DFOLLOW_XDG_SPEC" 105 | fi 106 | } 107 | -------------------------------------------------------------------------------- /data/Makefile: -------------------------------------------------------------------------------- 1 | include ../config.mk 2 | 3 | all: 4 | 5 | strip: 6 | 7 | install: 8 | mkdir -p $(DESTDIR)$(datadir)/applications 9 | install -c -m 644 gmrun.desktop $(DESTDIR)$(datadir)/applications 10 | mkdir -p $(DESTDIR)$(datadir)/pixmaps 11 | install -c -m 644 gmrun.png $(DESTDIR)$(datadir)/pixmaps 12 | mkdir -p $(DESTDIR)$(mandir)/man1 13 | install -c -m 644 gmrun.1 $(DESTDIR)$(mandir)/man1 14 | mkdir -p $(DESTDIR)$(sysconfdir) 15 | install -c -m 644 gmrunrc $(DESTDIR)$(sysconfdir) 16 | 17 | uninstall: 18 | rm -f $(DESTDIR)$(datadir)/applications/gmrun.desktop 19 | rm -f $(DESTDIR)$(datadir)/pixmaps/gmrun.png 20 | rm -f $(DESTDIR)$(mandir)/man1/gmrun.1 21 | rm -f $(DESTDIR)$(sysconfdir)/gmrunrc 22 | 23 | install-strip: install 24 | 25 | clean: 26 | 27 | distclean: 28 | 29 | 30 | -------------------------------------------------------------------------------- /data/gmrun.1: -------------------------------------------------------------------------------- 1 | .\" First parameter, NAME, should be all caps 2 | .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection 3 | .\" other parameters are allowed: see man(7), man(1) 4 | .TH gmrun 1.2w "2021" 5 | .\" Please adjust this date whenever revising the manpage. 6 | .\" 7 | .\" Some roff macros, for reference: 8 | .\" .nh disable hyphenation 9 | .\" .hy enable hyphenation 10 | .\" .ad l left justify 11 | .\" .ad b justify to both left and right margins 12 | .\" .nf disable filling 13 | .\" .fi enable filling 14 | .\" .br insert line break 15 | .\" .sp insert n+1 empty lines 16 | .\" for manpage-specific macros, see man(7) 17 | .SH NAME 18 | gmrun \- Featureful CLI-like GTK+ application launcher 19 | .SH SYNOPSIS 20 | .B gmrun \fR\fI[TEXT]\fR 21 | .br 22 | .SH DESCRIPTION 23 | .\" TeX users may be more comfortable with the \fB\fP and 24 | .\" \fI\fP escape sequences to invode bold face and italics, 25 | .\" respectively. 26 | \fBgmrun\fP is a GTK program that allows a user to use a CLI-like interface to launch applications. 27 | .SH OPTIONS 28 | If gmrun is invoked with a command-line parameter, that will be the initial content of the dialog box. 29 | .PP 30 | gmrun can be customized using a configuration file, ~/.gmrunrc . The system-wide configuration file is /etc/gmrunrc . 31 | .PP 32 | The format of the configuration file is simple; "variable = value". 33 | .TP 34 | .B Example: 35 | variable1 = foobar 36 | .br 37 | .TP 38 | .B Terminal 39 | The command to run when Ctrl+Enter is pressed with no command entered; used to start a new terminal. 40 | .TP 41 | .B TermExec 42 | The command to run when Ctrl+Enter is pressed with a command entered. The entered command is supplied as an argument to TermExec. 43 | .TP 44 | .B Geometry 45 | see /etc/gmrunrc for more info 46 | .TP 47 | .B History 48 | Number of entered commands which should be kept in gmrun's history(~/.gmrun_history). Using the Up and Down arrow keys within the gmrun window will cycle through the history. You can search backwards through the history with Ctrl+R, and forward with Ctrl+S. Hit "!" to enter a special search that matches only the start of strings. To cancel a search, hit the \fIESC\fP key. Otherwise, after you have found the history item you wish to run, hit Enter. Also: CTRL-P = Up / CTRL-N = Down. 49 | .TP 50 | .B ShowLast 51 | Whether to show the last command as initial text, or an empty textarea (1 or 0). 52 | .TP 53 | .B Selected 54 | Whether the initial text should be selected or not (1 or 0). 55 | .SH URL/File HANDLING 56 | .TP 57 | .B USE_GLIB_XDG = 1 58 | For automatic URL and File handling using the freedesktop specification. 59 | .TP 60 | See /etc/gmrunrc for custom URL and extension handlers (USE_GLIB_XDG = 0) 61 | .TP 62 | The included configuration files have a few examples which you can work with. You should also note that the protocol part of URL_protocol can be *anything*. You can set up "URL_foobarbazcustom", and so long as you enter in "foobarbazcustom:", it will use the given program. 63 | .SH SEE ALSO 64 | .BR /etc/gmrunrc. 65 | .br 66 | .SH AUTHOR 67 | Originally written by 68 | .B Mihai Bazon 69 | , the latest version is available from 70 | \fIhttps://github.com/wdlkmpx/gmrun/\fP 71 | .PP 72 | This manual page was originally written by David B Harris 73 | -------------------------------------------------------------------------------- /data/gmrun.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=gmrun 3 | Icon=gmrun 4 | Comment=execute a command 5 | Exec=gmrun 6 | Terminal=false 7 | Type=Application 8 | Categories=TerminalEmulator;System 9 | GenericName=gmrun 10 | -------------------------------------------------------------------------------- /data/gmrun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlkmpx/gmrun/f873b4934e17c4679d5b8cf3c9d7eebdd8c17fa4/data/gmrun.png -------------------------------------------------------------------------------- /data/gmrunrc: -------------------------------------------------------------------------------- 1 | # gmrun configuration file 2 | 3 | # Set terminal 4 | Terminal = xterm 5 | TermExec = ${Terminal} -e 6 | AlwaysInTerm = ssh telnet ftp lynx mc vi vim pine centericq perldoc man 7 | 8 | # Set window geometry 9 | # - Wx width 400x 10 | # - WxH width x height 400x70 (height = 0 = ignore) 11 | # - WxH+x+y width x height +x +y 400x0+200+100 12 | # 13 | # If +x+y is not specified (or not allowed), the gmrun window is displayed centered 14 | Geometry = 500x 15 | 16 | # History size 17 | History = 256 18 | 19 | # Shows last history line selected when invoked 20 | ShowLast = 0 21 | 22 | # Show files starting with '.' 23 | # Default is 0 (off), set it to 1 if you want "hidden" files to show up 24 | # in the completion window 25 | ShowDotFiles = 0 26 | 27 | # Timeout (in milliseconds) after which gmrun will simulate a TAB press 28 | # Set this to NULL if don't like this feature. 29 | TabTimeout = 0 30 | 31 | # Use libc's system(3) to run commands, this includes any shell command 32 | # and special stuff that only a shell interpreter can understand 33 | # Set to 0 if you want a more conservative approach where the file to run 34 | # is validated and errors running files are reported (without closing the gmrun window) 35 | SHELL_RUN = 1 36 | 37 | 38 | # use GLib XDG handling? (freedesktop specification) 39 | USE_GLIB_XDG = 0 40 | 41 | #======================================================================= 42 | # USE_GLIB_XDG = 0, you can specify URL and extension Handlers... 43 | #======================================================================= 44 | 45 | # URL handlers 46 | # If the entered text is "http://www.google.com" then: 47 | # - %u gets replaced with the whole URL ("http://www.google.com") 48 | # - %s gets replaced with "//www.google.com". 49 | # useful for URL's like "man:printf" --> %s becomes printf 50 | URL_http = xdg-open '%u' 51 | URL_mailto = xdg-email '%u' 52 | URL_file = xdg-open '%s' 53 | URL_man = ${TermExec} man %s 54 | URL_info = ${TermExec} info %s 55 | URL_search = xdg-open 'http://www.google.com/search?q=%s' 56 | 57 | # extension handlers 58 | # Customize your own extension handler. 59 | # syntax-> EXT:ext,etc = handler '%s' 60 | EXT:doc,rtf,txt,cc,cpp,h,java,html,htm,epl,tex,latex,js,css,xml,xsl,am,ps,pdf = xdg-open '%s' 61 | EXT:mkv,mp4,avi,asf,wmv = xdg-open '%s' 62 | EXT:mp3,flac,ogg,aac,wav = xdg-open '%s' 63 | -------------------------------------------------------------------------------- /gtkcompat.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * For more information, please refer to 5 | */ 6 | 7 | /** 2022-04-20 **/ 8 | 9 | /* 10 | * gtkcompat.h, GTK2+ compatibility layer 11 | * 12 | * This lib makes it easier to support older GTK versions 13 | * while still avoiding deprecated functions as much as possible. 14 | * 15 | * The older the GTK version, the more compatible functions are "defined" 16 | * so it's not wise to use the compiled binary in newer distros or something. 17 | * 18 | * Apps should support gtk2 >= 2.14 / gtk3 >= 3.14 19 | * 20 | */ 21 | 22 | #ifndef __GTKCOMPAT_H 23 | #define __GTKCOMPAT_H 24 | 25 | #ifdef __cplusplus 26 | extern "C" 27 | { 28 | #endif 29 | 30 | #include 31 | 32 | /* ================================================== */ 33 | /* GLIB */ 34 | /* ================================================== */ 35 | 36 | #ifndef __GLIB_COMPAT_H 37 | #define __GLIB_COMPAT_H 38 | 39 | #include 40 | 41 | // GLIB < 2.58 42 | #if ! GLIB_CHECK_VERSION (2, 58, 0) 43 | #define G_SOURCE_FUNC(f) ((GSourceFunc) (void (*)(void)) (f)) 44 | #endif 45 | 46 | 47 | // GLIB < 2.40 48 | #if ! GLIB_CHECK_VERSION (2, 40, 0) 49 | #define g_key_file_save_to_file(kfile,filename,error) { \ 50 | char * xdatax = g_key_file_to_data (kfile, NULL, error); \ 51 | g_file_set_contents (filename, xdatax, -1, error); \ 52 | g_free (xdatax); \ 53 | } 54 | #endif 55 | 56 | 57 | // GLIB < 2.32 58 | #if ! GLIB_CHECK_VERSION (2, 32, 0) 59 | #define G_SOURCE_REMOVE FALSE 60 | #define G_SOURCE_CONTINUE TRUE 61 | #define GRecMutex GStaticRecMutex 62 | #define g_rec_mutex_init(x) g_static_rec_mutex_init(x) 63 | #define g_rec_mutex_lock(x) g_static_rec_mutex_lock(x) 64 | #define g_rec_mutex_trylock(x) g_static_rec_mutex_trylock(x) 65 | #define g_rec_mutex_unlock(x) g_static_rec_mutex_unlock(x) 66 | #define g_rec_mutex_clear(x) g_static_rec_mutex_free(x) 67 | #define g_thread_new(name,func,data) g_thread_create(func,data,TRUE,NULL) 68 | #define g_thread_try_new(name,func,data,error) g_thread_create(func,data,TRUE,error) 69 | #define g_hash_table_add(ht,key) g_hash_table_replace(ht,key,key) 70 | #define g_hash_table_contains(ht,key) g_hash_table_lookup_extended(ht,key,NULL,NULL) 71 | #endif 72 | 73 | 74 | // GMutex vs GStaticMutex 75 | #if GLIB_CHECK_VERSION (2, 32, 0) 76 | // since version 2.32.0 GMutex can be statically allocated 77 | // don't use WGMutex to replace GMutex * ... issues, errors. 78 | # define WGMutex GMutex 79 | # define Wg_mutex_init g_mutex_init 80 | # define Wg_mutex_lock g_mutex_lock 81 | # define Wg_mutex_trylock g_mutex_trylock 82 | # define Wg_mutex_unlock g_mutex_unlock 83 | # define Wg_mutex_clear g_mutex_clear 84 | #else 85 | # define WGMutex GStaticMutex 86 | # define Wg_mutex_init g_static_mutex_init 87 | # define Wg_mutex_lock g_static_mutex_lock 88 | # define Wg_mutex_trylock g_static_mutex_trylock 89 | # define Wg_mutex_unlock g_static_mutex_unlock 90 | # define Wg_mutex_clear g_static_mutex_free 91 | // sed -i 's%g_mutex_%Wg_mutex_%g' $(grep "g_mutex_" *.c *.h | cut -f 1 -d ":" | grep -v -E 'gtkcompat|glib-compat' | sort -u) 92 | #endif 93 | 94 | 95 | // GLIB < 2.30 96 | #if ! GLIB_CHECK_VERSION (2, 30, 0) 97 | #define g_format_size g_format_size_for_display 98 | #endif 99 | 100 | 101 | // GLIB < 2.28 102 | #if ! GLIB_CHECK_VERSION (2, 28, 0) 103 | #define g_list_free_full(list,free_func) {\ 104 | g_list_foreach (list, (GFunc) free_func, NULL);\ 105 | g_list_free (list);\ 106 | } 107 | #endif 108 | 109 | 110 | // GLIB < 2.22 111 | #if ! GLIB_CHECK_VERSION (2, 22, 0) 112 | #define g_mapped_file_unref(x) g_mapped_file_free(x) 113 | #endif 114 | 115 | // GLIB < 2.20 116 | #if ! GLIB_CHECK_VERSION (2, 20, 0) 117 | #define g_app_info_get_commandline(app) g_app_info_get_executable(app) 118 | #endif 119 | 120 | /* glib 2.18+ tested */ 121 | 122 | #endif /* __GLIB_COMPAT_H */ 123 | 124 | 125 | /* ================================================== */ 126 | /* GDK KEYS */ 127 | /* ================================================== */ 128 | 129 | #include 130 | #if !defined(GDK_KEY_a) // GTK_MAJOR_VERSION >= 3 131 | #define GDK_KEY(symbol) GDK_##symbol 132 | #else 133 | #define GDK_KEY(symbol) GDK_KEY_##symbol 134 | #endif 135 | 136 | 137 | /* ================================================== */ 138 | /* GTK 3 */ 139 | /* ================================================== */ 140 | 141 | // GTK >= 3.0 -- applies to GTK3, GTK4... 142 | #if GTK_MAJOR_VERSION >= 3 143 | #define GTKCOMPAT_DRAW_SIGNAL "draw" 144 | #define gtkcompat_widget_set_halign_left(w) gtk_widget_set_halign(GTK_WIDGET(w), GTK_ALIGN_START) 145 | #define gtkcompat_widget_set_halign_center(w) gtk_widget_set_halign(GTK_WIDGET(w), GTK_ALIGN_CENTER) 146 | #define gtkcompat_widget_set_halign_right(w) gtk_widget_set_halign(GTK_WIDGET(w), GTK_ALIGN_END) 147 | // using GtkTable format, translate to to GtkGrid 148 | #define gtkcompat_grid_attach(table,child,left,right,top,bottom) \ 149 | gtk_grid_attach((table),(child), (left), (top), (right)-(left), (bottom)-(top)) 150 | #define gtkcompat_grid_new(rows,cols) (gtk_grid_new()) 151 | #endif 152 | 153 | 154 | #if GTK_MAJOR_VERSION == 3 155 | 156 | // GTK >= 3.20 (gtk_widget_set_focus_on_click) 157 | #if GTK_MINOR_VERSION >= 20 158 | #define gtk_button_set_focus_on_click(w,b) gtk_widget_set_focus_on_click(GTK_WIDGET(w),b) 159 | #define gtk_button_get_focus_on_click(w) gtk_widget_get_focus_on_click(GTK_WIDGET(w)) 160 | #define gtk_combo_box_set_focus_on_click(w,b) gtk_widget_set_focus_on_click(GTK_WIDGET(w),b) 161 | #define gtk_combo_box_get_focus_on_click(w) gtk_widget_get_focus_on_click(GTK_WIDGET(w)) 162 | #define gtk_file_chooser_button_set_focus_on_click(w,b) gtk_widget_set_focus_on_click(GTK_WIDGET(w),b) 163 | #define gtk_file_chooser_button_get_focus_on_click(w) gtk_widget_get_focus_on_click(GTK_WIDGET(w)) 164 | #endif 165 | 166 | /* GTK < 3.12 167 | #if ! GTK_CHECK_VERSION (3, 12, 0) 168 | #define gtk_application_set_accels_for_action(app,name,accels) gtk_application_add_accelerator(app,accels[0],name,NULL) 169 | #define gtk_widget_set_margin_start(widget,margin) gtk_widget_set_margin_left(widget,margin) 170 | #define gtk_widget_set_margin_end(widget,margin) gtk_widget_set_margin_right(widget,margin) 171 | #endif */ 172 | 173 | #endif /* ------- GTK3 ------- */ 174 | 175 | 176 | /* ================================================== */ 177 | /* GTK 2 */ 178 | /* ================================================== */ 179 | 180 | #if GTK_MAJOR_VERSION == 2 181 | 182 | // define some GTK3.14+ functions 183 | // GTK < 3.10 184 | // gdk_window_create_similar_image_surface() was removed in gtk4 185 | // only use gdk_window_create_similar_surface() 186 | // GTK < 3.8 187 | #define gtk_widget_set_opacity(w,o) gtk_window_set_opacity(GTK_WINDOW(w),o) 188 | #define gtk_widget_get_opacity(w) (gtk_window_get_opacity(GTK_WINDOW(w)) 189 | // GTK < 3.4 190 | #define gtk_application_window_new(app) gtk_window_new(GTK_WINDOW_TOPLEVEL) 191 | 192 | /*** GTK 3.0 ***/ 193 | #define GTKCOMPAT_DRAW_SIGNAL "expose_event" 194 | #define gtk_box_new(ori,spacing) \ 195 | ((ori == GTK_ORIENTATION_HORIZONTAL) ? gtk_hbox_new(FALSE,spacing) \ 196 | : gtk_vbox_new(FALSE,spacing)) 197 | #define gtk_button_box_new(ori) \ 198 | ((ori == GTK_ORIENTATION_HORIZONTAL) ? gtk_hbutton_box_new() \ 199 | : gtk_vbutton_box_new()) 200 | #define gtk_scale_new(ori,adjustment) \ 201 | ((ori == GTK_ORIENTATION_HORIZONTAL) ? gtk_hscale_new(adjustment) \ 202 | : gtk_vscale_new(adjustment)) 203 | #define gtk_scale_new_with_range(ori,min,max,step) \ 204 | ((ori == GTK_ORIENTATION_HORIZONTAL) ? gtk_hscale_new_with_range(min,max,step) \ 205 | : gtk_vscale_new_with_range(min,max,step)) 206 | #define gtk_separator_new(ori) \ 207 | ((ori == GTK_ORIENTATION_HORIZONTAL) ? gtk_hseparator_new() \ 208 | : gtk_vseparator_new()) 209 | #define gtk_scrollbar_new(ori,adjustment) \ 210 | ((ori == GTK_ORIENTATION_HORIZONTAL) ? gtk_hscrollbar_new(adjustment) \ 211 | : gtk_vscrollbar_new(adjustment)) 212 | #define gtk_paned_new(ori) \ 213 | ((ori == GTK_ORIENTATION_HORIZONTAL) ? gtk_hpaned_new() \ 214 | : gtk_vpaned_new()) 215 | #define gtk_widget_get_allocated_height(widget) (GTK_WIDGET(widget)->allocation.height ) 216 | #define gtk_widget_get_allocated_width(widget) (GTK_WIDGET(widget)->allocation.width ) 217 | #define gtk_combo_box_text_remove_all(cmb) { \ 218 | gtk_list_store_clear (GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (cmb)))); \ 219 | } 220 | #define gtk_tree_model_iter_previous(model,iter) ({ \ 221 | GtkTreePath * xpathx = gtk_tree_model_get_path (model, iter); \ 222 | gboolean xvalidx = gtk_tree_path_prev (xpathx); \ 223 | if (xvalidx) gtk_tree_model_get_iter (model, iter, xpathx); \ 224 | gtk_tree_path_free (xpathx); \ 225 | xvalidx; \ 226 | }) 227 | #define gtk_progress_bar_set_show_text(pb,show) 228 | #define gtk_widget_override_font(w,f) gtk_widget_modify_font(w,f) 229 | #define gtkcompat_widget_set_halign_left(w) gtk_misc_set_alignment(GTK_MISC(w), 0.0, 0.5) 230 | #define gtkcompat_widget_set_halign_center(w) gtk_misc_set_alignment(GTK_MISC(w), 0.5, 0.5) 231 | #define gtkcompat_widget_set_halign_right(w) gtk_misc_set_alignment(GTK_MISC(w), 1.0, 0.5) 232 | //- 233 | #define GTK_GRID GTK_TABLE 234 | #define GtkGrid GtkTable 235 | //#define gtk_grid_attach(grid,child,left,top,width,height) gtk_table_attach_defaults(grid,child,left,left+width,top,top+height) 236 | #define gtkcompat_grid_new(rows,cols) (gtk_table_new((rows),(cols),FALSE)) 237 | #define gtkcompat_grid_attach gtk_table_attach_defaults 238 | #define gtk_grid_set_column_spacing gtk_table_set_col_spacings 239 | #define gtk_grid_set_row_spacing gtk_table_set_row_spacings 240 | #define gtk_grid_get_column_spacing gtk_table_get_default_col_spacing 241 | #define gtk_grid_get_row_spacing gtk_table_get_default_row_spacing 242 | #define gtk_grid_set_row_homogeneous gtk_table_set_homogeneous 243 | #define gtk_grid_set_column_homogeneous gtk_table_set_homogeneous 244 | #define gtk_grid_get_row_homogeneous gtk_table_get_homogeneous 245 | #define gtk_grid_get_column_homogeneous gtk_table_gset_homogeneous 246 | //- 247 | typedef enum /* GtkAlign */ 248 | { 249 | GTK_ALIGN_FILL, 250 | GTK_ALIGN_START, 251 | GTK_ALIGN_END, 252 | GTK_ALIGN_CENTER 253 | } GtkAlign; 254 | /* GtkApplication */ 255 | #define GtkApplication void 256 | #define g_application_quit(app) gtk_main_quit() 257 | #undef G_APPLICATION 258 | #define G_APPLICATION(app) ((void *) (app)) 259 | //- 260 | #define gdk_error_trap_pop_ignored gdk_error_trap_pop 261 | 262 | 263 | // GTK < 2.24 264 | #if GTK_MINOR_VERSION < 24 265 | typedef struct _GtkComboBox GtkComboBoxText; 266 | typedef struct _GtkComboBoxClass GtkComboBoxTextClass; 267 | typedef struct _GtkComboBoxPrivate GtkComboBoxTextPrivate; 268 | #define GTK_TYPE_COMBO_BOX_TEXT (gtk_combo_box_get_type ()) 269 | #define GTK_COMBO_BOX_TEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_COMBO_BOX, GtkComboBoxText)) 270 | #define GTK_COMBO_BOX_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_COMBO_BOX, GtkComboBoxTextClass)) 271 | #define GTK_IS_COMBO_BOX_TEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_COMBO_BOX)) 272 | #define GTK_IS_COMBO_BOX_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_COMBO_BOX)) 273 | #define GTK_COMBO_BOX_TEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_COMBO_BOX, GtkComboBoxTextClass)) 274 | #define gtk_combo_box_text_new() gtk_combo_box_new_text() 275 | #define gtk_combo_box_text_new_with_entry() gtk_combo_box_entry_new_text() 276 | #define gtk_combo_box_text_append_text(combo,text) gtk_combo_box_append_text(combo,text) 277 | #define gtk_combo_box_text_insert_text(combo,pos,text) gtk_combo_box_insert_text(combo,pos,text) 278 | #define gtk_combo_box_text_prepend_text(combo,text) gtk_combo_box_prepend_text(combo,text) 279 | #define gtk_combo_box_text_remove(combo,pos) gtk_combo_box_remove_text(combo,pos) 280 | #define gtk_combo_box_text_get_active_text(combo) (gtk_combo_box_get_active_text(combo)) 281 | //#define gtk_combo_box_get_has_entry(combo) (0) 282 | #define gtk_combo_box_set_entry_text_column(combo,cl) 283 | #define gtk_combo_box_get_entry_text_column(combo) (0) 284 | #define gtk_range_get_round_digits(range) (GTK_RANGE(range)->round_digits) 285 | //#define gdk_window_get_visual(w) (gdk_drawable_get_visual(GDK_DRAWABLE(w))) 286 | #define gdk_window_get_screen(w) (gdk_drawable_get_screen(GDK_DRAWABLE(w))) 287 | #define gdk_window_get_display(w) (gdk_drawable_get_display(GDK_DRAWABLE(w))) 288 | #define gtk_notebook_get_group_name gtk_notebook_get_group 289 | #define gtk_notebook_set_group_name gtk_notebook_set_group 290 | #endif 291 | 292 | 293 | // GTK < 2.22 294 | #if GTK_MINOR_VERSION < 22 295 | #define gtk_accessible_get_widget(a) ((a)->widget) 296 | #define gtk_window_has_group(w) (GTK_WINDOW(w)->group != NULL) 297 | #define gtk_window_group_get_current_grab(wg) \ 298 | ((GTK_WINDOW_GROUP(wg)->grabs) ? GTK_WIDGET(GTK_WINDOW_GROUP(wg)->grabs->data) : NULL) 299 | #define gtk_font_selection_dialog_get_font_selection(fsd)(GTK_FONT_SELECTION_DIALOG(fsd)->fontsel) 300 | #define gtk_notebook_get_tab_hborder(n) (GTK_NOTEBOOK(n)->tab_hborder) 301 | #define gtk_notebook_get_tab_vborder(n) (GTK_NOTEBOOK(n)->tab_vborder) 302 | #define gtk_button_get_event_window(button) (GTK_BUTTON(button)->event_window) 303 | #define gdk_visual_get_visual_type(visual) (GDK_VISUAL(visual)->type) 304 | #define gdk_visual_get_depth(visual) (GDK_VISUAL(visual)->depth) 305 | #define gdk_visual_get_byte_order(visual) (GDK_VISUAL(visual)->byte_order) 306 | #define gdk_visual_get_colormap_size(visual) (GDK_VISUAL(visual)->colormap_size) 307 | #define gdk_visual_get_bits_per_rgb(visual) (GDK_VISUAL(visual)->bits_per_rgb) 308 | #define gtk_icon_view_get_item_orientation gtk_icon_view_get_orientation 309 | #define gtk_icon_view_set_item_orientation gtk_icon_view_set_orientation 310 | #define gdk_window_create_similar_surface(gdksurf,content,width,height) ({ \ 311 | cairo_t * wcr = gdk_cairo_create (gdksurf); \ 312 | cairo_surface_t * window_surface = cairo_get_target (wcr); \ 313 | cairo_surface_t * out_s = cairo_surface_create_similar (window_surface, content, width, height); \ 314 | cairo_destroy (wcr); \ 315 | out_s; \ 316 | }) 317 | #endif 318 | 319 | 320 | // GTK < 2.20 321 | #if GTK_MINOR_VERSION < 20 322 | #define gtk_widget_get_mapped(wid) ((GTK_WIDGET_FLAGS (wid) & GTK_MAPPED) != 0) 323 | #define gtk_widget_get_realized(wid) ((GTK_WIDGET_FLAGS (wid) & GTK_REALIZED) != 0) 324 | #define gtk_window_get_window_type(window) (GTK_WINDOW(window)->type) 325 | #define gtk_widget_get_requisition(w,r) (*(r) = GTK_WIDGET(w)->requisition) 326 | #define gtk_widget_set_mapped(w,yes) { \ 327 | if (yes) GTK_WIDGET_SET_FLAGS(w,GTK_MAPPED); \ 328 | else GTK_WIDGET_UNSET_FLAGS(w,GTK_MAPPED); \ 329 | } 330 | #define gtk_widget_set_realized(w,yes) { \ 331 | if (yes) GTK_WIDGET_SET_FLAGS(w,GTK_REALIZED); \ 332 | else GTK_WIDGET_UNSET_FLAGS(w,GTK_REALIZED); \ 333 | } 334 | #define gtk_range_get_slider_size_fixed(range) (GTK_RANGE(range)->slider_size_fixed) 335 | #define gtk_range_get_min_slider_size(range) (GTK_RANGE(range)->min_slider_size) 336 | #define gtk_entry_get_text_window(entry) (GTK_ENTRY(entry)->text_area) 337 | #endif 338 | 339 | 340 | // GTK < 2.18 341 | #if GTK_MINOR_VERSION < 18 342 | #define gtk_widget_get_state(wid) (GTK_WIDGET (wid)->state) 343 | #define gtk_widget_is_toplevel(wid) ((GTK_WIDGET_FLAGS (wid) & GTK_TOPLEVEL) != 0) 344 | #define gtk_widget_get_has_window(wid) !((GTK_WIDGET_FLAGS (wid) & GTK_NO_WINDOW) != 0) 345 | #define gtk_widget_get_visible(wid) ((GTK_WIDGET_FLAGS (wid) & GTK_VISIBLE) != 0) 346 | #define gtk_widget_is_drawable(wid) (GTK_WIDGET_VISIBLE (wid) && GTK_WIDGET_MAPPED (wid)) 347 | #define gtk_widget_get_sensitive(wid) ((GTK_WIDGET_FLAGS (wid) & GTK_SENSITIVE) != 0) 348 | #define gtk_widget_get_can_focus(wid) ((GTK_WIDGET_FLAGS (wid) & GTK_CAN_FOCUS) != 0) 349 | #define gtk_widget_has_focus(wid) ((GTK_WIDGET_FLAGS (wid) & GTK_HAS_FOCUS) != 0) 350 | #define gtk_widget_get_can_default(wid) ((GTK_WIDGET_FLAGS (wid) & GTK_CAN_DEFAULT) != 0) 351 | #define gtk_widget_get_receives_default(wid) ((GTK_WIDGET_FLAGS (wid) & GTK_RECEIVES_DEFAULT) != 0) 352 | #define gtk_widget_has_default(wid) ((GTK_WIDGET_FLAGS (wid) & GTK_HAS_DEFAULT) != 0) 353 | #define gtk_widget_has_grab(wid) ((GTK_WIDGET_FLAGS (wid) & GTK_HAS_GRAB) != 0) 354 | #define gtk_widget_get_app_paintable(wid) ((GTK_WIDGET_FLAGS (wid) & GTK_APP_PAINTABLE) != 0) 355 | #define gtk_widget_get_double_buffered(wid) ((GTK_WIDGET_FLAGS (wid) & GTK_DOUBLE_BUFFERED) != 0) 356 | #define gtk_widget_set_allocation(w,alloc) (GTK_WIDGET(w)->allocation = *(alloc)) 357 | #define gtk_widget_get_allocation(w,alloc) (*(alloc) = GTK_WIDGET(w)->allocation) 358 | #define gtk_widget_set_can_default(w,yes) { \ 359 | if (yes) GTK_WIDGET_SET_FLAGS(w,GTK_CAN_DEFAULT); \ 360 | else GTK_WIDGET_UNSET_FLAGS(w,GTK_CAN_DEFAULT); \ 361 | } 362 | #define gtk_widget_set_can_focus(w,yes) { \ 363 | if (yes) GTK_WIDGET_SET_FLAGS(w,GTK_CAN_FOCUS); \ 364 | else GTK_WIDGET_UNSET_FLAGS(w,GTK_CAN_FOCUS); \ 365 | } 366 | #define gtk_widget_set_has_window(w,yes) { \ 367 | if (yes) GTK_WIDGET_UNSET_FLAGS(w,GTK_NO_WINDOW); \ 368 | else GTK_WIDGET_SET_FLAGS(w,GTK_NO_WINDOW); \ 369 | } 370 | #define gtk_widget_set_visible(w,yes) { \ 371 | if (yes) gtk_widget_show(w); \ 372 | else gtk_widget_hide(w); \ 373 | } 374 | #define gtk_range_get_flippable(range) (GTK_RANGE(range)->flippable) 375 | #define gdk_window_is_destroyed(w) (GDK_WINDOW_DESTROYED (GDK_WINDOW(w))) 376 | #endif 377 | 378 | 379 | // GTK < 2.16 380 | #if GTK_MINOR_VERSION < 16 381 | #define gtk_menu_item_get_label(i) (gtk_label_get_label (GTK_LABEL (GTK_BIN (i)->child))) 382 | #define gtk_menu_item_set_label(i,label) gtk_label_set_label(GTK_LABEL(GTK_BIN(i)->child), (label) ? label : "") 383 | #define gtk_menu_item_get_use_underline(i) (gtk_label_get_use_underline (GTK_LABEL (GTK_BIN (i)->child))) 384 | #define gtk_status_icon_set_tooltip_text gtk_status_icon_set_tooltip 385 | #endif 386 | 387 | 388 | // GTK < 2.14 389 | #if GTK_MINOR_VERSION < 14 390 | #define gtk_dialog_get_action_area(dialog) (GTK_DIALOG(dialog)->action_area) 391 | #define gtk_dialog_get_content_area(dialog) (GTK_DIALOG(dialog)->vbox) 392 | #define gtk_widget_get_window(widget) (GTK_WIDGET(widget)->window) 393 | #define gtk_window_get_default_widget(window) (GTK_WINDOW(window)->default_widget) 394 | #define gtk_menu_item_get_accel_path(i) (GTK_MENU_ITEM(i)->accel_path) 395 | #define gtk_menu_get_accel_path(menu) (GTK_MENU(menu)->accel_path) 396 | #define gtk_message_dialog_get_image(m) (GTK_MESSAGE_DIALOG(m)->image) 397 | #define gtk_entry_get_overwrite_mode(e) (GTK_ENTRY(e)->overwrite_mode) 398 | // --- 399 | #define gtk_adjustment_set_page_increment(a,val) ((a)->page_increment = (val)) 400 | #define gtk_adjustment_get_page_size(a) ((a)->page_size) 401 | #define gtk_adjustment_get_lower(a) ((a)->lower) 402 | #define gtk_adjustment_get_upper(a) ((a)->upper) // GTK_ADJUSTMENT 403 | #define gtk_selection_data_get_length(data) ((data)->length) 404 | #endif 405 | 406 | 407 | #endif /* ------- GTK2 ------- */ 408 | 409 | // =================================================== 410 | 411 | // CAIRO < 1.10 412 | #if CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 10, 0) 413 | #define cairo_region_t GdkRegion 414 | #define cairo_rectangle_int_t GdkRectangle 415 | #define cairo_region_create gdk_region_new 416 | #define cairo_region_copy gdk_region_copy 417 | #define cairo_region_destroy gdk_region_destroy 418 | #define cairo_region_create_rectangle gdk_region_rectangle 419 | #define cairo_region_get_extents gdk_region_get_clipbox 420 | #define cairo_region_is_empty gdk_region_empty 421 | #define cairo_region_equal gdk_region_empty 422 | #define cairo_region_contains_point gdk_region_point_in 423 | #define cairo_region_contains_rectangle gdk_region_rect_in 424 | #define cairo_region_translate gdk_region_rect_in 425 | #define cairo_region_union_rectangle gdk_region_union_with_rect 426 | #define cairo_region_intersect gdk_region_intersect 427 | #define cairo_region_union gdk_region_union 428 | #define cairo_region_subtract gdk_region_subtract 429 | #define cairo_region_xor gdk_region_xor 430 | //#define cairo_region_num_rectangles / cairo_region_get_rectangle gdk_region_get_rectangles 431 | #endif 432 | 433 | 434 | // PANGO 435 | #ifndef PANGO_WEIGHT_MEDIUM 436 | #define PANGO_WEIGHT_MEDIUM 500 437 | #endif 438 | 439 | 440 | #ifdef __cplusplus 441 | } 442 | #endif 443 | 444 | #endif /* __GTKCOMPAT_H */ 445 | -------------------------------------------------------------------------------- /po/Makefile: -------------------------------------------------------------------------------- 1 | 2 | include ../config.mk 3 | 4 | USE_NLS ?= no 5 | LINGUAS ?= 6 | GETTEXT_PACKAGE ?= 7 | 8 | all: all-$(USE_NLS) 9 | all-no: 10 | 11 | all-yes: 12 | @sh zzpo.sh mo 13 | 14 | install: install-data-$(USE_NLS) 15 | install-data-no: all 16 | install-data-yes: 17 | @sh zzpo.sh install "$(DESTDIR)$(localedir)" 18 | 19 | strip: 20 | install-strip: install 21 | 22 | uninstall: 23 | @sh zzpo.sh uninstall "$(DESTDIR)$(localedir)" 24 | 25 | mostlyclean: 26 | rm -f *.new.po *.mo *.gmo 27 | clean: mostlyclean 28 | distclean: mostlyclean 29 | 30 | update-pot: 31 | @sh zzpo.sh pot 32 | 33 | update-po: 34 | @sh zzpo.sh po 35 | 36 | -------------------------------------------------------------------------------- /po/POTFILES: -------------------------------------------------------------------------------- 1 | # generated by zzpo.sh 2 | # this file is only used as a reference to notice if something has changed 3 | ../src/main.c 4 | -------------------------------------------------------------------------------- /po/es.po: -------------------------------------------------------------------------------- 1 | # This file is put in the public domain. 2 | # 3 | #, fuzzy 4 | msgid "" 5 | msgstr "" 6 | "Language: es\n" 7 | "MIME-Version: 1.0\n" 8 | "Content-Type: text/plain; charset=UTF-8\n" 9 | "Content-Transfer-Encoding: 8bit\n" 10 | 11 | #: ../src/main.c:177 ../src/main.c:198 12 | msgid "Program: " 13 | msgstr "Programa: " 14 | 15 | #: ../src/main.c:237 ../src/main.c:577 16 | msgid "Run:" 17 | msgstr "Ejecutar:" 18 | 19 | #: ../src/main.c:245 20 | msgid "unique" 21 | msgstr "único" 22 | 23 | #: ../src/main.c:251 24 | msgid "not unique" 25 | msgstr "no único" 26 | 27 | #: ../src/main.c:257 28 | msgid "not found" 29 | msgstr "no encontrado" 30 | 31 | #: ../src/main.c:265 ../src/main.c:281 32 | msgid "Search:" 33 | msgstr "Buscar:" 34 | 35 | #: ../src/main.c:269 36 | msgid "Search OFF" 37 | msgstr "Búsqueda DESACTIVADA" 38 | 39 | #: ../src/main.c:288 40 | msgid "Not Found!" 41 | msgstr "No encontrado!" 42 | -------------------------------------------------------------------------------- /po/fr.po: -------------------------------------------------------------------------------- 1 | # This file is put in the public domain. 2 | # 3 | #, fuzzy 4 | msgid "" 5 | msgstr "" 6 | "Language: fr\n" 7 | "MIME-Version: 1.0\n" 8 | "Content-Type: text/plain; charset=UTF-8\n" 9 | "Content-Transfer-Encoding: 8bit\n" 10 | 11 | #: ../src/main.c:177 ../src/main.c:198 12 | msgid "Program: " 13 | msgstr "" 14 | 15 | #: ../src/main.c:237 ../src/main.c:577 16 | msgid "Run:" 17 | msgstr "" 18 | 19 | #: ../src/main.c:245 20 | msgid "unique" 21 | msgstr "" 22 | 23 | #: ../src/main.c:251 24 | msgid "not unique" 25 | msgstr "" 26 | 27 | #: ../src/main.c:257 28 | msgid "not found" 29 | msgstr "" 30 | 31 | #: ../src/main.c:265 ../src/main.c:281 32 | msgid "Search:" 33 | msgstr "" 34 | 35 | #: ../src/main.c:269 36 | msgid "Search OFF" 37 | msgstr "" 38 | 39 | #: ../src/main.c:288 40 | msgid "Not Found!" 41 | msgstr "" 42 | -------------------------------------------------------------------------------- /po/gmrun.pot: -------------------------------------------------------------------------------- 1 | # This file is put in the public domain. 2 | # 3 | #, fuzzy 4 | msgid "" 5 | msgstr "" 6 | "Language: \n" 7 | "MIME-Version: 1.0\n" 8 | "Content-Type: text/plain; charset=UTF-8\n" 9 | "Content-Transfer-Encoding: 8bit\n" 10 | 11 | #: ../src/main.c:177 ../src/main.c:198 12 | msgid "Program: " 13 | msgstr "" 14 | 15 | #: ../src/main.c:237 ../src/main.c:577 16 | msgid "Run:" 17 | msgstr "" 18 | 19 | #: ../src/main.c:245 20 | msgid "unique" 21 | msgstr "" 22 | 23 | #: ../src/main.c:251 24 | msgid "not unique" 25 | msgstr "" 26 | 27 | #: ../src/main.c:257 28 | msgid "not found" 29 | msgstr "" 30 | 31 | #: ../src/main.c:265 ../src/main.c:281 32 | msgid "Search:" 33 | msgstr "" 34 | 35 | #: ../src/main.c:269 36 | msgid "Search OFF" 37 | msgstr "" 38 | 39 | #: ../src/main.c:288 40 | msgid "Not Found!" 41 | msgstr "" 42 | -------------------------------------------------------------------------------- /po/he.po: -------------------------------------------------------------------------------- 1 | # This file is put in the public domain. 2 | # 3 | #, fuzzy 4 | msgid "" 5 | msgstr "" 6 | "Language: he\n" 7 | "MIME-Version: 1.0\n" 8 | "Content-Type: text/plain; charset=UTF-8\n" 9 | "Content-Transfer-Encoding: 8bit\n" 10 | 11 | #: ../src/main.c:177 ../src/main.c:198 12 | msgid "Program: " 13 | msgstr "תוכנית: " 14 | 15 | #: ../src/main.c:237 ../src/main.c:577 16 | msgid "Run:" 17 | msgstr "הפעל:" 18 | 19 | #: ../src/main.c:245 20 | msgid "unique" 21 | msgstr "מיוחד" 22 | 23 | #: ../src/main.c:251 24 | msgid "not unique" 25 | msgstr "לא מיוחד" 26 | 27 | #: ../src/main.c:257 28 | msgid "not found" 29 | msgstr "לא נמצא" 30 | 31 | #: ../src/main.c:265 ../src/main.c:281 32 | msgid "Search:" 33 | msgstr "חיפוש:" 34 | 35 | #: ../src/main.c:269 36 | msgid "Search OFF" 37 | msgstr "חיפוש כבוי" 38 | 39 | #: ../src/main.c:288 40 | msgid "Not Found!" 41 | msgstr "לא נמצא!" 42 | -------------------------------------------------------------------------------- /po/zzpo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # https://unlicense.org 3 | 4 | # 5 | # param: 6 | # mo - create mo files, `rm *.mo` to update 7 | # pot - update pot file 8 | # po - update pot file + pot files 9 | # 10 | # Utilities: 11 | # - msgfmt (required - create .mo files) 12 | # - xgettext (create .pot) 13 | # - msgmerge (update .po files) 14 | 15 | cd $(dirname "$0") 16 | 17 | if [ -z "$VERSION" ] ; then 18 | if [ -f config.sh ] ; then 19 | . ./config.sh 20 | elif [ -f ../config.sh ] ; then 21 | . ../config.sh 22 | elif [ -f ../../config.sh ] ; then 23 | . ../../config.sh 24 | fi 25 | fi 26 | 27 | #========================================================================== 28 | 29 | get_linguas() 30 | { 31 | if [ -z "$LINGUAS" ] ; then 32 | LINGUAS="$(ls *.po | sed 's/\.po$//')" 33 | fi 34 | if [ -z "$LINGUAS" ] ; then 35 | echo "-- There no .po files --" 36 | exit 0 37 | fi 38 | } 39 | 40 | 41 | get_pot_source_files() 42 | { 43 | if [ -z "$POT_SOURCE_FILES" ] ; then 44 | filez=$(find .. -type f -name '*.h' -or -name '*.c' -or -name '*.cc' -or -name '*.cpp' -or -name '*.hh') 45 | if [ -z "$filez" ] ; then 46 | echo "-- No source files have been translated --" 47 | exit 0 48 | fi 49 | fi 50 | #-- 51 | POT_SOURCE_FILES="$(grep '_(' $filez | sed -e 's%^\./%%' -e 's%:.*%%' | sort -u)" 52 | if [ -z "$POT_SOURCE_FILES" ] ; then 53 | echo "-- No source files have been translated --" 54 | rm -f POTFILES 55 | exit 0 56 | fi 57 | echo "# generated by zzpo.sh" > POTFILES 58 | echo "# this file is only used as a reference to notice if something has changed" >> POTFILES 59 | echo "$POT_SOURCE_FILES" >> POTFILES 60 | } 61 | 62 | 63 | get_gettext_package() 64 | { 65 | if [ -z "$GETTEXT_PACKAGE" ] ; then 66 | GETTEXT_PACKAGE=$(ls *.pot | head -n 1 | sed 's%.pot%%') 67 | if [ -z "$GETTEXT_PACKAGE" ] ; then 68 | echo ".pot file is missing" 69 | exit 1 70 | fi 71 | fi 72 | } 73 | 74 | #========================================================================== 75 | 76 | update_pot() 77 | { 78 | get_pot_source_files 79 | get_gettext_package 80 | #-- 81 | echo "GETTEXT_PACKAGE: ${GETTEXT_PACKAGE}.pot" 82 | xgettext \ 83 | --default-domain=${GETTEXT_PACKAGE} \ 84 | --add-comments \ 85 | --no-wrap \ 86 | --keyword=_ \ 87 | --keyword=N_ \ 88 | --from-code=UTF-8 \ 89 | --package-name=${GETTEXT_PACKAGE} \ 90 | -o ${GETTEXT_PACKAGE}.pot2 ${POT_SOURCE_FILES} 91 | if [ $? -ne 0 ] ; then 92 | rm -f ${GETTEXT_PACKAGE}.pot2 93 | exit 1 94 | fi 95 | ( 96 | echo '# This file is put in the public domain.' 97 | # fix charset and remove some unnecesary info 98 | sed \ 99 | -e 's%charset=CHARSET%charset=UTF-8%' \ 100 | -e '/^# /d' \ 101 | -e '/Project-Id-Version/d' \ 102 | -e '/Report-Msgid-Bugs-To/d' \ 103 | -e '/POT-Creation-Date/d' \ 104 | -e '/PO-Revision-Date/d' \ 105 | -e '/Last-Translator/d' \ 106 | -e '/Language-Team/d' \ 107 | ${GETTEXT_PACKAGE}.pot2 108 | ) > ${GETTEXT_PACKAGE}.pot 109 | rm -f ${GETTEXT_PACKAGE}.pot2 110 | } 111 | 112 | 113 | update_po() 114 | { 115 | get_linguas 116 | get_gettext_package 117 | #-- 118 | for lang in ${LINGUAS} 119 | do 120 | printf " %s " "${lang}"; 121 | sed -i -e 's%charset=CHARSET%charset=UTF-8%' ${lang}.po; 122 | if msgmerge ${lang}.po ${GETTEXT_PACKAGE}.pot -o ${lang}.new.po; then 123 | mv -f ${lang}.new.po ${lang}.po || exit 1; 124 | else 125 | echo "msgmerge for $lang failed!"; 126 | rm -f ${lang}.new.po; 127 | fi; 128 | if grep -q 'Language: \\\n' ${lang}.po ; then 129 | # fix language: 130 | sed -i 's%Language: \\n%Language: '${lang}'\\n%' ${lang}.po 131 | fi 132 | done 133 | #sed -i '/#~ /d' po/*.po 134 | exit 135 | } 136 | 137 | 138 | build_mo() 139 | { 140 | get_linguas 141 | MSGFMT=msgfmt # gmsgfmt 142 | # -- 143 | for i in ${LINGUAS} ; do 144 | if ! test -f ${i}.mo ; then 145 | echo "${MSGFMT} -o ${i}.mo ${i}.po" 146 | ${MSGFMT} -o ${i}.mo ${i}.po || exit 1 147 | fi 148 | done 149 | } 150 | 151 | #========================================================================== 152 | 153 | case "$1" in 154 | pot) 155 | update_pot 156 | exit $? 157 | ;; 158 | po) 159 | update_pot 160 | update_po 161 | exit $? 162 | ;; 163 | mo) 164 | build_mo 165 | exit $? 166 | ;; 167 | 168 | # this is only for Makefile 169 | install) 170 | DESTDIR="$2" 171 | if [ -z "$DESTDIR" ] ; then 172 | echo "$0 install: pleasy specify installation directory" 173 | exit 1 174 | fi 175 | #-- 176 | build_mo 177 | get_gettext_package 178 | for cat in ${LINGUAS} 179 | do 180 | dir=${DESTDIR}/${cat}/LC_MESSAGES 181 | mkdir -p ${dir} 182 | install -c -m 644 ${cat}.mo ${dir}/${GETTEXT_PACKAGE}.mo 183 | echo "installing ${cat}.mo as ${dir}/${GETTEXT_PACKAGE}.mo" 184 | done 185 | exit 0 186 | ;; 187 | uninstall) 188 | DESTDIR="$2" 189 | if [ -z "$DESTDIR" ] ; then 190 | echo "$0 install: pleasy specify installation directory" 191 | exit 1 192 | fi 193 | #-- 194 | get_linguas 195 | get_gettext_package 196 | for cat in ${LINGUAS} 197 | do 198 | echo "rm -f ${DESTDIR}/${cat}/LC_MESSAGES/${GETTEXT_PACKAGE}.mo" 199 | rm -f ${DESTDIR}/${cat}/LC_MESSAGES/${GETTEXT_PACKAGE}.mo 200 | done 201 | exit 0 202 | ;; 203 | *) 204 | echo "unrecognized command: $1" 205 | exit 1 206 | ;; 207 | esac 208 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | 2 | include ../config.mk 3 | 4 | APP = gmrun 5 | # ls *.c | sed 's%\.c%.o%g' | tr '\n' ' ' >> Makefile 6 | OBJS = config_prefs.o gtkcompletionline.o history.o main.o 7 | 8 | #CPPFLAGS += -I.. 9 | CFLAGS += -I.. 10 | 11 | CFLAGS += $(GTK_CFLAGS) 12 | LIBS += $(GTK_LIBS) 13 | 14 | all: $(APP) 15 | 16 | $(APP): $(OBJS) 17 | $(CC) $(CFLAGS) $(OBJS) -o $(APP) $(LDFLAGS) $(LIBS) 18 | 19 | strip: $(APP) 20 | $(STRIP) $(APP) 21 | 22 | clean: 23 | rm -f *.o *~ $(APP) $(APP).exe 24 | 25 | distclean: clean 26 | 27 | install: $(APP) 28 | mkdir -p $(DESTDIR)$(bindir) 29 | install -c $(APP) $(DESTDIR)$(bindir) 30 | 31 | install-strip: strip install 32 | 33 | uninstall: 34 | rm -f $(DESTDIR)$(bindir)/$(APP) 35 | 36 | -------------------------------------------------------------------------------- /src/config_prefs.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * For more information, please refer to 5 | */ 6 | 7 | /* 8 | * Read config file formatted like this: 9 | * 10 | * key1 = value1 11 | * key2=value2 12 | * 13 | * It's possible to open several files with the same config 14 | * The keys will be updated with thew new values. 15 | * 'key' is not case sensitive 16 | */ 17 | 18 | #include "config_prefs.h" 19 | 20 | #define APP_CONFIG_FILE "gmrunrc" 21 | 22 | // ============================================================ 23 | // PRIVATE 24 | // ============================================================ 25 | 26 | static GList * PrefsGList = NULL; 27 | static GList * ExtensionGList = NULL; 28 | 29 | struct _pref_item 30 | { 31 | char * key; 32 | char * value; 33 | }; 34 | typedef struct _pref_item pref_item; 35 | 36 | 37 | static void pref_item_free (pref_item * item) 38 | { 39 | if (item) { 40 | if (item->key) free (item->key); 41 | if (item->value) free (item->value); 42 | free (item); 43 | } 44 | } 45 | 46 | static void pref_item_free_cb (gpointer data) 47 | { 48 | pref_item_free ((pref_item *) data); 49 | } 50 | 51 | 52 | static GList * config_find_key (GList * list, const char * key) 53 | { 54 | GList *i; 55 | pref_item * item; 56 | 57 | if (!key || !*key) { /* ignore empty keys (strings) */ 58 | return (NULL); 59 | } 60 | 61 | for (i = list; i; i = i->next) 62 | { 63 | item = (pref_item *) (i->data); 64 | if (strcasecmp (key, item->key) == 0) { 65 | return (i); 66 | } 67 | } 68 | return (NULL); /* key not found */ 69 | } 70 | 71 | 72 | static void config_replace_key (GList ** list, pref_item * item) 73 | { 74 | GList * found = config_find_key (*list, item->key); 75 | if (found) { 76 | /* only update found item */ 77 | pref_item * found_item = (pref_item *) (found->data); 78 | if (strcmp (found_item->value, item->value) == 0) { 79 | pref_item_free (item); 80 | return; /* values are equal, nothing to update */ 81 | } 82 | free (found_item->value); 83 | found_item->value = strdup (item->value); 84 | pref_item_free (item); 85 | } else { 86 | /* append item */ 87 | *list = g_list_append (*list, (gpointer) item); 88 | } 89 | } 90 | 91 | 92 | /** get value, it's always a string **/ 93 | static char * config_get_item_value (GList * list, const char * key) 94 | { 95 | GList * ret; 96 | pref_item * item; 97 | 98 | ret = config_find_key (list, key); 99 | if (ret) { 100 | item = (pref_item *) (ret->data); 101 | return (item->value); 102 | } 103 | return (NULL); /* key not found */ 104 | } 105 | 106 | 107 | static void config_load_from_file (const char * filename, GList ** out_list) 108 | { 109 | FILE *fp; 110 | char buf[1024]; 111 | 112 | char * stripped; 113 | char ** keyvalue; 114 | pref_item * item; 115 | 116 | fp = fopen (filename, "r"); 117 | if (!fp) { 118 | return; 119 | } 120 | 121 | /* Read file line by line */ 122 | while (fgets (buf, sizeof (buf), fp)) 123 | { 124 | stripped = buf; 125 | while (*stripped && *stripped <= 0x20) { // 32 = space 126 | stripped++; 127 | } 128 | if (strlen (stripped) < 3 || *stripped == '#') { 129 | continue; 130 | } 131 | if (!strchr (stripped, '=')) { 132 | continue; 133 | } 134 | 135 | item = (pref_item *) calloc (1, sizeof (pref_item)); 136 | keyvalue = g_strsplit (stripped, "=", 2); 137 | item->key = g_strstrip (keyvalue[0]); 138 | item->value = g_strstrip (keyvalue[1]); 139 | 140 | if (!*item->key || !*item->value) { 141 | g_strfreev (keyvalue); 142 | free (item); 143 | continue; 144 | } 145 | 146 | /// fprintf (stderr, "### %s = %s\n", key, value); 147 | /* Insert or replace item */ 148 | config_replace_key (out_list, item); 149 | } 150 | 151 | fclose (fp); 152 | return; 153 | } 154 | 155 | 156 | static void create_extension_handler_list (void) 157 | { 158 | GList * i; 159 | pref_item * item, * item_out; 160 | char ** str_vector; 161 | 162 | for (i = PrefsGList; i; i = i->next) 163 | { 164 | item = (pref_item *) (i->data); 165 | if (strncasecmp (item->key, "EXT:", 4) == 0) 166 | { 167 | int w; 168 | str_vector = g_strsplit (item->key + 4, ",", 0); 169 | for (w = 0; str_vector[w]; w++) 170 | { 171 | item_out = (pref_item *) calloc (1, sizeof (pref_item)); 172 | item_out->key = strdup (str_vector[w]); 173 | item_out->value = strdup (item->value); 174 | config_replace_key (&ExtensionGList, item_out); 175 | } 176 | g_strfreev (str_vector); 177 | } 178 | } 179 | } 180 | 181 | 182 | static char * replace_variable (char * txt) /* config_get_string_expanded() */ 183 | { 184 | // pre${variable}post : ${Terminal} -e ... 185 | char * pre = NULL, * post = NULL; 186 | char * variable = NULL; 187 | char * variable_value = NULL; 188 | char * new_text = NULL; 189 | char * p, * p2; 190 | 191 | if (strlen (txt) < 5) { // at least ${xx} 192 | return (NULL); 193 | } 194 | p = strstr (txt, "${"); 195 | if (!p) { 196 | return (NULL); // syntax error 197 | } 198 | if (!strchr (p + 3, '}')) { 199 | return (NULL); // syntax error 200 | } 201 | 202 | if (txt[0] != '$' && txt[1] != '$') { 203 | pre = strdup (txt); 204 | p2 = strchr (pre, '$'); 205 | if (p2) p2 = 0; 206 | } 207 | 208 | variable = strdup (p + 2); // variable start 209 | p2 = strchr (variable, '}'); // variable end 210 | *p2 = 0; // `Terminal` 211 | post = p2 + 1; // ` -e ...` 212 | 213 | variable_value = config_get_item_value (PrefsGList, variable); // xterm 214 | 215 | if (variable_value) { 216 | if (pre) { 217 | // pre xterm -e ... 218 | new_text = g_strconcat (pre, variable_value, post, NULL); 219 | } else { 220 | // xterm -e ... 221 | new_text = g_strconcat (variable_value, post, NULL); 222 | } 223 | } 224 | 225 | if (pre) free (pre); 226 | if (variable) free (variable); 227 | 228 | return (new_text); 229 | } 230 | 231 | 232 | // ============================================================ 233 | // PUBLIC 234 | // ============================================================ 235 | 236 | void config_init () 237 | { 238 | if (PrefsGList) { 239 | return; 240 | } 241 | 242 | char * config_file = g_build_filename ("/etc", APP_CONFIG_FILE, NULL); 243 | config_load_from_file (config_file, &PrefsGList); 244 | g_free (config_file); 245 | 246 | #ifdef FOLLOW_XDG_SPEC 247 | config_file = g_build_filename (g_get_user_config_dir(), APP_CONFIG_FILE, NULL); 248 | #else 249 | config_file = g_build_filename (g_get_home_dir(), "." APP_CONFIG_FILE, NULL); 250 | #endif 251 | config_load_from_file (config_file, &PrefsGList); 252 | g_free (config_file); 253 | 254 | create_extension_handler_list (); 255 | } 256 | 257 | 258 | void config_destroy () 259 | { 260 | if (PrefsGList) { 261 | g_list_free_full (PrefsGList, pref_item_free_cb); 262 | g_list_free_full (ExtensionGList, pref_item_free_cb); 263 | PrefsGList = NULL; 264 | ExtensionGList = NULL; 265 | } 266 | } 267 | 268 | 269 | void config_reload () 270 | { 271 | config_destroy (); 272 | config_init (); 273 | } 274 | 275 | 276 | void config_print () 277 | { 278 | GList * i; 279 | pref_item * item; 280 | for (i = PrefsGList; i; i = i->next) 281 | { 282 | item = (pref_item *) (i->data); 283 | printf ("%s = %s\n", item->key, item->value); 284 | } 285 | for (i = ExtensionGList; i; i = i->next) 286 | { 287 | item = (pref_item *) (i->data); 288 | printf ("%s = %s\n", item->key, item->value); 289 | } 290 | } 291 | 292 | 293 | gboolean config_get_int (const char * key, int * out_int) 294 | { 295 | char * value; 296 | value = config_get_item_value (PrefsGList, key); 297 | if (value) { 298 | *out_int = (int) strtoll (value, NULL, 0); 299 | return TRUE; 300 | } else { 301 | *out_int = -1; 302 | return FALSE; 303 | } 304 | } 305 | 306 | 307 | // returns a string that must be freed with g_free 308 | gboolean config_get_string_expanded (const char * key, char ** out_str) 309 | { 310 | char * value1, * value2, * value = NULL; 311 | 312 | value1 = config_get_item_value (PrefsGList, key); 313 | if (value1 && strstr (value1, "${")) { 314 | value2 = replace_variable (value1); 315 | value = value2; 316 | // expand variable up to 2 times 317 | if (value2 && strstr (value2, "${")) { 318 | value = replace_variable (value2); 319 | free (value2); 320 | } 321 | } else if (value1) { 322 | value = strdup (value1); 323 | } 324 | 325 | if (value) { 326 | *out_str = value; 327 | return TRUE; 328 | } else { 329 | *out_str = NULL; 330 | return FALSE; 331 | } 332 | } 333 | 334 | 335 | gboolean config_get_string (const char * key, char ** out_str) 336 | { 337 | char * value; 338 | value = config_get_item_value (PrefsGList, key); 339 | if (value) { 340 | *out_str = value; 341 | return TRUE; 342 | } else { 343 | *out_str = NULL; 344 | return FALSE; 345 | } 346 | } 347 | 348 | 349 | char * config_get_handler_for_extension (const char * extension) 350 | { 351 | char * handler; 352 | if (extension && *extension == '.') { 353 | extension++; // .html -> html 354 | } 355 | handler = config_get_item_value (ExtensionGList, extension); 356 | return (handler); 357 | } 358 | -------------------------------------------------------------------------------- /src/config_prefs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * For more information, please refer to 5 | */ 6 | 7 | #ifndef __CONFIG_PREFS_H 8 | #define __CONFIG_PREFS_H 9 | 10 | #ifdef __cplusplus 11 | extern "C" 12 | { 13 | #endif 14 | 15 | #include "gtkcompat.h" // glib-compat.h 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | void config_init (); 22 | void config_destroy (); 23 | void config_reload (); 24 | void config_print (); 25 | 26 | gboolean config_get_int (const char * key, int * out_int); 27 | 28 | /// changes string pointer (must not be freed) 29 | gboolean config_get_string (const char * key, char ** out_str); 30 | 31 | /// allocates a string that must be freed with g_free 32 | gboolean config_get_string_expanded (const char * key, char ** out_str); 33 | 34 | /// returns a constant string (must not be freed) 35 | char * config_get_handler_for_extension (const char * extension); 36 | 37 | #ifdef __cplusplus 38 | } 39 | #endif 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/gtkcompletionline.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Mihai Bazon 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software 5 | * for any purpose with or without fee is hereby granted, provided that 6 | * the above copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES 10 | * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 11 | * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR 12 | * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 13 | * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 14 | * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "config_prefs.h" 33 | #include "gtkcompletionline.h" 34 | 35 | #define HISTORY_FILE "gmrun_history" 36 | 37 | #ifndef PATH_MAX 38 | #define PATH_MAX 4096 39 | #endif 40 | 41 | static int on_cursor_changed_handler = 0; 42 | static int on_key_press_handler = 0; 43 | static guint timeout_id = 0; 44 | 45 | /* history search - backwards / fordwards -- see history.c */ 46 | const char * (*history_search_first_func) (HistoryFile *); 47 | const char * (*history_search_next_func) (HistoryFile *); 48 | static gboolean searching_history = FALSE; 49 | 50 | /* GLOBALS */ 51 | 52 | /* signals */ 53 | enum { 54 | UNIQUE, 55 | NOTUNIQUE, 56 | INCOMPLETE, 57 | RUNWITHTERM, 58 | SEARCH_MODE, 59 | SEARCH_LETTER, 60 | SEARCH_NOT_FOUND, 61 | EXT_HANDLER, 62 | CANCEL, 63 | LAST_SIGNAL 64 | }; 65 | 66 | static guint gtk_completion_line_signals[LAST_SIGNAL]; 67 | 68 | /* string list containing each directory in PATH */ 69 | static char ** path_strv = NULL; 70 | static GList * path_glist = NULL; 71 | 72 | static gchar * prefix = NULL; 73 | static int g_show_dot_files; 74 | 75 | /* callbacks */ 76 | static gboolean on_key_press (GtkCompletionLine *cl, GdkEventKey *event, gpointer data); 77 | static gboolean on_scroll (GtkCompletionLine *cl, GdkEventScroll *event, gpointer data); 78 | 79 | // https://developer.gnome.org/gobject/stable/gobject-Type-Information.html#G-DEFINE-TYPE-EXTENDED:CAPS 80 | G_DEFINE_TYPE_EXTENDED (GtkCompletionLine, /* type name */ 81 | gtk_completion_line, /* type name in lowercase, separated by '_' */ 82 | GTK_TYPE_ENTRY, /* GType of the parent type */ 83 | (GTypeFlags)0, 84 | NULL); 85 | static void gtk_completion_line_dispose (GObject *object); 86 | static void gtk_completion_line_finalize (GObject *object); 87 | // see also https://developer.gnome.org/gobject/stable/GTypeModule.html#G-DEFINE-DYNAMIC-TYPE:CAPS 88 | 89 | /* class_init */ 90 | static void gtk_completion_line_class_init (GtkCompletionLineClass *klass) 91 | { 92 | GtkWidgetClass * object_class = GTK_WIDGET_CLASS (klass); 93 | guint s; 94 | 95 | GObjectClass * basic_class = G_OBJECT_CLASS (klass); 96 | basic_class->dispose = gtk_completion_line_dispose; 97 | basic_class->finalize = gtk_completion_line_finalize; 98 | 99 | s = g_signal_new ("unique", 100 | G_TYPE_FROM_CLASS (object_class), 101 | G_SIGNAL_RUN_FIRST, 102 | G_STRUCT_OFFSET (GtkCompletionLineClass, unique), 103 | NULL, 104 | NULL, 105 | g_cclosure_marshal_VOID__VOID, 106 | G_TYPE_NONE, /* return_type */ 107 | 0); /* n_params */ 108 | gtk_completion_line_signals[UNIQUE] = s; 109 | 110 | s = g_signal_new ("notunique", 111 | G_TYPE_FROM_CLASS (object_class), 112 | G_SIGNAL_RUN_FIRST, 113 | G_STRUCT_OFFSET (GtkCompletionLineClass, notunique), 114 | NULL, 115 | NULL, 116 | g_cclosure_marshal_VOID__VOID, 117 | G_TYPE_NONE, /* return_type */ 118 | 0); /* n_params */ 119 | gtk_completion_line_signals[NOTUNIQUE] = s; 120 | 121 | s = g_signal_new ("incomplete", 122 | G_TYPE_FROM_CLASS (object_class), 123 | G_SIGNAL_RUN_FIRST, 124 | G_STRUCT_OFFSET (GtkCompletionLineClass, incomplete), 125 | NULL, 126 | NULL, 127 | g_cclosure_marshal_VOID__VOID, 128 | G_TYPE_NONE, /* return_type */ 129 | 0); /* n_params */ 130 | gtk_completion_line_signals[INCOMPLETE] = s; 131 | 132 | s = g_signal_new ("runwithterm", 133 | G_TYPE_FROM_CLASS (object_class), 134 | G_SIGNAL_RUN_FIRST, 135 | G_STRUCT_OFFSET (GtkCompletionLineClass, runwithterm), 136 | NULL, 137 | NULL, 138 | g_cclosure_marshal_VOID__VOID, 139 | G_TYPE_NONE, /* return_type */ 140 | 0); /* n_params */ 141 | gtk_completion_line_signals[RUNWITHTERM] = s; 142 | 143 | s = g_signal_new ("search_mode", 144 | G_TYPE_FROM_CLASS (object_class), 145 | G_SIGNAL_RUN_FIRST, 146 | G_STRUCT_OFFSET (GtkCompletionLineClass, search_mode), 147 | NULL, 148 | NULL, 149 | g_cclosure_marshal_VOID__VOID, 150 | G_TYPE_NONE, /* return_type */ 151 | 0); /* n_params */ 152 | gtk_completion_line_signals[SEARCH_MODE] = s; 153 | 154 | s = g_signal_new ("search_not_found", 155 | G_TYPE_FROM_CLASS (object_class), 156 | G_SIGNAL_RUN_FIRST, 157 | G_STRUCT_OFFSET (GtkCompletionLineClass, search_not_found), 158 | NULL, 159 | NULL, 160 | g_cclosure_marshal_VOID__VOID, 161 | G_TYPE_NONE, /* return_type */ 162 | 0); /* n_params */ 163 | gtk_completion_line_signals[SEARCH_NOT_FOUND] = s; 164 | 165 | s = g_signal_new ("search_letter", 166 | G_TYPE_FROM_CLASS (object_class), 167 | G_SIGNAL_RUN_FIRST, 168 | G_STRUCT_OFFSET (GtkCompletionLineClass, search_letter), 169 | NULL, 170 | NULL, 171 | g_cclosure_marshal_VOID__VOID, 172 | G_TYPE_NONE, /* return_type */ 173 | 0); /* n_params */ 174 | gtk_completion_line_signals[SEARCH_LETTER] = s; 175 | 176 | s = g_signal_new ("ext_handler", 177 | G_TYPE_FROM_CLASS (object_class), 178 | G_SIGNAL_RUN_FIRST, 179 | G_STRUCT_OFFSET (GtkCompletionLineClass, ext_handler), 180 | NULL, 181 | NULL, 182 | g_cclosure_marshal_VOID__POINTER, 183 | G_TYPE_NONE, /* return_type */ 184 | 1, /* n_params */ 185 | G_TYPE_POINTER); 186 | gtk_completion_line_signals[EXT_HANDLER] = s; 187 | 188 | s = g_signal_new ("cancel", 189 | G_TYPE_FROM_CLASS (object_class), 190 | G_SIGNAL_RUN_FIRST, 191 | G_STRUCT_OFFSET (GtkCompletionLineClass, cancel), 192 | NULL, 193 | NULL, 194 | g_cclosure_marshal_VOID__POINTER, 195 | G_TYPE_NONE, /* return_type */ 196 | 1, /* n_params */ 197 | G_TYPE_POINTER); 198 | gtk_completion_line_signals[CANCEL] = s; 199 | 200 | klass->unique = NULL; 201 | klass->notunique = NULL; 202 | klass->incomplete = NULL; 203 | klass->runwithterm = NULL; 204 | klass->search_mode = NULL; 205 | klass->search_letter = NULL; 206 | klass->search_not_found = NULL; 207 | klass->ext_handler = NULL; 208 | klass->cancel = NULL; 209 | } 210 | 211 | /* init */ 212 | static void gtk_completion_line_init (GtkCompletionLine *self) 213 | { 214 | /* Add object initialization / creation stuff here */ 215 | self->win_compl = NULL; 216 | self->list_compl = NULL; 217 | self->sort_list_compl = NULL; 218 | self->tree_compl = NULL; 219 | self->hist_search_mode = FALSE; 220 | self->hist_word[0] = 0; 221 | self->hist_word_count = 0; 222 | self->tabtimeout = 0; 223 | self->show_dot_files = 0; 224 | 225 | // required for gtk3+ 226 | gtk_widget_add_events(GTK_WIDGET(self), GDK_SCROLL_MASK); 227 | 228 | on_key_press_handler = g_signal_connect(G_OBJECT(self), 229 | "key_press_event", 230 | G_CALLBACK(on_key_press), NULL); 231 | g_signal_connect(G_OBJECT(self), "scroll-event", 232 | G_CALLBACK(on_scroll), NULL); 233 | 234 | char * history_file; 235 | #ifdef FOLLOW_XDG_SPEC 236 | history_file = g_build_filename (g_get_user_data_dir(), HISTORY_FILE, NULL); 237 | #else 238 | history_file = g_build_filename (g_get_home_dir(), "." HISTORY_FILE, NULL); 239 | #endif 240 | 241 | int HIST_MAX_SIZE; 242 | if (!config_get_int ("History", &HIST_MAX_SIZE)) 243 | HIST_MAX_SIZE = 20; 244 | 245 | self->hist = history_new (history_file, HIST_MAX_SIZE); 246 | g_free (history_file); 247 | // hacks for prev/next will be applied 248 | history_unset_current (self->hist); 249 | } 250 | 251 | static void gtk_completion_line_dispose (GObject *object) 252 | { 253 | GtkCompletionLine * self = GTK_COMPLETION_LINE (object); 254 | // GTK3: Pango-CRITICAL **: pango_layout_get_cursor_pos: assertion 'index >= 0 && index <= layout->length' failed 255 | // -- for some reason there's an error when the object is destroyed 256 | // -- The GtkCompletionLine 'cancel' signal makes gmrun destroy the object and exit 257 | // -- The current fix is to set an empty text 258 | gtk_entry_set_text (GTK_ENTRY (self), ""); 259 | // -- 260 | if (path_glist) { 261 | g_list_free_full (path_glist, g_free); 262 | path_glist = NULL; 263 | } 264 | G_OBJECT_CLASS (gtk_completion_line_parent_class)->dispose (object); 265 | } 266 | 267 | static void gtk_completion_line_finalize (GObject *object) 268 | { 269 | GtkCompletionLine * self = GTK_COMPLETION_LINE (object); 270 | if (self->hist) { 271 | // 0 = save | 1 = if changed 272 | history_save (self->hist, HISTORY_SAVE_IF_CHANGED); 273 | //history_print (self->hist); //debug 274 | history_destroy (self->hist); 275 | self->hist = NULL; 276 | } 277 | G_OBJECT_CLASS (gtk_completion_line_parent_class)->finalize (object); 278 | } 279 | 280 | // ==================================================================== 281 | 282 | void gtk_completion_line_last_history_item (GtkCompletionLine* object) { 283 | const char *last_item = history_last (object->hist); 284 | if (last_item) { 285 | gtk_entry_set_text (GTK_ENTRY(object), last_item); 286 | } 287 | } 288 | 289 | /* Get one word of a string: separator is space, but only unescaped spaces */ 290 | static const gchar *get_token (const gchar *str, char *out_buf, int out_buf_len) 291 | { 292 | gboolean escaped = FALSE; 293 | *out_buf = 0; 294 | int x = 0; 295 | while (*str != '\0') 296 | { 297 | if (escaped) { 298 | escaped = FALSE; 299 | out_buf[x++] = *str; 300 | } else if (*str == '\\') { 301 | escaped = TRUE; 302 | } else if (isspace(*str)) { 303 | while (isspace(*str)) str++; 304 | break; 305 | } else { 306 | out_buf[x++] = *str; 307 | } 308 | if (x >= out_buf_len) { 309 | break; 310 | } 311 | str++; 312 | } 313 | out_buf[x] = 0; 314 | return str; 315 | } 316 | 317 | /* get words before current edit position */ 318 | int get_words (GtkCompletionLine * object, GList ** words) 319 | { 320 | #ifdef DEBUG 321 | printf (" get_words\n"); 322 | #endif 323 | const gchar * content = gtk_entry_get_text (GTK_ENTRY(object)); 324 | const gchar * i = content; 325 | int pos = gtk_editable_get_position (GTK_EDITABLE(object)); 326 | int n_w = 0; 327 | char tmp[2048] = ""; 328 | 329 | while (*i != '\0') 330 | { 331 | i = get_token (i, tmp, 2000); 332 | *words = g_list_append (*words, g_strdup(tmp)); 333 | if (*i && (i - content < pos) && (i != content)) 334 | n_w++; 335 | } 336 | 337 | if (!*words) { // must add empty string otherwise a segfault awaits 338 | *words = g_list_append (*words, strdup ("")); 339 | } 340 | 341 | return n_w; 342 | } 343 | 344 | /* Replace words in the entry fields, return position of char after first 'pos' words */ 345 | int set_words (GtkCompletionLine *object, GList *words, int pos) 346 | { 347 | #ifdef DEBUG 348 | printf (" set_words\n"); 349 | #endif 350 | GList * igl; 351 | char * word; 352 | char * tmp; 353 | int tmp_len = 0; 354 | 355 | // determine buffer length 356 | for (igl = words; igl; igl = igl->next) { 357 | word = (char*) (igl->data); 358 | tmp_len += strlen (word) + 5; 359 | for (tmp = word; *tmp; tmp++) 360 | if (*tmp == ' ') 361 | tmp_len += 5; 362 | } 363 | tmp = (char*) g_malloc0 (sizeof(char) * tmp_len); 364 | 365 | if (pos == -1) 366 | pos = g_list_length (words) - 1; 367 | int cur = 0; 368 | int i = 0; 369 | 370 | while (words) 371 | { 372 | // replace ' ' with '\ ' [escape] 373 | word = (char *)(words->data); 374 | while (*word) { 375 | if (*word == ' ') { 376 | tmp[i++] = '\\'; 377 | tmp[i++] = ' '; 378 | } else { 379 | tmp[i++] = *word; 380 | } 381 | word++; 382 | } 383 | 384 | // add space if not the last word 385 | if (words != g_list_last (words)) { 386 | tmp[i++] = ' '; 387 | } 388 | if (!pos && !cur) { 389 | cur = strlen (tmp); /* cur: length of string after inserting pos words */ 390 | } else { 391 | --pos; 392 | } 393 | words = words->next; 394 | } 395 | tmp[i] = 0; 396 | 397 | if (g_list_length(words) == 1) { 398 | g_signal_emit_by_name (G_OBJECT(object), "ext_handler", NULL); 399 | } 400 | 401 | gtk_entry_set_text (GTK_ENTRY(object), tmp); 402 | gtk_editable_set_position (GTK_EDITABLE(object), cur); 403 | g_free (tmp); 404 | return cur; 405 | } 406 | 407 | /* Filter callback for scandir */ 408 | static int select_executables_only(const struct dirent* dent) 409 | { 410 | int len = strlen(dent->d_name); 411 | int lenp = prefix ? strlen (prefix) : 0; 412 | 413 | if (dent->d_name[0] == '.') { 414 | if (!g_show_dot_files) 415 | return 0; 416 | if (dent->d_name[1] == '\0') 417 | return 0; 418 | if ((dent->d_name[1] == '.') && (dent->d_name[2] == '\0')) 419 | return 0; 420 | } 421 | if (dent->d_name[len - 1] == '~') 422 | return 0; 423 | if (lenp == 0) 424 | return 1; 425 | if (lenp > len) 426 | return 0; 427 | 428 | if (strncmp(dent->d_name, prefix, lenp) == 0) 429 | return 1; 430 | 431 | return 0; 432 | } 433 | 434 | /* Iterates though PATH and list all executables */ 435 | static GList * generate_execs_list (char * pfix) 436 | { 437 | int i = 0; 438 | GList *gli; 439 | char *path_cstr = NULL; 440 | struct stat sb; 441 | char * dir; 442 | char resolved_path[PATH_MAX]; 443 | 444 | // generate_path_list 445 | if (!path_glist) 446 | { 447 | path_cstr = (char*) getenv("PATH"); 448 | path_strv = g_strsplit (path_cstr, ":", -1); 449 | for (i = 0; path_strv[i]; i++) 450 | { 451 | // deal with syminks and duplicate dirs 452 | *resolved_path = 0; 453 | lstat (path_strv[i], &sb); 454 | if (S_ISLNK(sb.st_mode)) { 455 | realpath (path_strv[i], resolved_path); 456 | } else if (!S_ISDIR(sb.st_mode)) { 457 | continue; 458 | } 459 | dir = path_strv[i]; 460 | if (*resolved_path) { 461 | dir = resolved_path; 462 | } 463 | // Avoid adding duplicate dirs. No need to search for dup while in first PATH entry 464 | if (i == 0 || !g_list_find_custom (path_glist, dir, (GCompareFunc)g_strcmp0)) { 465 | path_glist = g_list_prepend (path_glist, g_strdup (dir)); 466 | } 467 | } 468 | g_strfreev (path_strv); 469 | } 470 | 471 | GList * execs_gc = NULL; 472 | if (prefix) g_free (prefix); 473 | prefix = g_strdup (pfix); 474 | 475 | for (gli = path_glist; gli; gli = gli->next) 476 | { 477 | struct dirent **eps; 478 | dir = (char *) gli->data; 479 | int n = scandir (dir, &eps, select_executables_only, NULL); 480 | int j; 481 | if (n >= 0) { 482 | for (j = 0; j < n; j++) { 483 | // Avoid adding duplicate entries. No need to search for dup while in first dir 484 | if (gli == path_glist || NULL == g_list_find_custom(execs_gc, eps[j]->d_name, (GCompareFunc)g_strcmp0)) { 485 | execs_gc = g_list_prepend (execs_gc, g_strdup (eps[j]->d_name)); 486 | } 487 | free (eps[j]); 488 | } 489 | free (eps); 490 | } 491 | } 492 | if (prefix) { 493 | g_free (prefix); 494 | prefix = NULL; 495 | } 496 | return (execs_gc); 497 | } 498 | 499 | /* list all subdirs in what, return if ok or not */ 500 | static GList * generate_dirlist (const char * path) 501 | { 502 | #ifdef DEBUG 503 | printf ("generate_dirlist\n"); 504 | #endif 505 | char * str = strdup (path); 506 | char * filename = strrchr (str, '/'); 507 | GList * dirlist_gc = NULL; 508 | 509 | char * p = str; 510 | int slashes = 0; 511 | while (*p) { 512 | if (*p == '/') slashes++; 513 | p++; 514 | } 515 | 516 | char * dir; 517 | if (slashes == 1) { 518 | dir = "/"; 519 | } else { 520 | dir = str; 521 | } 522 | 523 | *filename = '\0'; 524 | filename++; 525 | if (prefix) g_free (prefix); 526 | prefix = g_strdup(filename); 527 | 528 | struct dirent **eps; 529 | char * file; 530 | int n, j, len; 531 | 532 | n = scandir (dir, &eps, select_executables_only, NULL); 533 | if (n >= 0) { 534 | for (j = 0; j < n; j++) 535 | { 536 | file = g_strconcat (str, "/", eps[j]->d_name, "/", NULL); 537 | len = strlen (file); 538 | file[len-1] = 0; 539 | struct stat filestatus; 540 | stat (file, &filestatus); 541 | if (S_ISDIR (filestatus.st_mode)) { 542 | file[len-1] = '/'; 543 | } 544 | dirlist_gc = g_list_prepend (dirlist_gc, file); 545 | free(eps[j]); 546 | } 547 | free(eps); 548 | } 549 | 550 | if (prefix) { 551 | g_free (prefix); 552 | prefix = NULL; 553 | } 554 | free (str); 555 | return (dirlist_gc); 556 | } 557 | 558 | /* Expand tilde */ 559 | static int parse_tilda (GtkCompletionLine *object) 560 | { 561 | const gchar *text = gtk_entry_get_text(GTK_ENTRY(object)); 562 | const gchar *match = g_strstr_len(text, -1, "~"); 563 | if (match) { 564 | gint cur = match - text; 565 | if ((cur > 0) && (text[cur - 1] != ' ')) 566 | return 0; 567 | if ((guint)cur < strlen(text) - 1 && text[cur + 1] != '/') { 568 | // FIXME: Parse another user's home 569 | } else { 570 | gtk_editable_insert_text(GTK_EDITABLE(object), 571 | g_get_home_dir(), strlen(g_get_home_dir()), &cur); 572 | gtk_editable_delete_text(GTK_EDITABLE(object), cur, cur + 1); 573 | } 574 | } 575 | 576 | return 0; 577 | } 578 | 579 | static void complete_from_list (GtkCompletionLine *object, char *cword) /* can be NULL */ 580 | { 581 | #ifdef DEBUG 582 | printf ("\ncomplete_from_list\n"); 583 | #endif 584 | if (on_cursor_changed_handler) { 585 | g_signal_handler_block (G_OBJECT(object->tree_compl), on_cursor_changed_handler); 586 | } 587 | 588 | parse_tilda(object); 589 | GList *words = NULL, *word_i; 590 | int pos = get_words (object, &words); 591 | word_i = g_list_nth (words, pos); 592 | char * word = NULL; 593 | 594 | /* Completion list is opened */ 595 | if (object->win_compl != NULL) { 596 | /* word will point to a dynamycally allocated string */ 597 | gtk_tree_model_get (object->sort_list_compl, &(object->list_compl_it), 0, &word, -1); 598 | 599 | GtkTreePath *path = gtk_tree_model_get_path(object->sort_list_compl, &(object->list_compl_it)); 600 | gtk_tree_view_set_cursor(GTK_TREE_VIEW(object->tree_compl), path, NULL, FALSE); 601 | gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(object->tree_compl), path, NULL, TRUE, 0.5, 0.5); 602 | gtk_tree_path_free(path); 603 | } else { 604 | word = cword ? strdup (cword) : NULL; 605 | } 606 | 607 | if (word) { 608 | g_free (word_i->data); 609 | word_i->data = word; 610 | } 611 | int current_pos = set_words (object, words, pos); 612 | gtk_editable_select_region (GTK_EDITABLE(object), object->pos_in_text, current_pos); 613 | 614 | g_list_free_full (words, g_free); 615 | 616 | if (on_cursor_changed_handler) { 617 | g_signal_handler_unblock (G_OBJECT(object->tree_compl), on_cursor_changed_handler); 618 | } 619 | } 620 | 621 | static void on_cursor_changed(GtkTreeView *tree, gpointer data) 622 | { 623 | #ifdef DEBUG 624 | printf ("on_cusor_changed\n"); 625 | #endif 626 | GtkCompletionLine *object = GTK_COMPLETION_LINE(data); 627 | 628 | GtkTreeSelection *selection; 629 | selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(object->tree_compl)); 630 | gtk_tree_selection_get_selected (selection, &(object->sort_list_compl), &(object->list_compl_it)); 631 | 632 | complete_from_list (object, NULL); 633 | } 634 | 635 | void compline_clear_selection (GtkCompletionLine* cl) 636 | { 637 | int pos = gtk_editable_get_position (GTK_EDITABLE (cl)); 638 | gtk_editable_select_region (GTK_EDITABLE(cl), pos, pos); 639 | } 640 | 641 | 642 | // called by tab_pressed() only if completion window doesn't exist 643 | static void complete_line (GtkCompletionLine *object) 644 | { 645 | #ifdef DEBUG 646 | printf ("complete_line\n"); 647 | #endif 648 | parse_tilda(object); 649 | 650 | GList * WordList = NULL, * witem = NULL; 651 | GList * FileList = NULL; 652 | int pos = get_words (object, &WordList); 653 | witem = g_list_nth (WordList, pos); 654 | char * word = (gchar *)(witem->data); 655 | 656 | g_show_dot_files = object->show_dot_files; 657 | 658 | /* populate FileList */ 659 | if (word[0] != '/') { /* exec list */ 660 | FileList = generate_execs_list (word); 661 | } else { /* dirlist */ 662 | FileList = generate_dirlist (word); 663 | } 664 | 665 | guint num_items = FileList ? g_list_length (FileList) : 0; 666 | 667 | if (num_items == 1) { // only 1 item 668 | complete_from_list (object, (char*)(FileList->data)); 669 | g_signal_emit_by_name(G_OBJECT(object), "unique"); 670 | compline_clear_selection (object); 671 | g_list_free_full (WordList, g_free); 672 | g_list_free_full (FileList, g_free); 673 | return; 674 | } else if (num_items == 0) { 675 | g_signal_emit_by_name(G_OBJECT(object), "incomplete"); 676 | g_list_free_full (WordList, g_free); 677 | g_list_free_full (FileList, g_free); 678 | return; 679 | } 680 | 681 | /*** num_items > 1 ***/ 682 | g_signal_emit_by_name (G_OBJECT(object), "notunique"); 683 | 684 | object->win_compl = gtk_window_new (GTK_WINDOW_POPUP); 685 | gtk_widget_set_name (object->win_compl, "gmrun_completion_window"); 686 | 687 | GtkWidget * main_win = gtk_widget_get_toplevel (GTK_WIDGET (object)); 688 | gtk_window_set_transient_for (GTK_WINDOW (object->win_compl), GTK_WINDOW (main_win)); 689 | 690 | /* attemp to silence warning: Gtk-WARNING **: Allocating size to Window ... 691 | https://git.eclipse.org/c/platform/eclipse.platform.swt.git/commit/?id=61a598af4dfda586b27d87537bb2d015bd614ba1 692 | https://sources.debian.org/src/clutter-gtk/1.8.2-2/clutter-gtk/gtk-clutter-actor.c/?hl=325#L325 693 | https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=867427 694 | */ 695 | #if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 20 && GTK_MINOR_VERSION < 24 696 | // not a proper fix 697 | GtkRequisition r, r2; 698 | gtk_widget_get_preferred_size (GTK_WIDGET (object->win_compl), &r, &r2); 699 | GtkAllocation wal = { 0, 0, r2.width, r.height }; 700 | gtk_widget_size_allocate (GTK_WIDGET (object->win_compl), &wal); 701 | #endif 702 | 703 | object->list_compl = gtk_list_store_new (1, G_TYPE_STRING); 704 | object->sort_list_compl = GTK_TREE_MODEL (object->list_compl); 705 | object->tree_compl = gtk_tree_view_new_with_model (object->sort_list_compl); 706 | g_object_unref (object->list_compl); 707 | 708 | /* fill ListStore before sorting */ 709 | GtkTreeIter iter; 710 | GList *p = FileList; 711 | while (p) { 712 | gtk_list_store_append (object->list_compl, &iter); /* modifies iter */ 713 | gtk_list_store_set (object->list_compl, &iter, 714 | 0, (char*) p->data, -1); 715 | p = g_list_next (p); 716 | } 717 | 718 | /* sort ListStore, column 0 */ 719 | GtkTreeSortable * sorted = GTK_TREE_SORTABLE (object->list_compl); 720 | gtk_tree_sortable_set_sort_column_id (sorted, 0, GTK_SORT_ASCENDING); 721 | 722 | GtkTreeViewColumn *col; 723 | GtkCellRenderer *renderer; 724 | col = gtk_tree_view_column_new (); 725 | renderer = gtk_cell_renderer_text_new (); 726 | gtk_tree_view_column_pack_start (col, renderer, TRUE); 727 | gtk_tree_view_column_add_attribute (col, renderer, "text", 0); 728 | gtk_tree_view_append_column (GTK_TREE_VIEW (object->tree_compl), col); 729 | 730 | GtkTreeSelection *selection; 731 | selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (object->tree_compl)); 732 | gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); 733 | gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (object->tree_compl), FALSE); 734 | 735 | on_cursor_changed_handler = g_signal_connect (GTK_TREE_VIEW (object->tree_compl), 736 | "cursor-changed", 737 | G_CALLBACK (on_cursor_changed), object); 738 | g_signal_handler_block (G_OBJECT (object->tree_compl), 739 | on_cursor_changed_handler); 740 | 741 | GtkWidget *scroll = gtk_scrolled_window_new (NULL, NULL); 742 | gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_OUT); 743 | gtk_widget_show (scroll); 744 | gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), 745 | GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); 746 | gtk_container_set_border_width (GTK_CONTAINER (object->tree_compl), 2); 747 | gtk_container_add (GTK_CONTAINER (scroll), object->tree_compl); 748 | gtk_container_add (GTK_CONTAINER (object->win_compl), scroll); 749 | 750 | GdkWindow *top = gtk_widget_get_parent_window (GTK_WIDGET (object)); 751 | int x, y; 752 | gdk_window_get_position(top, &x, &y); 753 | GtkAllocation al; 754 | gtk_widget_get_allocation( GTK_WIDGET(object), &al ); 755 | x += al.x; 756 | y += al.y + al.height; 757 | 758 | // gtk_widget_popup(object->win_compl, x, y); 759 | gtk_window_move (GTK_WINDOW (object->win_compl), x, y); 760 | gtk_widget_show_all (object->win_compl); 761 | 762 | gtk_tree_view_columns_autosize (GTK_TREE_VIEW (object->tree_compl)); 763 | gtk_widget_get_allocation (object->tree_compl, &al); 764 | gtk_widget_set_size_request (scroll, al.width + 40, 150); 765 | 766 | gtk_tree_model_get_iter_first (object->sort_list_compl, &(object->list_compl_it)); 767 | gtk_tree_selection_select_iter (selection, &(object->list_compl_it)); 768 | 769 | g_signal_handler_unblock (G_OBJECT(object->tree_compl), 770 | on_cursor_changed_handler); 771 | 772 | // completion has been created, now use the 1st item from TreeView 773 | object->pos_in_text = gtk_editable_get_position (GTK_EDITABLE (object)); 774 | complete_from_list (object, NULL); 775 | 776 | g_list_free_full (WordList, g_free); 777 | g_list_free_full (FileList, g_free); 778 | return; 779 | } 780 | 781 | 782 | GtkWidget * gtk_completion_line_new() 783 | { 784 | return GTK_WIDGET(g_object_new(gtk_completion_line_get_type(), NULL)); 785 | } 786 | 787 | 788 | static void up_history (GtkCompletionLine* cl) 789 | { 790 | static int pause = 0; 791 | const char * text_up; 792 | if (pause == 1) { 793 | text_up = history_last (cl->hist); 794 | pause = 0; 795 | } else { 796 | text_up = history_prev (cl->hist); 797 | if (!text_up) { // empty, set a flag, next time we'll get something 798 | pause = 1; 799 | text_up = ""; 800 | } 801 | } 802 | if (text_up) { 803 | gtk_entry_set_text (GTK_ENTRY(cl), text_up); 804 | } 805 | } 806 | 807 | static void down_history (GtkCompletionLine* cl) 808 | { 809 | static int pause = 0; 810 | const char * text_down; 811 | if (pause == 1) { 812 | text_down = history_first (cl->hist); 813 | pause = 0; 814 | } else { 815 | text_down = history_next (cl->hist); 816 | if (!text_down) { // empty, set a flag, next time we'll get something 817 | pause = 1; 818 | text_down = ""; 819 | } 820 | } 821 | if (text_down) { 822 | gtk_entry_set_text (GTK_ENTRY(cl), text_down); 823 | } 824 | } 825 | 826 | 827 | static void search_off (GtkCompletionLine* cl) 828 | { 829 | cl->hist_search_mode = FALSE; 830 | int i; 831 | for (i = 0; i < MAX_HISTWORD_CHARS; i++) { 832 | cl->hist_word[i] = 0; 833 | } 834 | g_signal_emit_by_name (G_OBJECT(cl), "search_mode"); 835 | history_unset_current (cl->hist); 836 | } 837 | 838 | 839 | static void search_history (GtkCompletionLine* cl, int next) 840 | { // must only be called if cl->hist_search_mode = TRUE 841 | /* a key is pressed and added to cl->hist_word */ 842 | searching_history = TRUE; 843 | if (cl->hist_word[0]) 844 | { 845 | const char * history_current_item = NULL; 846 | const char * search_str = cl->hist_word; 847 | if (next) { 848 | history_current_item = history_search_next_func (cl->hist); 849 | } else { 850 | history_current_item = history_search_first_func (cl->hist); 851 | } 852 | 853 | int search_str_len = 0; 854 | int search_match_start = cl->hist_search_match_start; 855 | if (search_match_start) { /* ! */ 856 | search_str_len = strlen (search_str); 857 | } 858 | 859 | while (1) 860 | { 861 | const char * s = NULL; 862 | if (history_current_item) { 863 | if (search_match_start) { /* ! */ 864 | if (strncmp (history_current_item, search_str, search_str_len) == 0) { 865 | s = history_current_item; 866 | } 867 | } else { /* CTRL-R / CTRL-S */ 868 | s = strstr (history_current_item, search_str); 869 | } 870 | } 871 | if (s) { 872 | gtk_entry_set_text (GTK_ENTRY(cl), history_current_item); 873 | g_signal_emit_by_name (G_OBJECT(cl), "search_letter"); 874 | return; 875 | } 876 | history_current_item = history_search_next_func (cl->hist); 877 | if (history_current_item == NULL) { 878 | g_signal_emit_by_name (G_OBJECT(cl), "search_not_found"); 879 | break; 880 | } 881 | } 882 | } 883 | g_signal_emit_by_name (G_OBJECT(cl), "search_letter"); 884 | searching_history = FALSE; 885 | } 886 | 887 | static guint tab_pressed(GtkCompletionLine* cl) 888 | { 889 | #ifdef DEBUG 890 | printf ("tab_pressed\n"); 891 | #endif 892 | if (cl->hist_search_mode == TRUE) { 893 | search_off(cl); 894 | } 895 | if (cl->win_compl) { 896 | // completion window exists, avoid calling complete_line() 897 | gboolean valid = gtk_tree_model_iter_next (cl->sort_list_compl, &(cl->list_compl_it)); 898 | if(!valid) { 899 | gtk_tree_model_get_iter_first (cl->sort_list_compl, &(cl->list_compl_it)); 900 | } 901 | complete_from_list (cl, NULL); 902 | } else { // complete_line () 903 | complete_line (cl); 904 | } 905 | timeout_id = 0; 906 | return FALSE; 907 | } 908 | 909 | static void destroy_completion_window (GtkCompletionLine *cl) 910 | { 911 | if (on_cursor_changed_handler) { 912 | g_signal_handler_block (G_OBJECT (cl->tree_compl), on_cursor_changed_handler); 913 | } 914 | gtk_list_store_clear (cl->list_compl); 915 | gtk_widget_destroy (cl->tree_compl); 916 | gtk_widget_destroy (cl->win_compl); 917 | cl->list_compl = NULL; 918 | cl->tree_compl = NULL; 919 | cl->win_compl = NULL; 920 | on_cursor_changed_handler = 0; 921 | } 922 | 923 | 924 | static gboolean 925 | on_scroll(GtkCompletionLine *cl, GdkEventScroll *event, gpointer data) 926 | { 927 | // https://developer.gnome.org/gdk2/stable/gdk2-Event-Structures.html#GdkEventScroll 928 | // https://developer.gnome.org/gdk3/stable/gdk3-Event-Structures.html#GdkEventScroll 929 | if (event->type != GDK_SCROLL) { 930 | return FALSE; 931 | } 932 | GdkScrollDirection direction; 933 | direction = event->direction; 934 | if (direction == GDK_SCROLL_UP) { 935 | if (cl->win_compl != NULL) { 936 | gboolean valid; 937 | valid = gtk_tree_model_iter_previous(cl->sort_list_compl, &(cl->list_compl_it)); 938 | if(!valid) { 939 | int rowCount = gtk_tree_model_iter_n_children (cl->sort_list_compl, NULL); 940 | gtk_tree_model_iter_nth_child(cl->sort_list_compl, &(cl->list_compl_it), NULL, rowCount - 1); 941 | } 942 | complete_from_list (cl, NULL); 943 | } else { 944 | up_history(cl); 945 | } 946 | if (cl->hist_search_mode == TRUE) { 947 | search_off(cl); 948 | } 949 | return TRUE; 950 | } else if (direction == GDK_SCROLL_DOWN) { 951 | if (cl->win_compl != NULL) { 952 | gboolean valid = gtk_tree_model_iter_next(cl->sort_list_compl, &(cl->list_compl_it)); 953 | if(!valid) { 954 | gtk_tree_model_get_iter_first(cl->sort_list_compl, &(cl->list_compl_it)); 955 | } 956 | complete_from_list (cl, NULL); 957 | } else { 958 | down_history(cl); 959 | } 960 | if (cl->hist_search_mode == TRUE) { 961 | search_off(cl); 962 | } 963 | return TRUE; 964 | } 965 | return FALSE; 966 | } 967 | 968 | static gboolean 969 | on_key_press(GtkCompletionLine *cl, GdkEventKey *event, gpointer data) 970 | { // https://developer.gnome.org/gtk2/stable/GtkWidget.html#GtkWidget-key-press-event 971 | int key = event->keyval; 972 | switch (key) 973 | { 974 | case GDK_KEY(Control_R): 975 | case GDK_KEY(Control_L): 976 | case GDK_KEY(Shift_R): 977 | case GDK_KEY(Shift_L): 978 | case GDK_KEY(Alt_R): 979 | case GDK_KEY(Alt_L): 980 | break; 981 | 982 | case GDK_KEY(Tab): 983 | if (timeout_id != 0) { 984 | g_source_remove(timeout_id); 985 | timeout_id = 0; 986 | } 987 | tab_pressed(cl); 988 | return TRUE; /* stop signal emission */ 989 | 990 | case GDK_KEY(Up): 991 | case GDK_KEY(P): 992 | case GDK_KEY(p): 993 | if (key == GDK_KEY(p) || key == GDK_KEY(P)) { 994 | if (!(event->state & GDK_CONTROL_MASK)) { 995 | goto ordinary; 996 | } 997 | } 998 | if (cl->win_compl != NULL) { 999 | gboolean valid; 1000 | valid = gtk_tree_model_iter_previous(cl->sort_list_compl, &(cl->list_compl_it)); 1001 | if(!valid) { 1002 | int rowCount = gtk_tree_model_iter_n_children (cl->sort_list_compl, NULL); 1003 | gtk_tree_model_iter_nth_child(cl->sort_list_compl, &(cl->list_compl_it), NULL, rowCount - 1); 1004 | } 1005 | complete_from_list (cl, NULL); 1006 | } else { 1007 | up_history(cl); 1008 | } 1009 | if (cl->hist_search_mode == TRUE) { 1010 | search_off(cl); 1011 | } 1012 | return TRUE; /* stop signal emission */ 1013 | 1014 | case GDK_KEY(space): 1015 | { 1016 | if (cl->hist_search_mode) { 1017 | search_off (cl); 1018 | } 1019 | if (cl->win_compl != NULL) { 1020 | destroy_completion_window (cl); 1021 | } 1022 | } 1023 | return FALSE; 1024 | 1025 | case GDK_KEY(Down): 1026 | case GDK_KEY(N): 1027 | case GDK_KEY(n): 1028 | if (key == GDK_KEY(n) || key == GDK_KEY(N)) { 1029 | if (!(event->state & GDK_CONTROL_MASK)) { 1030 | goto ordinary; 1031 | } 1032 | } 1033 | if (cl->win_compl != NULL) { 1034 | gboolean valid = gtk_tree_model_iter_next(cl->sort_list_compl, &(cl->list_compl_it)); 1035 | if(!valid) { 1036 | gtk_tree_model_get_iter_first(cl->sort_list_compl, &(cl->list_compl_it)); 1037 | } 1038 | complete_from_list (cl, NULL); 1039 | } else { 1040 | down_history(cl); 1041 | } 1042 | if (cl->hist_search_mode == TRUE) { 1043 | search_off(cl); 1044 | } 1045 | return TRUE; /* stop signal emission */ 1046 | 1047 | case GDK_KEY(Return): 1048 | if (cl->win_compl != NULL) { 1049 | destroy_completion_window (cl); 1050 | } 1051 | if (event->state & GDK_CONTROL_MASK) { 1052 | g_signal_emit_by_name(G_OBJECT(cl), "runwithterm"); 1053 | } else { 1054 | g_signal_emit_by_name(G_OBJECT(cl), "activate"); 1055 | } 1056 | return TRUE; /* stop signal emission */ 1057 | 1058 | case GDK_KEY(S): 1059 | case GDK_KEY(s): 1060 | case GDK_KEY(R): 1061 | case GDK_KEY(r): 1062 | if (event->state & GDK_CONTROL_MASK) { 1063 | if (searching_history == FALSE) { 1064 | /* set proper funcs for forward/backward search */ 1065 | if (key == GDK_KEY(R) || key == GDK_KEY(r)) { /* reverse - backward */ 1066 | history_search_first_func = history_last; 1067 | history_search_next_func = history_prev; 1068 | } else { /* from start - forward */ 1069 | history_search_first_func = history_first; 1070 | history_search_next_func = history_next; 1071 | } 1072 | } 1073 | if (cl->hist_search_mode == FALSE) { 1074 | gtk_entry_set_text (GTK_ENTRY (cl), ""); 1075 | cl->hist_search_mode = TRUE; 1076 | cl->hist_search_match_start = FALSE; 1077 | cl->hist_word[0] = 0; 1078 | cl->hist_word_count = 0; 1079 | g_signal_emit_by_name(G_OBJECT(cl), "search_mode"); 1080 | } else { 1081 | // search next result for `cl->hist_word` 1082 | search_history (cl, 1); 1083 | } 1084 | return TRUE; /* stop signal emission */ 1085 | } else goto ordinary; 1086 | 1087 | case GDK_KEY(exclam): 1088 | if (cl->hist_search_mode == FALSE) { 1089 | const char * entry_text = gtk_entry_get_text (GTK_ENTRY (cl)); 1090 | if (!*entry_text) { 1091 | history_search_first_func = history_last; 1092 | history_search_next_func = history_prev; 1093 | cl->hist_search_mode = TRUE; 1094 | cl->hist_search_match_start = TRUE; 1095 | cl->hist_word[0] = 0; 1096 | cl->hist_word_count = 0; 1097 | g_signal_emit_by_name (G_OBJECT(cl), "search_mode"); 1098 | return TRUE; /* stop signal emission */ 1099 | } 1100 | } 1101 | goto ordinary; 1102 | 1103 | case GDK_KEY(BackSpace): 1104 | if (cl->hist_search_mode == TRUE) { 1105 | if (cl->hist_word[0]) { 1106 | cl->hist_word_count--; 1107 | cl->hist_word[cl->hist_word_count] = 0; 1108 | search_history (cl, 0); 1109 | g_signal_emit_by_name(G_OBJECT(cl), "search_letter"); 1110 | } 1111 | return TRUE; /* stop signal emission */ 1112 | } 1113 | return FALSE; 1114 | 1115 | case GDK_KEY(Home): 1116 | case GDK_KEY(End): 1117 | compline_clear_selection(cl); 1118 | goto ordinary; 1119 | 1120 | case GDK_KEY(Escape): 1121 | if (cl->hist_search_mode == TRUE) { 1122 | search_off(cl); 1123 | } else if (cl->win_compl != NULL) { 1124 | destroy_completion_window (cl); 1125 | } else { 1126 | // user cancelled 1127 | g_signal_emit_by_name(G_OBJECT(cl), "cancel"); 1128 | } 1129 | return TRUE; /* stop signal emission */ 1130 | 1131 | case GDK_KEY(G): 1132 | case GDK_KEY(g): 1133 | if ((event->state & GDK_CONTROL_MASK) && cl->hist_search_mode) { 1134 | search_off(cl); 1135 | gtk_entry_set_text (GTK_ENTRY (cl), ""); 1136 | return TRUE; /* stop signal emission */ 1137 | } else goto ordinary; 1138 | 1139 | ordinary: 1140 | default: 1141 | if (cl->win_compl != NULL) { 1142 | destroy_completion_window (cl); 1143 | } 1144 | if (cl->hist_search_mode == TRUE) { 1145 | // https://developer.gnome.org/gdk2/stable/gdk2-Event-Structures.html#GdkEventKey 1146 | if (event->state & GDK_CONTROL_MASK) 1147 | return TRUE; /* stop signal emission */ 1148 | if (event->length > 0) { 1149 | // event->string = char: 'c' 'd' 1150 | cl->hist_word[cl->hist_word_count] = event->string[0]; 1151 | cl->hist_word_count++; 1152 | if (cl->hist_word_count < MAX_HISTWORD_CHARS) { 1153 | search_history (cl, 0); 1154 | } 1155 | return TRUE; /* stop signal emission */ 1156 | } else 1157 | search_off(cl); 1158 | } 1159 | if (cl->tabtimeout != 0) { 1160 | if (timeout_id != 0) { 1161 | g_source_remove(timeout_id); 1162 | timeout_id = 0; 1163 | } 1164 | if (isprint (*event->string)) { 1165 | timeout_id = g_timeout_add (cl->tabtimeout, 1166 | (GSourceFunc) tab_pressed, cl); 1167 | } 1168 | } 1169 | break; 1170 | } //switch 1171 | return FALSE; 1172 | } 1173 | 1174 | -------------------------------------------------------------------------------- /src/gtkcompletionline.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Mihai Bazon 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software 5 | * for any purpose with or without fee is hereby granted, provided that 6 | * the above copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES 10 | * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 11 | * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR 12 | * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 13 | * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 14 | * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | #ifndef __GTKCOMPLETIONLINE_H__ 19 | #define __GTKCOMPLETIONLINE_H__ 20 | 21 | #include "gtkcompat.h" 22 | #include "history.h" 23 | 24 | #ifdef __cplusplus 25 | extern "C" 26 | { 27 | #endif 28 | 29 | #define GTK_COMPLETION_LINE(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, gtk_completion_line_get_type(), GtkCompletionLine) 30 | #define GTK_COMPLETION_LINE_CLASS(klass) GTK_CHECK_CLASS_CAST(klass, gtk_completion_line_get_type(), GtkCompletionLineClass) 31 | #define IS_GTK_COMPLETION_LINE(obj) GTK_CHECK_TYPE(obj, gtk_completion_line_get_type()) 32 | 33 | typedef struct _GtkCompletionLine GtkCompletionLine; 34 | typedef struct _GtkCompletionLineClass GtkCompletionLineClass; 35 | 36 | #define MAX_HISTWORD_CHARS 2048 37 | 38 | struct _GtkCompletionLine 39 | { 40 | GtkEntry parent; 41 | GtkWidget * win_compl; 42 | GtkListStore * list_compl; 43 | GtkTreeModel * sort_list_compl; 44 | GtkWidget * tree_compl; 45 | GtkTreeIter list_compl_it; 46 | int pos_in_text; /* Cursor position in main "line" */ 47 | 48 | HistoryFile * hist; 49 | gboolean hist_search_mode; 50 | gboolean hist_search_match_start; 51 | char hist_word[MAX_HISTWORD_CHARS]; /* history search: word that is being typed */ 52 | int hist_word_count; /* history search: word that is being typed */ 53 | 54 | int tabtimeout; 55 | int show_dot_files; 56 | }; 57 | 58 | 59 | struct _GtkCompletionLineClass 60 | { 61 | GtkEntryClass parent_class; 62 | /* add your CLASS members here */ 63 | void (* unique) (GtkCompletionLine *cl); 64 | void (* notunique) (GtkCompletionLine *cl); 65 | void (* incomplete) (GtkCompletionLine *cl); 66 | void (* runwithterm) (GtkCompletionLine *cl); 67 | void (* search_mode) (GtkCompletionLine *cl); 68 | void (* search_letter) (GtkCompletionLine *cl); 69 | void (* search_not_found) (GtkCompletionLine *cl); 70 | void (* ext_handler) (GtkCompletionLine *cl); 71 | void (* cancel) (GtkCompletionLine *cl); 72 | }; 73 | 74 | 75 | GType gtk_completion_line_get_type (void); 76 | GtkWidget * gtk_completion_line_new (); 77 | void gtk_completion_line_last_history_item (GtkCompletionLine*); 78 | 79 | void compline_clear_selection (GtkCompletionLine* cl); 80 | 81 | #ifdef __cplusplus 82 | } 83 | #endif 84 | 85 | #endif /* __GTKCOMPLETIONLINE_H__ */ 86 | 87 | -------------------------------------------------------------------------------- /src/history.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * 4 | * For more information, please refer to 5 | */ 6 | 7 | /* 8 | * - Generic implementation of HistoryFile 9 | * - Loads history from file. Saves history to file. 10 | * - Keeps track of the last entry, making it a bit more efficient 11 | * - Only adding entries is supported ... 12 | * - Supports maximum number of entries, 13 | * The first entry is removed if the new entry exceeds the max count. 14 | * - Does not allow duplicate strings 15 | * This is not a good idea if the list is too long 16 | * - If entry exists it's moved to the end of the list.. 17 | * 18 | */ 19 | 20 | #include "history.h" 21 | 22 | // ============================================================ 23 | // PRIVATE 24 | // ============================================================ 25 | 26 | struct _Whistory 27 | { 28 | long int index; 29 | unsigned int count; 30 | unsigned int max; 31 | char * filename; 32 | int has_changed; 33 | int save_if_changed; 34 | GList * list; 35 | GList * list_end; 36 | GList * current; 37 | }; 38 | 39 | static void _history_clear (HistoryFile * history) 40 | { 41 | // keep history->filename (destroyed in _history_free()) 42 | if (history->list) 43 | { 44 | //g_list_free_full (history->list, free); 45 | GList * i; 46 | for (i = history->list; i; i = i->next) { 47 | free (i->data); 48 | } 49 | g_list_free (history->list); 50 | history->list = NULL; 51 | history->has_changed = 1; 52 | } 53 | history->index = 0; 54 | history->count = 0; 55 | } 56 | 57 | 58 | static void _history_free (HistoryFile * history) 59 | { 60 | _history_clear (history); 61 | if (history->filename) { 62 | free (history->filename); 63 | history->filename = NULL; 64 | } 65 | free (history); 66 | } 67 | 68 | 69 | /// load entries from file and initialize private variables 70 | static void _history_load_from_file (HistoryFile * history, const char * filename) 71 | { 72 | FILE *fp; 73 | char buf[1024]; 74 | char * p, * item; 75 | size_t len; 76 | unsigned int count = 0; 77 | unsigned int max = history->max; 78 | GList * out_list = NULL; 79 | 80 | fp = fopen (filename, "r"); 81 | if (!fp) { 82 | return; 83 | } 84 | 85 | /* Read file line by line */ 86 | while (fgets (buf, sizeof (buf), fp)) 87 | { 88 | p = buf; 89 | while (*p && *p <= 0x20) { // 32 = space [ignore spaces] 90 | p++; 91 | } 92 | if (!*p) { 93 | continue; 94 | } 95 | 96 | item = strdup (p); 97 | len = strlen (buf); 98 | item[len-1] = 0; 99 | if (max > 0 && count >= max) { 100 | free (item); 101 | break; 102 | } 103 | count++; 104 | out_list = g_list_prepend (out_list, (gpointer) item); 105 | } 106 | 107 | if (out_list) { 108 | history->list_end = out_list; 109 | history->list = out_list; 110 | if (out_list->next) { 111 | history->list = g_list_reverse (out_list); 112 | } 113 | history->index = 1; 114 | } 115 | 116 | history->count = count; 117 | history->current = history->list; // current = 1st item 118 | fclose (fp); 119 | return; 120 | } 121 | 122 | 123 | void _history_write_to_file (HistoryFile * history, const char * filename) 124 | { 125 | FILE *fp; 126 | fp = fopen (filename, "w"); 127 | if (!fp) { 128 | return; 129 | } 130 | GList * i; 131 | for (i = history->list; i; i = i->next) 132 | { 133 | fprintf (fp, "%s\n", (char *) (i->data)); 134 | } 135 | fclose (fp); 136 | return; 137 | } 138 | 139 | 140 | // ============================================================ 141 | // PUBLIC 142 | // ============================================================ 143 | 144 | HistoryFile * history_new (const char * filename, unsigned int maxcount) 145 | { 146 | HistoryFile * history = calloc (1, sizeof (HistoryFile)); 147 | history->max = maxcount; 148 | if (filename && *filename) { 149 | history->filename = strdup (filename); 150 | _history_load_from_file (history, filename); 151 | } 152 | return (history); 153 | } 154 | 155 | void history_save (HistoryFile * history, int save_if_changed) 156 | { 157 | if (history && history->filename) { 158 | if (save_if_changed) { 159 | if (history->has_changed) { 160 | _history_write_to_file (history, history->filename); 161 | } // else printf ("not saving!!\n"); 162 | } else { 163 | _history_write_to_file (history, history->filename); 164 | } 165 | } else { 166 | fprintf (stderr, "history_save(): history or filename is NULL\n"); 167 | } 168 | } 169 | 170 | void history_destroy (HistoryFile * history) 171 | { 172 | if (history) { 173 | _history_free (history); 174 | } 175 | } 176 | 177 | void history_reload (HistoryFile * history) 178 | { 179 | if (history) { 180 | _history_clear (history); 181 | if (history->filename) { 182 | _history_load_from_file (history, history->filename); 183 | } 184 | history->has_changed = 0; 185 | } 186 | } 187 | 188 | 189 | void history_print (HistoryFile * history) 190 | { 191 | if (history) { 192 | unsigned int count = 0; 193 | GList * i; 194 | for (i = history->list; i; i = i->next) { 195 | count++; 196 | printf ("[%d] %s\n", count, (char *) (i->data)); 197 | } 198 | printf ("-- list internal count: [%d]\n", history->count); 199 | if (history->list_end) { 200 | printf ("** list : %s\n", (char *) (history->list->data)); 201 | printf ("** list_end : %s\n", (char *) (history->list_end->data)); 202 | } 203 | if (history->current) { 204 | printf ("** list current : %s\n", (char *) (history->current->data)); 205 | } 206 | } 207 | } 208 | 209 | 210 | // some apps might want to handle prev/next in a special way 211 | void history_unset_current (HistoryFile * history) 212 | { 213 | if (history) { 214 | history->current = NULL; 215 | history->index = -1; 216 | } 217 | } 218 | 219 | 220 | const char * history_get_current (HistoryFile * history) 221 | { 222 | if (history) return ((char*) (history->current->data)); 223 | else return (NULL); 224 | } 225 | 226 | 227 | int history_get_current_index (HistoryFile * history) 228 | { 229 | if (history) return (history->index); 230 | else return (-1); 231 | } 232 | 233 | 234 | const char * history_next (HistoryFile * history) 235 | { 236 | if (history->current && history->current->next) { 237 | history->current = history->current->next; 238 | history->index++; 239 | return ((char *) (history->current->data)); 240 | } 241 | return (NULL); 242 | } 243 | 244 | 245 | const char * history_prev (HistoryFile * history) 246 | { 247 | if (history->current && history->current->prev) { 248 | history->current = history->current->prev; 249 | history->index--; 250 | return ((char *) (history->current->data)); 251 | } 252 | return (NULL); 253 | } 254 | 255 | 256 | const char * history_first (HistoryFile * history) 257 | { 258 | if (history->list) { 259 | history->current = history->list; 260 | history->index = 1; 261 | return ((char *) (history->current->data)); 262 | } 263 | return (NULL); 264 | } 265 | 266 | 267 | const char * history_last (HistoryFile * history) 268 | { 269 | if (history->list) { 270 | history->current = history->list_end; // g_list_last (history->list); 271 | return ((char *) (history->current->data)); 272 | history->index = history->count; 273 | } 274 | return (NULL); 275 | } 276 | 277 | 278 | void history_append (HistoryFile * history, const char * text) 279 | { 280 | if (!text || !*text) { 281 | return; 282 | } 283 | 284 | GList * i, * templist; 285 | char * ientry; 286 | // if new entry = last entry, then abort 287 | if (history->list_end) { 288 | ientry = (char *) (history->list_end->data); 289 | if (strcmp (text, ientry) == 0) { 290 | return; 291 | } 292 | } 293 | // do not allow duplicate entries, remove existing entry 294 | for (i = history->list; i; i = i->next) 295 | { 296 | char * ientry = (char *) (i->data); 297 | if (strcmp (text, ientry) == 0) 298 | { 299 | // entry already exists.. remove 300 | free (i->data); 301 | templist = g_list_delete_link (history->list, i); 302 | if (!templist->prev) { // no previous entry.. new start 303 | history->list = templist; 304 | } 305 | if (!templist->next) { // no next... only 1 entry 306 | history->list_end = templist; 307 | } 308 | history->count--; 309 | break; 310 | } 311 | // TODO: handle 'current' 312 | } 313 | 314 | // if new entry exceeds the max count, first entry will be removed 315 | unsigned int remove_first = 0; 316 | if (history->max > 0 317 | && history->count > 1 318 | && history->count >= history->max) { 319 | remove_first = 1; 320 | } 321 | 322 | if (history->count < history->max) { 323 | history->count++; 324 | } 325 | 326 | // add new item 327 | char * new_item = strdup (text); 328 | GList * position = history->list_end ? history->list_end : history->list; 329 | position = g_list_append (position, (gpointer) new_item); 330 | history->has_changed = 1; 331 | if (!history->list) { 332 | history->list = position; // first 333 | history->current = history->list; // set current to first 334 | history->index = 1; 335 | } 336 | 337 | if (history->list_end) { 338 | history->list_end = history->list_end->next; 339 | } else { 340 | history->list_end = history->list; // first element has just been added 341 | } 342 | 343 | if (remove_first && history->list->next) { 344 | // problem when removing the first entry - current may become invalid 345 | if (history->current == history->list) { 346 | history->current = history->list->next; 347 | } 348 | free (history->list->data); 349 | history->list = g_list_delete_link (history->list, history->list); 350 | } 351 | } 352 | 353 | 354 | void history_reverse (HistoryFile * history) 355 | { 356 | if (history && history->list) { 357 | GList * prev_first = history->list; 358 | history->list = g_list_reverse (history->list); 359 | history->list_end = prev_first; 360 | if (history->current == prev_first) { 361 | history->current = history->list; 362 | } 363 | } 364 | } 365 | 366 | -------------------------------------------------------------------------------- /src/history.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * This is free and unencumbered software released into the public domain. 4 | * 5 | * For more information, please refer to 6 | */ 7 | 8 | /* 9 | * Generic implementation of HistoryFile 10 | */ 11 | 12 | #ifndef __HISTORY_H 13 | #define __HISTORY_H 14 | 15 | #ifdef __cplusplus 16 | extern "C" 17 | { 18 | #endif 19 | 20 | #include "gtkcompat.h" 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | typedef struct _Whistory HistoryFile; 27 | 28 | enum 29 | { 30 | HISTORY_SAVE_ALWAYS, 31 | HISTORY_SAVE_IF_CHANGED, 32 | }; 33 | 34 | /// create a new HistoryFile 35 | /// the history is initialized using filename and the filename is stored 36 | /// maxcount > 0 sets a maximun item count 37 | HistoryFile * history_new (const char * filename, unsigned int maxcount); 38 | 39 | /// save history to file, 0 = force save / 1 = save only if history has changed 40 | void history_save (HistoryFile * history, int save_if_changed); 41 | 42 | /// history is destroyed, you must variable to NULL 43 | void history_destroy (HistoryFile * history); 44 | 45 | /// clear history and reload from file 46 | void history_reload (HistoryFile * history); 47 | 48 | /// print history, good for debugging 49 | void history_print (HistoryFile * history); 50 | 51 | /// some apps might want to handle prev/next in a special way 52 | void history_unset_current (HistoryFile * history); 53 | 54 | /// returns string with the current history item 55 | const char * history_get_current (HistoryFile * history); 56 | 57 | /// returns current index: valid index > 0 58 | /// assume unser current item if index <= 0 59 | int history_get_current_index (HistoryFile * history); 60 | 61 | /// moves position to the next entry and returns text 62 | /// if there is no next entry, the position is not moved and returns NULL 63 | const char * history_next (HistoryFile * history); 64 | 65 | /// moves position to the previous entry and returns text 66 | /// if there is no next entry, the position is not moved and returns NULL 67 | const char * history_prev (HistoryFile * history); 68 | 69 | /// moves position to the first entry en returns text 70 | /// if there are no entries, it returns NULL 71 | const char * history_first (HistoryFile * history); 72 | 73 | /// moves position to the last entry en returns text 74 | /// if there are no entries, it returns NULL 75 | const char * history_last (HistoryFile * history); 76 | 77 | /// add entry to history, if entry already exists, it's moved to the end of the list 78 | void history_append (HistoryFile * history, const char * text); 79 | 80 | void history_reverse (HistoryFile * history); 81 | 82 | #ifdef __cplusplus 83 | } 84 | #endif 85 | 86 | #endif 87 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Mihai Bazon 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software 5 | * for any purpose with or without fee is hereby granted, provided that 6 | * the above copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES 10 | * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 11 | * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR 12 | * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 13 | * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 14 | * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | * 16 | */ 17 | 18 | #ifdef HAVE_CONFIG_H 19 | #include 20 | #endif 21 | 22 | #if ENABLE_NLS 23 | #include 24 | #include 25 | #define _(x) gettext(x) 26 | #define N_(x) (x) 27 | #else 28 | #define _(x) (x) 29 | #define N_(x) (x) 30 | #endif 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | #include "gtkcompletionline.h" 37 | #include "config_prefs.h" 38 | 39 | enum 40 | { 41 | W_TEXT_STYLE_NORMAL, 42 | W_TEXT_STYLE_NOTFOUND, 43 | W_TEXT_STYLE_NOTUNIQUE, 44 | W_TEXT_STYLE_UNIQUE, 45 | }; 46 | 47 | GtkApplication * gmrun_app = NULL; 48 | 49 | char * gmrun_text = NULL; 50 | static void gmrun_exit (void); 51 | static gint signal_connect_id_cb_dialog_size_allocate; 52 | GtkAllocation window_geom = { -1, -1, -1, -1 }; 53 | /* widgets that are used in several functions */ 54 | GtkWidget * compline = NULL; 55 | GtkWidget * wlabel = NULL; 56 | GtkWidget * wlabel_search = NULL; 57 | 58 | /* preferences */ 59 | int USE_GLIB_XDG = 0; 60 | int SHELL_RUN = 1; 61 | 62 | /// BEGIN: TIMEOUT MANAGEMENT 63 | 64 | static gboolean search_off_timeout (); 65 | static guint g_search_off_timeout_id = 0; 66 | 67 | static void remove_search_off_timeout (void) 68 | { 69 | if (g_search_off_timeout_id) { 70 | g_source_remove(g_search_off_timeout_id); 71 | g_search_off_timeout_id = 0; 72 | } 73 | } 74 | 75 | static void add_search_off_timeout (guint32 timeout, GSourceFunc func) 76 | { 77 | remove_search_off_timeout(); 78 | if (!func) 79 | func = (GSourceFunc) search_off_timeout; 80 | g_search_off_timeout_id = g_timeout_add (timeout, func, NULL); 81 | } 82 | 83 | /// END: TIMEOUT MANAGEMENT 84 | 85 | // https://unix.stackexchange.com/questions/457584/gtk3-change-text-color-in-a-label-raspberry-pi 86 | static void set_info_text_color (GtkWidget *w, const char *text, int spec) 87 | { 88 | char *markup = NULL; 89 | if (spec == W_TEXT_STYLE_NORMAL) { 90 | gtk_label_set_text (GTK_LABEL(w), text); 91 | return; 92 | } 93 | static const char * colors[] = { 94 | "black", /* W_TEXT_STYLE_NORMAL */ 95 | "red", /* W_TEXT_STYLE_NOTFOUND */ 96 | "blue", /* W_TEXT_STYLE_NOTUNIQUE */ 97 | "green", /* W_TEXT_STYLE_UNIQUE */ 98 | }; 99 | markup = g_markup_printf_escaped ("%s", 100 | colors[spec], text); 101 | if (markup) { 102 | gtk_label_set_markup (GTK_LABEL (w), markup); 103 | g_free(markup); 104 | } 105 | } 106 | 107 | 108 | static void run_the_command (char * cmd) 109 | { 110 | #if DEBUG 111 | fprintf (stderr, "cmd: %s\n", cmd); 112 | #endif 113 | if (SHELL_RUN) 114 | { 115 | /* need to add extra & */ 116 | char * cmd2 = g_strconcat (cmd, " &", NULL); 117 | int ret = system (cmd2); 118 | #if DEBUG 119 | fprintf (stderr, "cmd2: %s\n", cmd2); 120 | #endif 121 | g_free (cmd2); 122 | if (ret != -1) { 123 | gmrun_exit (); 124 | } else { 125 | gchar *errmsg = g_strconcat("ERROR: ", strerror(errno), NULL); 126 | set_info_text_color (wlabel, errmsg, W_TEXT_STYLE_NOTFOUND); 127 | add_search_off_timeout (3000, NULL); 128 | g_free(errmsg); 129 | } 130 | } 131 | else // glib - more conservative approach and robust error reporting 132 | { 133 | GError * error = NULL; 134 | gboolean success; 135 | int argc; 136 | char ** argv; 137 | success = g_shell_parse_argv (cmd, &argc, &argv, &error); 138 | if (!success) { 139 | set_info_text_color (wlabel, error->message, W_TEXT_STYLE_NOTFOUND); 140 | g_error_free (error); 141 | add_search_off_timeout (3000, NULL); 142 | return; 143 | } 144 | success = g_spawn_async (NULL, argv, NULL, 145 | G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error); 146 | if (argv) { 147 | g_strfreev (argv); 148 | } 149 | if (success) { 150 | gmrun_exit (); 151 | } else { 152 | set_info_text_color (wlabel, error->message, W_TEXT_STYLE_NOTFOUND); 153 | g_error_free (error); 154 | add_search_off_timeout (3000, NULL); 155 | } 156 | } 157 | } 158 | 159 | 160 | static void on_ext_handler (GtkCompletionLine *cl, const char * filename) 161 | { 162 | if (USE_GLIB_XDG) // GLib XDG handling (freedesktop specification) 163 | { 164 | gchar * content_type, * mime_type, * msg; 165 | const gchar * handler; 166 | GAppInfo * app_info; 167 | 168 | if (filename) { 169 | content_type = g_content_type_guess (filename, NULL, 0, NULL); 170 | if (content_type) { 171 | mime_type = g_content_type_get_mime_type (content_type); 172 | g_free (content_type); 173 | app_info = g_app_info_get_default_for_type (mime_type, FALSE); 174 | g_free (mime_type); 175 | if (app_info) { 176 | handler = g_app_info_get_commandline (app_info); 177 | msg = g_strconcat(_("Program: "), handler, NULL); 178 | gtk_label_set_text (GTK_LABEL (wlabel_search), msg); 179 | gtk_widget_show (wlabel_search); 180 | 181 | g_object_unref(app_info); 182 | g_free(msg); 183 | return; 184 | } 185 | } 186 | } 187 | search_off_timeout(); 188 | } 189 | else // custom EXT handlers 190 | { 191 | const char * ext = strrchr (filename, '.'); 192 | if (!ext) { 193 | search_off_timeout (); 194 | return; 195 | } 196 | const char * handler = config_get_handler_for_extension (ext); 197 | if (handler) { 198 | char * tmp = g_strconcat (_("Program: "), handler, NULL); 199 | gtk_label_set_text (GTK_LABEL (wlabel_search), tmp); 200 | gtk_widget_show (wlabel_search); 201 | g_free (tmp); 202 | } 203 | } 204 | } 205 | 206 | 207 | static void on_compline_runwithterm (GtkCompletionLine *cl) 208 | { 209 | gchar *cmd; 210 | char * term; 211 | char * entry_text = g_strdup (gtk_entry_get_text (GTK_ENTRY(cl))); 212 | g_strstrip (entry_text); 213 | 214 | if (*entry_text) { 215 | if (config_get_string_expanded ("TermExec", &term)) { 216 | cmd = g_strconcat( term, " ", entry_text, NULL); 217 | g_free (term); 218 | } else { 219 | cmd = g_strconcat( "xterm -e ", entry_text, NULL); 220 | } 221 | } else { 222 | if (config_get_string ("Terminal", &term)) { 223 | cmd = g_strdup(term); 224 | } else { 225 | cmd = g_strdup("xterm"); 226 | } 227 | } 228 | 229 | history_append (cl->hist, cmd); 230 | run_the_command (cmd); 231 | g_free (entry_text); 232 | g_free (cmd); 233 | } 234 | 235 | static gboolean search_off_timeout () 236 | { 237 | set_info_text_color (wlabel, _("Run:"), W_TEXT_STYLE_NORMAL); 238 | gtk_widget_hide (wlabel_search); 239 | g_search_off_timeout_id = 0; 240 | return G_SOURCE_REMOVE; 241 | } 242 | 243 | static void on_compline_unique (GtkCompletionLine *cl) 244 | { 245 | set_info_text_color (wlabel, _("unique"), W_TEXT_STYLE_UNIQUE); 246 | add_search_off_timeout (1000, NULL); 247 | } 248 | 249 | static void on_compline_notunique (GtkCompletionLine *cl) 250 | { 251 | set_info_text_color (wlabel, _("not unique"), W_TEXT_STYLE_NOTUNIQUE); 252 | add_search_off_timeout (1000, NULL); 253 | } 254 | 255 | static void on_compline_incomplete (GtkCompletionLine *cl) 256 | { 257 | set_info_text_color (wlabel, _("not found"), W_TEXT_STYLE_NOTFOUND); 258 | add_search_off_timeout (1000, NULL); 259 | } 260 | 261 | static void on_search_mode (GtkCompletionLine *cl) 262 | { 263 | if (cl->hist_search_mode == TRUE) { 264 | gtk_widget_show (wlabel_search); 265 | gtk_label_set_text (GTK_LABEL (wlabel), _("Search:")); 266 | gtk_label_set_text (GTK_LABEL (wlabel_search), cl->hist_word); 267 | } else { 268 | gtk_widget_hide (wlabel_search); 269 | gtk_label_set_text (GTK_LABEL (wlabel), _("Search OFF")); 270 | add_search_off_timeout (1000, NULL); 271 | } 272 | } 273 | 274 | static void on_search_letter (GtkCompletionLine *cl, GtkWidget *label) 275 | { 276 | gtk_label_set_text (GTK_LABEL(label), cl->hist_word); 277 | } 278 | 279 | static gboolean search_fail_timeout (gpointer user_data) 280 | { 281 | set_info_text_color (wlabel, _("Search:"), W_TEXT_STYLE_NOTUNIQUE); 282 | g_search_off_timeout_id = 0; 283 | return G_SOURCE_REMOVE; 284 | } 285 | 286 | static void on_search_not_found(GtkCompletionLine *cl) 287 | { 288 | set_info_text_color (wlabel, _("Not Found!"), W_TEXT_STYLE_NOTFOUND); 289 | add_search_off_timeout (1000, (GSourceFunc) search_fail_timeout); 290 | } 291 | 292 | 293 | // ============================================================= 294 | 295 | static void xdg_app_run_command (GAppInfo *app, const gchar *args) 296 | { 297 | // get 298 | char * cmd, * exe; 299 | GRegex * regex; 300 | 301 | regex = g_regex_new (".%[fFuUdDnNickvm]", G_REGEX_OPTIMIZE, G_REGEX_MATCH_NOTEMPTY, NULL); 302 | exe = g_regex_replace_literal (regex, // Remove xdg desktop files fields from app 303 | g_app_info_get_commandline (app), 304 | -1, 0, "", G_REGEX_MATCH_NOTEMPTY, NULL); 305 | cmd = g_strconcat (exe, " ", args, NULL); 306 | run_the_command (cmd); /* Launch the command */ 307 | g_regex_unref (regex); 308 | g_free (exe); 309 | g_free (cmd); 310 | } 311 | 312 | /* Handler for URLs */ 313 | static gboolean url_check (GtkCompletionLine *cl, char * entry_text) 314 | { 315 | if (USE_GLIB_XDG) // GLib XDG handling (freedesktop specification) 316 | { 317 | char * delim; 318 | const char * url, * protocol; 319 | GAppInfo * app; 320 | delim = strchr (entry_text, ':'); 321 | if (!delim || !*(delim+1)) { 322 | return FALSE; 323 | } 324 | url = delim + 1; 325 | if (url[0] == '/' && url[1] == '/') 326 | { 327 | protocol = entry_text; 328 | *delim = 0; 329 | app = g_app_info_get_default_for_uri_scheme (protocol); 330 | *delim = ':'; 331 | if (app) 332 | { // found known uri handler for protocol 333 | xdg_app_run_command (app, entry_text); 334 | history_append (cl->hist, entry_text); 335 | g_object_unref (app); 336 | return TRUE; 337 | } 338 | } 339 | return FALSE; 340 | 341 | } 342 | else //-------- custom URL handlers 343 | { 344 | // 345 | // http : //www.fsf.org 346 | // 347 | // config: URL_ 348 | // handler %s (format 1) = run handler with 349 | // handler %u (format 2) = run handler with 350 | char * cmd; 351 | char * tmp, * delim, * p; 352 | char * url, * url_type, * full_url, * chosen_url; 353 | char * url_handler; 354 | char * config_key; 355 | cmd = tmp = delim = p = url_handler = config_key = NULL; 356 | delim = strchr (entry_text, ':'); 357 | if (!delim || !*(delim+1)) { 358 | return FALSE; 359 | } 360 | tmp = g_strdup (entry_text); 361 | delim = strchr (tmp, ':'); 362 | *delim = 0; 363 | url_type = tmp; // http 364 | url = delim + 1; // //www.fsf.org 365 | full_url = entry_text; 366 | 367 | config_key = g_strconcat ("URL_", url_type, NULL); 368 | if (config_get_string_expanded (config_key, &url_handler)) 369 | { 370 | chosen_url = url; 371 | p = strchr (url_handler, '%'); 372 | if (p) { // handler %s 373 | p++; 374 | if (*p == 'u') { // handler %u 375 | *p = 's'; // convert %u to %s (for printf) 376 | chosen_url = full_url; 377 | } 378 | cmd = g_strdup_printf (url_handler, chosen_url); 379 | } else { 380 | cmd = g_strconcat (url_handler, " ", url, NULL); 381 | } 382 | g_free (url_handler); 383 | } 384 | 385 | g_free (config_key); 386 | g_free (tmp); 387 | if (cmd) { 388 | history_append (cl->hist, entry_text); 389 | run_the_command (cmd); 390 | g_free (cmd); 391 | return TRUE; 392 | } 393 | return FALSE; 394 | } 395 | } 396 | 397 | 398 | static char * escape_spaces (char * entry_text) 399 | { // run file with glib: replace " " with "\ " 400 | GRegex * regex; 401 | char * quoted; 402 | if (!strstr (entry_text, "\\ ")) { 403 | regex = g_regex_new (" ", G_REGEX_OPTIMIZE, G_REGEX_MATCH_NOTEMPTY, NULL); 404 | quoted = g_regex_replace_literal (regex, entry_text, -1, 0, "\\ ", G_REGEX_MATCH_NOTEMPTY, NULL); 405 | g_regex_unref (regex); 406 | } else { 407 | quoted = strdup (entry_text); // already scaped, just duplicate text 408 | } 409 | return (quoted); 410 | } 411 | 412 | /* Handler for extensions */ 413 | static gboolean ext_check (GtkCompletionLine *cl, char * entry_text) 414 | { 415 | if (USE_GLIB_XDG) // GLib XDG handling (freedesktop specification) 416 | { 417 | char *quoted, *content_type, *mime_type; 418 | GAppInfo *app_info; 419 | gboolean sure; 420 | quoted = escape_spaces (entry_text); 421 | /* File is executable: launch it (fail silently if file isn't really executable) */ 422 | if (g_file_test (quoted, G_FILE_TEST_IS_EXECUTABLE)) { 423 | run_the_command (quoted); 424 | history_append (cl->hist, entry_text); 425 | g_free (quoted); 426 | return TRUE; 427 | } 428 | /* Check mime type through extension */ 429 | if (quoted[0] == '/' && strchr (quoted, '.')) { 430 | content_type = g_content_type_guess (quoted, NULL, 0, &sure); 431 | if (content_type) { 432 | mime_type = g_content_type_get_mime_type (content_type); 433 | g_free (content_type); 434 | app_info = g_app_info_get_default_for_type (mime_type, FALSE); 435 | g_free (mime_type); 436 | if (app_info) { // found mime 437 | xdg_app_run_command (app_info, quoted); 438 | history_append (cl->hist, entry_text); 439 | g_free (quoted); 440 | g_object_unref(app_info); 441 | return TRUE; 442 | } 443 | } 444 | } 445 | g_free (quoted); 446 | return FALSE; 447 | 448 | } 449 | else //-------- custom EXTension handlers 450 | { 451 | // example: file.html | xdg-open '%s' -> xdg-open 'file.html' 452 | char * cmd; 453 | char * unescaped = g_strcompress (entry_text); /* unescape chars */ 454 | char * ext = strrchr (entry_text, '.'); 455 | char * handler_format = NULL; 456 | if (access (unescaped, F_OK) == -1) { 457 | // entry_text must be a valid filename 458 | g_free (unescaped); 459 | return FALSE; 460 | } 461 | if (ext) { 462 | handler_format = config_get_handler_for_extension (ext); 463 | } 464 | if (handler_format) { 465 | if (strstr (handler_format, "%s")) { 466 | cmd = g_strdup_printf (handler_format, unescaped); 467 | } 468 | else { // xdg-open 469 | cmd = g_strconcat (handler_format, " '", unescaped, "'", NULL); 470 | } 471 | history_append (cl->hist, entry_text); 472 | run_the_command (cmd); 473 | g_free (cmd); 474 | g_free (unescaped); 475 | return TRUE; 476 | } 477 | 478 | g_free (unescaped); 479 | return FALSE; 480 | } 481 | } 482 | 483 | // ============================================================= 484 | 485 | static void on_compline_activated (GtkCompletionLine *cl) 486 | { 487 | char * entry_text = g_strdup (gtk_entry_get_text (GTK_ENTRY(cl))); 488 | g_strstrip (entry_text); 489 | 490 | if (url_check(cl,entry_text) == TRUE || ext_check(cl,entry_text) == TRUE) 491 | { 492 | g_free (entry_text); 493 | return; 494 | } 495 | 496 | gchar * cmd; 497 | char * AlwaysInTerm = NULL; 498 | char ** term_progs = NULL; 499 | char * selected_term_prog = NULL; 500 | 501 | if (config_get_string ("AlwaysInTerm", &AlwaysInTerm)) 502 | { 503 | term_progs = g_strsplit (AlwaysInTerm, " ", 0); 504 | int i; 505 | for (i = 0; term_progs[i]; i++) { 506 | if (strcmp (term_progs[i], entry_text) == 0) { 507 | selected_term_prog = g_strdup (term_progs[i]); 508 | break; 509 | } 510 | } 511 | g_strfreev (term_progs); 512 | } 513 | 514 | if (selected_term_prog) { 515 | char * TermExec; 516 | config_get_string_expanded ("TermExec", &TermExec); 517 | cmd = g_strconcat(TermExec, " ", selected_term_prog, NULL); 518 | g_free (selected_term_prog); 519 | g_free (TermExec); 520 | } else { 521 | cmd = g_strdup(entry_text); 522 | } 523 | g_free (entry_text); 524 | 525 | history_append (cl->hist, cmd); 526 | run_the_command (cmd); 527 | g_free(cmd); 528 | } 529 | 530 | 531 | static void cb_dialog_size_allocate (GtkWidget *w, GdkRectangle *alloc, gpointer udata) 532 | { 533 | // https://stackoverflow.com/questions/4886420/gtk-forbid-vertical-resize-of-gtkwindow 534 | GdkGeometry hints; 535 | g_signal_handler_disconnect (G_OBJECT(w), signal_connect_id_cb_dialog_size_allocate); 536 | /* dummy values for minx/max to no restrict vertical resizing */ 537 | hints.min_width = 0; 538 | hints.max_width = G_MAXINT; 539 | /* do not allow vertical resizing */ 540 | hints.min_height = alloc->height; 541 | hints.max_height = alloc->height; 542 | gtk_window_set_geometry_hints (GTK_WINDOW(w), NULL, &hints, 543 | GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE); 544 | } 545 | 546 | // ============================================================= 547 | 548 | 549 | static void gmrun_activate(void) 550 | { 551 | GtkWidget *dialog, * main_vbox; 552 | GtkWidget *label_search; 553 | 554 | GtkWidget * window = gtk_application_window_new (gmrun_app); 555 | dialog = gtk_dialog_new(); 556 | gtk_window_set_transient_for( (GtkWindow*)dialog, (GtkWindow*)window ); 557 | gtk_window_set_title (GTK_WINDOW(window), "gmrun"); 558 | gtk_window_set_title (GTK_WINDOW(dialog), "gmrun"); 559 | main_vbox = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); 560 | 561 | // this removes the title bar.. 562 | gtk_widget_realize (dialog); 563 | GdkWindow *gwin = gtk_widget_get_window (GTK_WIDGET(dialog)); 564 | gdk_window_set_decorations (gwin, GDK_DECOR_BORDER); 565 | 566 | gtk_container_set_border_width(GTK_CONTAINER(dialog), 4); 567 | g_signal_connect (G_OBJECT(dialog), "destroy", 568 | G_CALLBACK(gmrun_exit), NULL); 569 | signal_connect_id_cb_dialog_size_allocate = 570 | g_signal_connect (G_OBJECT(dialog), "size-allocate", 571 | G_CALLBACK(cb_dialog_size_allocate), NULL); 572 | 573 | GtkWidget *hhbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); 574 | gtk_box_pack_start (GTK_BOX (main_vbox), hhbox, FALSE, FALSE, 0); 575 | 576 | GtkWidget *label = gtk_label_new (_("Run:")); 577 | gtk_box_pack_start (GTK_BOX(hhbox), label, FALSE, FALSE, 10); 578 | gtkcompat_widget_set_halign_left (GTK_WIDGET (label)); 579 | wlabel = label; 580 | 581 | label_search = gtk_label_new(""); 582 | gtk_box_pack_start (GTK_BOX (hhbox), label_search, FALSE, TRUE, 0); 583 | wlabel_search = label_search; 584 | 585 | compline = gtk_completion_line_new(); 586 | 587 | gtk_box_pack_start (GTK_BOX (main_vbox), compline, TRUE, TRUE, 0); 588 | 589 | if (!config_get_int ("SHELL_RUN", &SHELL_RUN)) { 590 | SHELL_RUN = 1; 591 | } 592 | 593 | // don't show files starting with "." by default 594 | if (!config_get_int ("ShowDotFiles", &(GTK_COMPLETION_LINE(compline)->show_dot_files))) { 595 | GTK_COMPLETION_LINE(compline)->show_dot_files = 0; 596 | } 597 | int tmp; 598 | if (!config_get_int ("TabTimeout", &tmp)) { 599 | ((GtkCompletionLine*)compline)->tabtimeout = tmp; 600 | } 601 | if (!config_get_int ("USE_GLIB_XDG", &USE_GLIB_XDG)) { 602 | USE_GLIB_XDG = 0; 603 | } 604 | 605 | g_signal_connect(G_OBJECT(compline), "cancel", 606 | G_CALLBACK(gmrun_exit), NULL); 607 | g_signal_connect(G_OBJECT(compline), "activate", 608 | G_CALLBACK (on_compline_activated), NULL); 609 | g_signal_connect(G_OBJECT(compline), "runwithterm", 610 | G_CALLBACK (on_compline_runwithterm), NULL); 611 | 612 | g_signal_connect(G_OBJECT(compline), "unique", 613 | G_CALLBACK (on_compline_unique), NULL); 614 | g_signal_connect(G_OBJECT(compline), "notunique", 615 | G_CALLBACK (on_compline_notunique), NULL); 616 | g_signal_connect(G_OBJECT(compline), "incomplete", 617 | G_CALLBACK (on_compline_incomplete), NULL); 618 | 619 | g_signal_connect(G_OBJECT(compline), "search_mode", 620 | G_CALLBACK (on_search_mode), NULL); 621 | g_signal_connect(G_OBJECT(compline), "search_not_found", 622 | G_CALLBACK (on_search_not_found), NULL); 623 | g_signal_connect(G_OBJECT(compline), "search_letter", 624 | G_CALLBACK(on_search_letter), label_search); 625 | 626 | g_signal_connect(G_OBJECT(compline), "ext_handler", 627 | G_CALLBACK (on_ext_handler), NULL); 628 | 629 | int shows_last_history_item; 630 | if (!config_get_int ("ShowLast", &shows_last_history_item)) { 631 | shows_last_history_item = 0; 632 | } 633 | if (gmrun_text) { 634 | gtk_entry_set_text (GTK_ENTRY(compline), gmrun_text); 635 | } else if (shows_last_history_item) { 636 | gtk_completion_line_last_history_item (GTK_COMPLETION_LINE(compline)); 637 | } 638 | 639 | // geometry: window position 640 | if (window_geom.x > -1 || window_geom.y > -1) { 641 | gtk_window_move (GTK_WINDOW (dialog), window_geom.x, window_geom.y); 642 | } else { 643 | /* default: centered */ 644 | gtk_window_set_position (GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ALWAYS); 645 | } 646 | 647 | // geometry: window size 648 | if (window_geom.height > -1 || window_geom.width > -1) { 649 | gtk_window_set_default_size (GTK_WINDOW (dialog), window_geom.width, 650 | window_geom.height); 651 | } else { 652 | /* default width = 500 */ 653 | gtk_window_set_default_size (GTK_WINDOW (dialog), 500, -1); 654 | } 655 | 656 | // window icon 657 | GError * error = NULL; 658 | GtkIconTheme * theme = gtk_icon_theme_get_default (); 659 | GdkPixbuf * icon = gtk_icon_theme_load_icon (theme, "gmrun", 48, GTK_ICON_LOOKUP_USE_BUILTIN, &error); 660 | if (error) { 661 | g_object_set (dialog, "icon-name", "gtk-execute", NULL); 662 | g_error_free (error); 663 | } else { 664 | gtk_window_set_icon (GTK_WINDOW (dialog), icon); 665 | g_object_unref (icon); 666 | } 667 | 668 | gtk_widget_show_all (dialog); 669 | 670 | gtk_window_set_focus (GTK_WINDOW(dialog), compline); 671 | if (gmrun_text) { 672 | // clear selection if command (text) is supplied as a parameter 673 | compline_clear_selection (GTK_COMPLETION_LINE (compline)); 674 | // free memory 675 | free (gmrun_text); 676 | } 677 | } 678 | 679 | // ============================================================= 680 | 681 | static void parse_command_line (int argc, char ** argv) 682 | { 683 | // --geometry / parse commandline options 684 | static char *geometry_str = NULL, *tmp = NULL; 685 | int i; 686 | static gboolean show_version; 687 | GError *error = NULL; 688 | GOptionContext *context = NULL; 689 | static GOptionEntry entries[] = 690 | { 691 | { "geometry", 'g', 0, G_OPTION_ARG_STRING, &geometry_str, "This option specifies the initial size and location of the window.", NULL, }, 692 | { "version", 'v', 0, G_OPTION_ARG_NONE, &show_version, "Show version", NULL, }, 693 | { NULL }, 694 | }; 695 | 696 | context = g_option_context_new (NULL); 697 | g_option_context_add_main_entries (context, entries, NULL); 698 | g_option_context_add_group (context, gtk_get_option_group (TRUE)); 699 | if (!g_option_context_parse (context, &argc, &argv, &error)) 700 | { 701 | g_print ("option parsing failed: %s\n", error->message); 702 | if (context) g_option_context_free (context); 703 | if (error) g_error_free (error); 704 | if (geometry_str) g_free (geometry_str); 705 | exit (1); 706 | } 707 | if (context) g_option_context_free (context); 708 | if (error) g_error_free (error); 709 | if (argc >= 2) { 710 | for (i = 1; i < argc; i++) 711 | { // all cli arguments are part of the same line 712 | if (tmp) { 713 | gmrun_text = g_strconcat (tmp, " ", argv[i], NULL); 714 | free (tmp); 715 | tmp = gmrun_text; 716 | } else { 717 | tmp = strdup (argv[i]); 718 | gmrun_text = tmp; 719 | } 720 | } 721 | } 722 | // -- 723 | 724 | if (show_version) { 725 | #ifdef HAVE_CONFIG_H 726 | puts (VERSION); 727 | #endif 728 | gmrun_exit (); 729 | exit (0); 730 | } 731 | 732 | if (!geometry_str) 733 | { 734 | // --geometry was not specified, see config file 735 | char * geomstr; 736 | if (config_get_string ("Geometry", &geomstr)) { 737 | geometry_str = g_strdup (geomstr); 738 | } 739 | } 740 | 741 | if (geometry_str) 742 | { 743 | // --geometry WxH+X+Y 744 | // width x height + posX + posY 745 | int width, height, posX, posY; 746 | char *Wstr, *Hstr, *Xstr, *Ystr; 747 | Wstr = Hstr = Xstr = Ystr = NULL; 748 | 749 | Xstr = strchr (geometry_str, '+'); 750 | if (Xstr) { // +posX+posY 751 | *Xstr = 0; 752 | Xstr++; // posX+posY 753 | Ystr = strchr (Xstr, '+'); 754 | if (Ystr) { // +posY 755 | *Ystr = 0; 756 | Ystr++; // posY 757 | } 758 | } 759 | if (Xstr && Ystr && *Xstr && *Ystr) { 760 | posX = strtoll (Xstr, NULL, 0); 761 | posY = strtoll (Ystr, NULL, 0); 762 | ///fprintf (stderr, "x: %" G_GINT64_FORMAT "\ny: %" G_GINT64_FORMAT "\n", posX, posY); 763 | window_geom.x = posX; 764 | window_geom.y = posY; 765 | } 766 | 767 | Hstr = strchr (geometry_str, 'x'); 768 | if (Hstr) { // WxH 769 | *Hstr = 0; 770 | Hstr++; // H 771 | Wstr = geometry_str; 772 | width = strtoll (Wstr, NULL, 0); 773 | height = strtoll (Hstr, NULL, 0); 774 | ///fprintf (stderr, "w: %" G_GINT64_FORMAT "\nh: %" G_GINT64_FORMAT "\n", width, height); 775 | window_geom.width = width; 776 | window_geom.height = height; 777 | } 778 | 779 | g_free (geometry_str); 780 | } 781 | } 782 | 783 | 784 | // ============================================================= 785 | // MAIN 786 | 787 | void gmrun_exit(void) 788 | { 789 | if (compline) { 790 | gtk_widget_destroy (compline); 791 | } 792 | config_destroy (); 793 | if (gmrun_app) { 794 | g_application_quit (G_APPLICATION (gmrun_app)); 795 | } 796 | } 797 | 798 | int main(int argc, char **argv) 799 | { 800 | int status = 0; 801 | 802 | #ifdef ENABLE_NLS 803 | bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALEDIR); 804 | bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); 805 | textdomain (GETTEXT_PACKAGE); 806 | #endif 807 | 808 | config_init (); 809 | parse_command_line (argc, argv); 810 | 811 | #if GTK_CHECK_VERSION(3, 4, 0) 812 | // Handling cmd line args with GApplication is a nightmare 813 | // follow this: https://developer.gnome.org/gtkmm-tutorial/stable/sec-multi-item-containers.html.en#boxes-command-line-options 814 | argc = 1; /* hide args from GApplication */ 815 | gmrun_app = gtk_application_new ("org.gtk.gmrun", G_APPLICATION_NON_UNIQUE); 816 | g_signal_connect (gmrun_app, "activate", gmrun_activate, NULL); 817 | status = g_application_run (G_APPLICATION (gmrun_app), argc, argv); 818 | g_object_unref (gmrun_app); 819 | 820 | #elif GTK_MAJOR_VERSION > 2 821 | # error "Gtk >= 3.4 is required" 822 | 823 | #else /* gtk2 */ 824 | // gmrun_app must point to *something* 825 | gmrun_app = (void *) &SHELL_RUN; //gtkcompat.h: #define g_application_quit(app) gtk_main_quit() 826 | gtk_init (&argc, &argv); 827 | gmrun_activate (); 828 | gtk_main (); 829 | #endif 830 | 831 | return (status); 832 | } 833 | 834 | -------------------------------------------------------------------------------- /w_conf/gettext: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # http://unlicense.org/ 3 | 4 | #======================================= 5 | w_new_option gettext 6 | #======================================= 7 | 8 | export GETTEXT_PACKAGE 9 | 10 | 11 | opt_print_gettext() 12 | { 13 | echo ' --disable-nls do not use Native Language Support (autodetect)' 14 | } 15 | 16 | 17 | opt_configure_gettext() 18 | { 19 | enable_gettext='check' 20 | for i in $@ 21 | do 22 | case $i in 23 | --enable-nls) enable_gettext='yes' ;; 24 | --disable-nls) enable_gettext='no' ;; 25 | # ./configure po|pot 26 | pot) 27 | ./po/zzpo.sh pot ${GETTEXT_PACKAGE} 28 | exit $? ;; 29 | po) 30 | ./po/zzpo.sh po ${GETTEXT_PACKAGE} 31 | #sed -i '/#~ /d' po/*.po 32 | exit 0 33 | ;; 34 | esac 35 | done 36 | } 37 | 38 | 39 | opt_check_gettext() 40 | { 41 | if [ "$enable_gettext" = "no" ] ; then 42 | return 43 | fi 44 | 45 | printf "Checking for gettext... " 46 | ccode='#include 47 | #include 48 | int main(void) { 49 | setlocale(LC_ALL, ""); 50 | gettext("hi"); 51 | return 0; 52 | } 53 | ' 54 | w_compile_c_code "$ccode" "" "" 55 | if [ $? = 0 ] ; then 56 | echo "yes" 57 | else 58 | w_compile_c_code "$ccode" "" "-lintl" 59 | if [ $? = 0 ] ; then 60 | echo "yes (libintl)" 61 | LDFLAGS="$LDFLAGS -lintl" 62 | else 63 | echo "no" 64 | if [ "$enable_gettext" = "yes" ] ; then 65 | exit_error "Use --disable-nls" 66 | fi 67 | enable_gettext='no' 68 | fi 69 | fi 70 | 71 | if [ "$enable_gettext" = "yes" ] ; then 72 | w_check_commands_required msgfmt 73 | elif [ "$enable_gettext" = "check" ] ; then 74 | w_check_command msgfmt 75 | if [ $? -ne 0 ] ; then 76 | enable_gettext='no' 77 | fi 78 | fi 79 | 80 | if [ "$enable_gettext" = "no" ] ; then 81 | return 82 | fi 83 | 84 | LINGUAS="$(ls po/*.po | sed -e 's%\.po$%%' -e 's%po/%%')" 85 | 86 | config_h_extra="$config_h_extra 87 | #define ENABLE_NLS 1 88 | #define HAVE_GETTEXT 1 89 | #define GETTEXT_PACKAGE \"$GETTEXT_PACKAGE\"" 90 | 91 | config_sh_extra="$config_sh_extra 92 | ENABLE_NLS=1 93 | USE_NLS=yes 94 | LINGUAS=\"$(echo $LINGUAS)\" 95 | GETTEXT_PACKAGE=\"$GETTEXT_PACKAGE\"" 96 | 97 | # config.mk 98 | make_extra_flags="$make_extra_flags 99 | USE_NLS = yes 100 | LINGUAS = $(echo $LINGUAS) 101 | GETTEXT_PACKAGE = $GETTEXT_PACKAGE 102 | " 103 | } 104 | -------------------------------------------------------------------------------- /w_conf/gtk: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is free and unencumbered software released into the public domain. 3 | # For more information, please refer to 4 | 5 | # set W_GTK_IS_OPTIONAL=yes to make GTK optional 6 | # and show (--disable-gtk) in --help msg 7 | # W_GTK_DEFAULT_VERSION can be 2/3/4 8 | 9 | #======================================= 10 | w_new_option gtk GTK 11 | #======================================= 12 | 13 | opt_print_gtk() 14 | { 15 | if [ "$W_GTK_ONLY_VERSION" ] ; then 16 | if [ "$W_GTK_IS_OPTIONAL" = "yes" ] ; then 17 | echo ' --disable-gtk do not build GTK support' 18 | fi 19 | return 20 | fi 21 | 22 | if [ -z "$W_GTK_DEFAULT_VERSION" ] ; then 23 | W_GTK_DEFAULT_VERSION=3 # default to GTK3 24 | fi 25 | 26 | case $W_GTK_DEFAULT_VERSION in 27 | 4) 28 | echo ' --disable-gtk4 build with GTK4 (Autodetect)' 29 | echo ' --enable-gtk3 build with GTK3 (Autodetect)' 30 | ;; 31 | 3) 32 | echo ' --disable-gtk3 build with GTK3 (Autodetect)' 33 | echo ' --enable-gtk2 build with GTK2 (Autodetect)' 34 | ;; 35 | 2) 36 | echo ' --disable-gtk2 build with GTK2 (Autodetect)' 37 | echo ' --enable-gtk3 build with GTK3 (Autodetect)' 38 | ;; 39 | esac 40 | 41 | if [ "$W_GTK_IS_OPTIONAL" = "yes" ] ; then 42 | echo ' --disable-gtk do not build GTK support' 43 | fi 44 | echo ' --disable-deprecated-gtk disable deprecated GTK (may break build)' 45 | } 46 | 47 | 48 | opt_configure_gtk() 49 | { 50 | if [ "$W_GTK_ONLY_VERSION" ] ; then 51 | opt_configure_gtk_only_version "$@" 52 | return 53 | fi 54 | 55 | W_GTK4_MIN_VERSION='' 56 | if [ -z "$W_GTK3_MIN_VERSION" ] ; then 57 | W_GTK3_MIN_VERSION='3.14' 58 | fi 59 | if [ -z "$W_GTK2_MIN_VERSION" ] ; then 60 | W_GTK2_MIN_VERSION='2.14' 61 | fi 62 | 63 | if [ -z "$W_GTK_DEFAULT_VERSION" ] ; then 64 | W_GTK_DEFAULT_VERSION=3 # default to GTK3 65 | fi 66 | 67 | case $W_GTK_DEFAULT_VERSION in 68 | 2) # hasn't been ported to GTK3 yet... 69 | enable_gtk4='no' 70 | enable_gtk3='no' 71 | enable_gtk2='check' 72 | ;; 73 | 3) # hasn't been ported to GTK4 yet... 74 | enable_gtk4='no' 75 | enable_gtk3='check' 76 | enable_gtk2='check' 77 | ;; 78 | 4) 79 | enable_gtk4='check' 80 | enable_gtk3='check' 81 | enable_gtk2='check' # should probably assume that gtk2 is not supported 82 | ;; 83 | esac 84 | 85 | force_gtk4=no 86 | force_gtk3=no 87 | force_gtk2=no 88 | 89 | disable_deprecated_gtk=no 90 | 91 | for i in $@ 92 | do 93 | case $i in 94 | --enable-gtk2) 95 | enable_gtk3='no' 96 | enable_gtk4='no' 97 | enable_gtk2='check' 98 | force_gtk2=yes 99 | ;; 100 | --disable-gtk2) 101 | enable_gtk2='no' 102 | ;; 103 | --enable-gtk3) 104 | enable_gtk4='no' 105 | enable_gtk2='no' 106 | enable_gtk3='check' 107 | force_gtk3=yes 108 | ;; 109 | --disable-gtk3) 110 | enable_gtk3='no' 111 | ;; 112 | --enable-gtk4) 113 | enable_gtk3='no' 114 | enable_gtk2='no' 115 | enable_gtk4='check' 116 | force_gtk4=yes 117 | ;; 118 | --disable-gtk4) 119 | enable_gtk4='no' 120 | ;; 121 | --disable-gtk) 122 | enable_gtk4='no' 123 | enable_gtk3='no' 124 | enable_gtk2='no' 125 | ;; 126 | --disable-deprecated-gtk) disable_deprecated_gtk=yes ;; 127 | --enable-deprecated-gtk) disable_deprecated_gtk=no ;; 128 | esac 129 | done 130 | 131 | if [ "$W_GTK_IS_OPTIONAL" != "yes" ] ; then 132 | if [ "$enable_gtk2" = "no" ] && [ "$enable_gtk3" = "no" ] && [ "$enable_gtk4" = "no" ] ; then 133 | exit_error "GTK has been disabled but it's required... ERROR" 134 | fi 135 | fi 136 | } 137 | 138 | 139 | opt_check_gtk() 140 | { 141 | if [ "$W_GTK_ONLY_VERSION" ] ; then 142 | opt_check_gtk_only_version 143 | return 144 | fi 145 | 146 | if [ "$enable_gtk2" = "no" ] && [ "$enable_gtk3" = "no" ] && [ "$enable_gtk4" = "no" ] ; then 147 | return 148 | fi 149 | 150 | if [ -n "$GTK_CFLAGS" ] || [ -n "$GTK_LIBS" ] ; then 151 | echo 'Checking for GTK... $GTK_CFLAGS/$GTK_LIBS' 152 | config_h_extra="$config_h_extra 153 | #define HAVE_GTK 1" 154 | make_extra_flags="$make_extra_flags 155 | GTK_CFLAGS = ${GTK_CFLAGS} 156 | GTK_LIBS = ${GTK_LIBS}" 157 | return 158 | fi 159 | 160 | if [ "$enable_gtk4" = "check" ] ; then 161 | if ! w_pkgconfig_check gtk4 ${W_GTK4_MIN_VERSION} ; then 162 | enable_gtk4='no' 163 | if [ "$force_gtk4" = "yes" ] ; then 164 | exit_error "ERROR: GTK4 was not found" 165 | fi 166 | else 167 | gtkpc='gtk4' 168 | enable_gtk4=yes 169 | enable_gtk2='no' 170 | enable_gtk3='no' 171 | fi 172 | fi 173 | 174 | if [ "$enable_gtk3" = "check" ] ; then 175 | if ! w_pkgconfig_check gtk+-3.0 ${W_GTK3_MIN_VERSION} ; then 176 | enable_gtk3='no' 177 | if [ "$force_gtk3" = "yes" ] ; then 178 | exit_error "ERROR: GTK3 was not found" 179 | fi 180 | else 181 | gtkpc='gtk+-3.0' 182 | enable_gtk3=yes 183 | enable_gtk2='no' 184 | enable_gtk4='no' 185 | fi 186 | fi 187 | 188 | if [ "$enable_gtk2" = "check" ] ; then 189 | if ! w_pkgconfig_check gtk+-2.0 ${W_GTK2_MIN_VERSION} ; then 190 | enable_gtk2='no' 191 | if [ "$force_gtk2" = "yes" ] ; then 192 | exit_error "ERROR: GTK2 was not found" 193 | fi 194 | else 195 | gtkpc='gtk+-2.0' 196 | enable_gtk2=yes 197 | enable_gtk3='no' 198 | enable_gtk4='no' 199 | fi 200 | fi 201 | 202 | if [ -z "$gtkpc" ] ; then 203 | exit_error "ERROR: GTK was not found" 204 | fi 205 | 206 | GTK_CFLAGS=$(run_pkg_config ${gtkpc} --cflags 2>/dev/null) 207 | GTK_LIBS=$(run_pkg_config ${gtkpc} --libs 2>/dev/null) 208 | 209 | # disabling deprecated gtk only makes sense if GTK is going to be compiled 210 | # so make it part of GTK_CFLAGS 211 | if test "$disable_deprecated_gtk" = "yes"; then 212 | GTK_CFLAGS="$GTK_CFLAGS -DGDK_DISABLE_DEPRECATED -DGDK_PIXBUF_DISABLE_DEPRECATED -DGDK_PIXBUF_DISABLE_SINGLE_INCLUDES -DGTK_DISABLE_DEPRECATED -DGTK_DISABLE_SINGLE_INCLUDES" 213 | fi 214 | 215 | config_h_have="$config_h_have GTK" 216 | config_mk_flags="$config_mk_flags GTK" 217 | } 218 | 219 | 220 | #============================================================= 221 | # W_GTK_ONLY_VERSION & W_GTK_MIN_VERSION 222 | #============================================================= 223 | 224 | opt_configure_gtk_only_version() 225 | { 226 | if [ "$W_GTK_IS_OPTIONAL" = "yes" ] ; then 227 | enable_gtk=check 228 | else 229 | enable_gtk=yes 230 | return 231 | fi 232 | for i in $@ 233 | do 234 | case $i in 235 | --enable-gtk) enable_gtk=yes ;; 236 | --disable-gtk) enable_gtk=no ;; 237 | esac 238 | done 239 | } 240 | 241 | opt_check_gtk_only_version() 242 | { 243 | if [ "$enable_gtk" = "no" ] ; then 244 | return 245 | fi 246 | 247 | if [ -n "$GTK_CFLAGS" ] || [ -n "$GTK_LIBS" ] ; then 248 | echo "Checking for GTK... $GTK_CFLAGS/$GTK_LIBS" 249 | else 250 | case $W_GTK_ONLY_VERSION in 251 | 2) gtkpc=gtk+-2.0 ;; 252 | 3) gtkpc=gtk+-3.0 ;; 253 | 4) gtkpc=gtk4 ;; 254 | *) exit_error "Invalid value for \$W_GTK_ONLY_VERSION : $W_GTK_ONLY_VERSION" ;; 255 | esac 256 | if ! w_pkgconfig_check ${gtkpc} ${W_GTK_MIN_VERSION} ; then 257 | if [ "$enable_gtk" = "yes" ] ; then 258 | exit_error "pkg-config: gtk was not found" 259 | fi 260 | fi 261 | #-- 262 | GTK_CFLAGS=$(run_pkg_config ${gtkpc} --cflags 2>/dev/null) 263 | GTK_LIBS=$(run_pkg_config ${gtkpc} --libs 2>/dev/null) 264 | fi 265 | 266 | # disabling deprecated gtk only makes sense if GTK is going to be compiled 267 | # so make it part of GTK_CFLAGS 268 | if test "$disable_deprecated_gtk" = "yes"; then 269 | GTK_CFLAGS="$GTK_CFLAGS -DGDK_DISABLE_DEPRECATED -DGDK_PIXBUF_DISABLE_DEPRECATED -DGDK_PIXBUF_DISABLE_SINGLE_INCLUDES -DGTK_DISABLE_DEPRECATED -DGTK_DISABLE_SINGLE_INCLUDES" 270 | fi 271 | 272 | config_h_have="$config_h_have GTK" 273 | config_mk_flags="$config_mk_flags GTK" 274 | } 275 | --------------------------------------------------------------------------------