From 0ec522a0929bd069a930ad7b652752dbe354daab Mon Sep 17 00:00:00 2001 From: Colin Clark Date: Mon, 14 Oct 2024 12:02:46 +0100 Subject: [PATCH] GTK4: Convert Geeqie to a GtkApplication GTK4 migration The following options are deleted: --fullscreen-start, --fullscreen-stop: Use --fullscreen which is a toggle --slideshow-start, --slideshow-stop: Use --slideshow which is a toggle --tools-show, --tools-hide: Use --tools which is a toggle --remote: No longer needed --disable-clutter: Start with GQ_DISABLE_CLUTTER=y[es] geeqie --cache-maintenance: Start with GQ_CACHE_MAINTENANCE=y[es] geeqie (disabled in this version) --new-instance: Start with GQ_NEW_INSTANCE=y[es] geeqie --blank, --list-add, --list-clear: I do not see the need for them GQ_CACHE_MAINTENANCE=y geeqie is disabled. It will be fixed in a future commit. --- auto-complete/geeqie | 11 +- auto-complete/geeqie-cache-maintenance | 71 + doc/docbook/CommandLineOptions.xml | 26 + meson.build | 14 +- org.geeqie.Geeqie.desktop.in | 4 +- org.geeqie.cache-maintenance.desktop.in | 14 + scripts/dbus-session.sh | 38 + scripts/generate-man-page.sh | 3 - scripts/image-test.py | 10 +- scripts/isolate-test.sh | 19 +- scripts/lua-test.sh | 8 +- scripts/test-ancillary-files.sh | 131 +- scripts/untranslated-text.sh | 6 + src/cache-maint.cc | 198 ++- src/cache-maint.h | 9 +- src/command-line-handling.cc | 1702 +++++++++++++++++++ src/command-line-handling.h | 30 + src/layout.cc | 14 +- src/main.cc | 1173 +++++-------- src/meson.build | 4 +- src/remote.cc | 2063 ----------------------- src/remote.h | 55 - src/ui-fileops.cc | 15 +- src/ui-fileops.h | 2 +- src/ui-utildlg.cc | 2 +- src/ui-utildlg.h | 3 +- src/ui/appimage-notification.ui | 21 - src/ui/meson.build | 3 +- src/ui/ui.gresource.xml | 1 - 29 files changed, 2497 insertions(+), 3153 deletions(-) create mode 100755 auto-complete/geeqie-cache-maintenance create mode 100644 org.geeqie.cache-maintenance.desktop.in create mode 100755 scripts/dbus-session.sh create mode 100644 src/command-line-handling.cc create mode 100644 src/command-line-handling.h delete mode 100644 src/remote.cc delete mode 100644 src/remote.h delete mode 100644 src/ui/appimage-notification.ui diff --git a/auto-complete/geeqie b/auto-complete/geeqie index 7f71d1b35..92d4ce4df 100644 --- a/auto-complete/geeqie +++ b/auto-complete/geeqie @@ -4,9 +4,7 @@ file_types='@(3fr|ani|arw|avif|bmp|cr2|cr3|crw|cur|dds|djvu|dng|erf|gif|gqv|heic actions='About AddMark0 AddMark1 AddMark2 AddMark3 AddMark4 AddMark5 AddMark6 AddMark7 AddMark8 AddMark9 AlterNone Animate Back ClearMarks CloseWindow ColorProfile0 ColorProfile1 ColorProfile2 ColorProfile3 ColorProfile4 ColorProfile5 ConnectZoom100 ConnectZoom200 ConnectZoom25 ConnectZoom300 ConnectZoom33 ConnectZoom400 ConnectZoom50 ConnectZoomFillHor ConnectZoomFillVert ConnectZoomFit ConnectZoomIn ConnectZoomOut Copy CopyImage CopyPath CopyPathUnquoted CropFourThree CropNone CropOneOne CropRectangle CropSixteenNine CropThreeTwo CutPath Delete DeleteWindow DrawRectangle Escape ExifRotate ExifWin FilterMark0 FilterMark1 FilterMark2 FilterMark3 FilterMark4 FilterMark5 FilterMark6 FilterMark7 FilterMark8 FilterMark9 FindDupes FirstImage FirstPage Flip FloatTools FolderTree Forward FullScreen Grayscale HelpChangeLog HelpContents HelpKbd HelpNotes HelpPdf HelpSearch HelpShortcuts HideBars HideSelectableToolbars HideTools HistogramChanB HistogramChanCycle HistogramChanG HistogramChanR HistogramChanRGB HistogramChanV HistogramModeCycle HistogramModeLin HistogramModeLog Home IgnoreAlpha ImageBack ImageForward ImageHistogram ImageOverlay ImageOverlayCycle IntMark0 IntMark1 IntMark2 IntMark3 IntMark4 IntMark5 IntMark6 IntMark7 IntMark8 IntMark9 KeywordAutocomplete LastImage LastPage LayoutConfig LogWindow Maintenance Mark0 Mark1 Mark2 Mark3 Mark4 Mark5 Mark6 Mark7 Mark8 Mark9 Mirror Move NewCollection NewFolder NewWindow NewWindowDefault NewWindowFromCurrent NextImage NextPage OpenArchive OpenCollection OpenRecent OpenWith OverUnderExposed PanView PermanentDelete Plugins Preferences PrevImage PrevPage Print Quit Rating0 Rating1 Rating2 Rating3 Rating4 Rating5 RatingM1 RectangularSelection Refresh Rename RenameWindow ResetMark0 ResetMark1 ResetMark2 ResetMark3 ResetMark4 ResetMark5 ResetMark6 ResetMark7 ResetMark8 ResetMark9 Rotate180 RotateCCW RotateCW SBar SBarSort SaveMetadata Search SearchAndRunCommand SelectAll SelectInvert SelectMark0 SelectMark1 SelectMark2 SelectMark3 SelectMark4 SelectMark5 SelectMark6 SelectMark7 SelectMark8 SelectMark9 SelectNone SetMark0 SetMark1 SetMark2 SetMark3 SetMark4 SetMark5 SetMark6 SetMark7 SetMark8 SetMark9 ShowFileFilter ShowInfoPixel ShowMarks SlideShow SlideShowFaster SlideShowPause SlideShowSlower SplitDownPane SplitHorizontal SplitNextPane SplitPaneSync SplitPreviousPane SplitQuad SplitSingle SplitTriple SplitUpPane SplitVertical StereoAuto StereoCross StereoCycle StereoOff StereoSBS Thumbnails ToggleMark0 ToggleMark1 ToggleMark2 ToggleMark3 ToggleMark4 ToggleMark5 ToggleMark6 ToggleMark7 ToggleMark8 ToggleMark9 UnselMark0 UnselMark1 UnselMark2 UnselMark3 UnselMark4 UnselMark5 UnselMark6 UnselMark7 UnselMark8 UnselMark9 Up UseColorProfiles UseImageProfile ViewIcons ViewInNewWindow ViewList WriteRotation WriteRotationKeepDate Zoom100 Zoom200 Zoom25 Zoom300 Zoom33 Zoom400 Zoom50 ZoomFillHor ZoomFillVert ZoomFit ZoomIn ZoomOut ZoomToRectangle' -options_basic='--blank --cache-maintenance= --disable-clutter --fullscreen --geometry= --help --list --new-instance --log-file= --remote --slideshow --with-tools --without-tools --version --show-log-window --debug= --grep=' - -options_remote='--action= --action-list --back --close-window --config-load= --cache-metadata --cache-render= --cache-render-recurse= --cache-render-shared= --cache-render-shared-recurse= --cache-shared= --cache-thumbs= --delay= --first --fullscreen --file= --File= --fullscreen-start --fullscreen-stop --geometry= --get-collection= --get-collection-list --get-destination= --get-file-info --get-filelist= --get-filelist-recurse= --get-rectangle --get-render-intent --get-selection --get-sidecars= --get-window-list --id= --last --list-add= --list-clear --lua= --new-window --next --pixel-info --print0 --PWD= --quit --raise --selection-add= --selection-clear --selection-remove= --slideshow --slideshow-recurse= --slideshow-start --slideshow-stop --tell --tools-hide --tools-show --view=' +options='--action= --action-list --back --cache-metadata --cache-render= --cache-render-recurse= --cache-render-shared= --cache-render-shared-recurse= --cache-shared= --cache-thumbs= --close-window --config-load= --debug= --delay= --file= --File= --first --fullscreen --geometry= --get-collection= --get-collection-list --get-destination= --get-file-info --get-filelist= --get-filelist-recurse= --get-rectangle --get-render-intent --get-selection --get-sidecars= --get-window-list --grep= --id= --last --log-file= --lua= --new-window --next --pixel-info --print0 --quit --raise --selection-add= --selection-clear --selection-remove= --show-log-window --slideshow --slideshow-recurse= --tell --tools --view= --version' _geeqie() { @@ -53,12 +51,7 @@ _geeqie() if [[ $cur == -* ]] then - if [[ $COMP_LINE == *"--remote "* ]] - then - COMPREPLY=($(compgen -W '$options_remote' -- "$cur")) - else - COMPREPLY=($(compgen -W '$options_basic' -- "$cur")) - fi + COMPREPLY=($(compgen -W '$options' -- "$cur")) [[ ${COMPREPLY-} == *[=] ]] && compopt -o nospace return diff --git a/auto-complete/geeqie-cache-maintenance b/auto-complete/geeqie-cache-maintenance new file mode 100755 index 000000000..8dbac491b --- /dev/null +++ b/auto-complete/geeqie-cache-maintenance @@ -0,0 +1,71 @@ +# bash completion for geeqie-cache-maintenance -*- shell-script -*- + +file_types='@(3fr|ani|arw|avif|bmp|cr2|cr3|crw|cur|dds|djvu|dng|erf|gif|gqv|heic|heif|ico|jp2|jpe|jpeg|jpg|jps|jxl|kdc|mef|mos|mpo|mrw|nef|orf|pbm|pdf|pef|pgm|png|pnm|ppm|psd|qif|qtif|raf|raw|rw2|scr|sr2|srf|svg|svgz|tga|tif|tiff|webp|xbm|xpm)' + +actions='About AddMark0 AddMark1 AddMark2 AddMark3 AddMark4 AddMark5 AddMark6 AddMark7 AddMark8 AddMark9 AlterNone Animate Back ClearMarks CloseWindow ColorProfile0 ColorProfile1 ColorProfile2 ColorProfile3 ColorProfile4 ColorProfile5 ConnectZoom100 ConnectZoom200 ConnectZoom25 ConnectZoom300 ConnectZoom33 ConnectZoom400 ConnectZoom50 ConnectZoomFillHor ConnectZoomFillVert ConnectZoomFit ConnectZoomIn ConnectZoomOut Copy CopyImage CopyPath CopyPathUnquoted CropFourThree CropNone CropOneOne CropRectangle CropSixteenNine CropThreeTwo CutPath Delete DeleteWindow DrawRectangle Escape ExifRotate ExifWin FilterMark0 FilterMark1 FilterMark2 FilterMark3 FilterMark4 FilterMark5 FilterMark6 FilterMark7 FilterMark8 FilterMark9 FindDupes FirstImage FirstPage Flip FloatTools FolderTree Forward FullScreen Grayscale HelpChangeLog HelpContents HelpKbd HelpNotes HelpSearch HelpShortcuts HideBars HideSelectableToolbars HideTools HistogramChanB HistogramChanCycle HistogramChanG HistogramChanR HistogramChanRGB HistogramChanV HistogramModeCycle HistogramModeLin HistogramModeLog Home IgnoreAlpha ImageBack ImageForward ImageHistogram ImageOverlay ImageOverlayCycle IntMark0 IntMark1 IntMark2 IntMark3 IntMark4 IntMark5 IntMark6 IntMark7 IntMark8 IntMark9 KeywordAutocomplete LastImage LastPage LayoutConfig LogWindow Maintenance Mark0 Mark1 Mark2 Mark3 Mark4 Mark5 Mark6 Mark7 Mark8 Mark9 Mirror Move NewCollection NewFolder NewWindow NewWindowDefault NewWindowFromCurrent NextImage NextPage OpenArchive OpenCollection OpenRecent OpenWith OverUnderExposed PanView PermanentDelete Plugins Preferences PrevImage PrevPage Print Quit Rating0 Rating1 Rating2 Rating3 Rating4 Rating5 RatingM1 RectangularSelection Refresh Rename RenameWindow ResetMark0 ResetMark1 ResetMark2 ResetMark3 ResetMark4 ResetMark5 ResetMark6 ResetMark7 ResetMark8 ResetMark9 Rotate180 RotateCCW RotateCW SBar SBarSort SaveMetadata Search SearchAndRunCommand SelectAll SelectInvert SelectMark0 SelectMark1 SelectMark2 SelectMark3 SelectMark4 SelectMark5 SelectMark6 SelectMark7 SelectMark8 SelectMark9 SelectNone SetMark0 SetMark1 SetMark2 SetMark3 SetMark4 SetMark5 SetMark6 SetMark7 SetMark8 SetMark9 ShowFileFilter ShowInfoPixel ShowMarks SlideShow SlideShowFaster SlideShowPause SlideShowSlower SplitDownPane SplitHorizontal SplitNextPane SplitPaneSync SplitPreviousPane SplitQuad SplitSingle SplitTriple SplitUpPane SplitVertical StereoAuto StereoCross StereoCycle StereoOff StereoSBS Thumbnails ToggleMark0 ToggleMark1 ToggleMark2 ToggleMark3 ToggleMark4 ToggleMark5 ToggleMark6 ToggleMark7 ToggleMark8 ToggleMark9 UnselMark0 UnselMark1 UnselMark2 UnselMark3 UnselMark4 UnselMark5 UnselMark6 UnselMark7 UnselMark8 UnselMark9 Up UseColorProfiles UseImageProfile ViewIcons ViewInNewWindow ViewList WriteRotation WriteRotationKeepDate Zoom100 Zoom200 Zoom25 Zoom300 Zoom33 Zoom400 Zoom50 ZoomFillHor ZoomFillVert ZoomFit ZoomIn ZoomOut ZoomToRectangle' + +options_basic='--blank --fullscreen --geometry= --help --help-all --help-gapplication --help-gtk --list --new-instance --log-file= --remote --slideshow --with-tools --without-tools --version --show-log-window --debug= --grep= --action= --action-list --back --close-window --config-load= --cache-metadata --cache-render= --cache-render-recurse= --cache-render-shared= --cache-render-shared-recurse= --cache-shared= --cache-thumbs= --delay= --first --fullscreen --file= --File= --fullscreen-start --fullscreen-stop --geometry= --get-collection= --get-collection-list --get-destination= --get-file-info --get-filelist= --get-filelist-recurse= --get-rectangle --get-render-intent --get-selection --get-sidecars= --get-window-list --id= --last --list-add= --list-clear --lua= --new-window --next --pixel-info --print0 --quit --raise --selection-add= --selection-clear --selection-remove= --slideshow --slideshow-recurse= --tell --tools --view=' + +options_remote='--action= --action-list --back --cache-metadata --cache-render= --cache-render-recurse= --cache-render-shared= --cache-render-shared-recurse= --cache-shared= --cache-thumbs= --close-window --config-load= --debug= --delay= --file= --File= --first --fullscreen --geometry= --get-collection= --get-collection-list --get-destination= --get-file-info --get-filelist= --get-filelist-recurse= --get-rectangle --get-render-intent --get-selection --get-sidecars= --get-window-list --grep --id= --last --log-file= --lua= --new-window --next --pixel-info --print0 --quit --raise --selection-add= --selection-clear --selection-remove= --show-log-window --slideshow --slideshow-recurse= --tell --tools --view= --version' + +_geeqie-cache-maintenance() +{ + local cur prev + _init_completion -s || return + + case "$prev" in + --action) + COMPREPLY=($(compgen -W '$actions' -- "$cur")) + return + ;; + + --cache-maintenance | --cache-render | --cache-render-recurse | --cache-render-shared-recurse | --get-filelist | --get-filelist-recurse | --slideshow-recurse) + _filedir + return + ;; + + --cache-shared | --cache-thumbs) + COMPREPLY=($(compgen -W 'clean clear' -- "$cur")) + return + ;; + + --config-load) + _filedir xml + return + ;; + + --file | --File | --get-destination | --get-sidecars | --list-add | --selection-add | --selection-remove | --view) + _filedir $file_types + return + ;; + + --get-collection) + collection="$(find $HOME/.local/share/geeqie/collections/ -maxdepth 1 -name "*gqv" -exec basename {} \;)" + COMPREPLY=($(compgen -W '$collection' -- "$cur")) + return + ;; + + --lua) + _filedir lua + return + ;; + esac + + if [[ $cur == -* ]] + then + if [[ $COMP_LINE == *"MAINTAIN"* ]] + then + COMPREPLY=($(compgen -W '$options_maintenance' -- "$cur")) + else + COMPREPLY=($(compgen -W '$options_basic' -- "$cur")) + fi + + [[ ${COMPREPLY-} == *[=] ]] && compopt -o nospace + return + fi + +_filedir $file_types + +} && complete -F _geeqie-cache-maintenance geeqie-cache-maintenance + +# ex: filetype=sh diff --git a/doc/docbook/CommandLineOptions.xml b/doc/docbook/CommandLineOptions.xml index 46571dc01..3b42929bc 100644 --- a/doc/docbook/CommandLineOptions.xml +++ b/doc/docbook/CommandLineOptions.xml @@ -474,6 +474,32 @@ can be used to modify this behavior on an individual basis e.g. + +All other command line parameters are used as plain files if they exist, or a URL or a folder. +The name of a collection, with or without either path or extension (.gqv) may be used. + +If more than one folder is on the command line, only the last will be used. + + +If more than one file is on the command line: + +If they are in the same folder, that folder will be opened and those files will be selected. +If they are not in the same folder, a new Collection containing those files will be opened. + +To run Geeqie as a new instance, use: +GQ_NON_UNIQUE=1 geeqie +Normally a single set of configuration files is used for all instances. +However, the environment variables XDG_CONFIG_HOME, XDG_CACHE_HOME, XDG_DATA_HOME +can be used to modify this behavior on an individual basis e.g. +XDG_CONFIG_HOME=/tmp/a XDG_CACHE_HOME=/tmp/b GQ_NON_UNIQUE=1 geeqie + +To disable Clutter use: +GQ_DISABLE_CLUTTER=1 geeqie + +To run or stop Geeqie in cache maintenance (non-GUI) mode use: +GQ_CACHE_MAINTENANCE=1 geeqie --help + +User manual: https://www.geeqie.org/help/GuideIndex.html diff --git a/meson.build b/meson.build index 7dfcb7147..44378cda5 100644 --- a/meson.build +++ b/meson.build @@ -671,6 +671,14 @@ i18n.merge_file( install : true, install_dir : join_paths(datadir, 'applications')) +i18n.merge_file( + input : 'org.geeqie.cache-maintenance.desktop.in', + output : 'org.geeqie.cache-maintenance.desktop', + type : 'desktop', + po_dir : podir, + install : true, + install_dir : join_paths(datadir, 'applications')) + i18n.merge_file( input : 'org.geeqie.Geeqie.appdata.xml.in', output : 'org.geeqie.Geeqie.appdata.xml', @@ -682,11 +690,12 @@ i18n.merge_file( configure_file(input: 'geeqie.spec.in', output: 'geeqie.spec', configuration: conf_data) isolate_test_sh = find_program('isolate-test.sh', dirs : scriptsdir, required : true) +dbus_session_sh = find_program('dbus-session.sh', dirs : scriptsdir, required : true) # Basic test of the executable xvfb = find_program('xvfb-run', required : false) if xvfb.found() - test_cmd = [xvfb.full_path(), '--auto-servernum', geeqie_exe.full_path(), '--version'] + test_cmd = [dbus_session_sh.full_path(), xvfb.full_path(), '--auto-servernum', geeqie_exe.full_path(), '--version'] test('Basic test', isolate_test_sh, args: test_cmd, timeout: 100, suite: 'functional') summary({'xvfb' : ['Test runs:', true]}, section : 'Testing', bool_yn : true) else @@ -716,8 +725,9 @@ if option.enabled() image_name = path_array[path_array.length() - 1] should_fail = image_name.startswith('fail') - test_cmd = [image_test_py.full_path(), geeqie_exe.full_path(), image] + test_cmd = [dbus_session_sh.full_path(), image_test_py.full_path(), geeqie_exe.full_path(), image] test('Image_ ' + image_name, isolate_test_sh, args: test_cmd, should_fail : should_fail, timeout: 100, suite: ['functional', 'image']) + endforeach summary({'Image tests' : ['Test runs:', true]}, section : 'Testing', bool_yn : true) else diff --git a/org.geeqie.Geeqie.desktop.in b/org.geeqie.Geeqie.desktop.in index 2d6f28a3b..4c3e651db 100644 --- a/org.geeqie.Geeqie.desktop.in +++ b/org.geeqie.Geeqie.desktop.in @@ -7,8 +7,8 @@ Icon=geeqie Type=Application Terminal=false # Startup notification disabled, since the remote -r switch may not open a new window... -#StartupNotify=false -#StartupWMClass=geeqie +StartupNotify=true +StartupWMClass=geeqie NotShowIn=X-Geeqie; Categories=Graphics;Viewer; MimeType=application/x-navi-animation;image/bmp;image/x-bmp;image/x-MS-bmp;image/gif;image/x-icon;image/jpeg;image/png;image/x-portable-anymap;image/x-portable-bitmap;image/x-portable-graymap;image/x-portable-pixmap;image/x-tga;image/tiff;image/x-xbitmap;image/x-xpixmap;image/svg;image/svg+xml;image/x-png;image/xpm;image/x-ico; diff --git a/org.geeqie.cache-maintenance.desktop.in b/org.geeqie.cache-maintenance.desktop.in new file mode 100644 index 000000000..1326011ba --- /dev/null +++ b/org.geeqie.cache-maintenance.desktop.in @@ -0,0 +1,14 @@ +[Desktop Entry] +Name=Geeqie Cache Maintenance +GenericName=Image Viewer +Comment=View and manage images +Exec=/bin/true +Icon=geeqie +Type=Application +Terminal=false +StartupNotify=true +StartupWMClass=geeqie +NotShowIn=X-Geeqie; +Categories=Graphics;Viewer; +MimeType=application/x-navi-animation;image/bmp;image/x-bmp;image/x-MS-bmp;image/gif;image/x-icon;image/jpeg;image/png;image/x-portable-anymap;image/x-portable-bitmap;image/x-portable-graymap;image/x-portable-pixmap;image/x-tga;image/tiff;image/x-xbitmap;image/x-xpixmap;image/svg;image/svg+xml;image/x-png;image/xpm;image/x-ico; +Keywords=Picture;Slideshow;Graphics; diff --git a/scripts/dbus-session.sh b/scripts/dbus-session.sh new file mode 100755 index 000000000..0ea34b5b4 --- /dev/null +++ b/scripts/dbus-session.sh @@ -0,0 +1,38 @@ +#!/bin/sh +#********************************************************************** +# Copyright (C) 2024 - The Geeqie Team +# +# Author: Colin Clark +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +#********************************************************************** + +set -e + +# This will automatically pass the command name and args in the expected order. +# And `set -e` (above) means that we'll automatically exit with the same return +# code as our sub-command. +# Start with a clean environment containing only these variables. +# +# G_DEBUG="fatal-warnings" will force an abort if a warning or +# critical error is encountered. +# https://docs.gtk.org/glib/running.html#environment-variables + +# Inhibit shellcheck warning SC2154 - var is referenced but not assigned +echo "${DBUS_SESSION_BUS_ADDRESS:-}" +echo "${XDG_CONFIG_HOME:-}" +echo "${XDG_RUNTIME_DIR:-}" + +env -i DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" HOME="$HOME" XDG_CONFIG_HOME="$XDG_CONFIG_HOME" XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" G_DEBUG="fatal-warnings" "$@" diff --git a/scripts/generate-man-page.sh b/scripts/generate-man-page.sh index 25a169843..b28e0b196 100755 --- a/scripts/generate-man-page.sh +++ b/scripts/generate-man-page.sh @@ -39,9 +39,6 @@ zooming, panning, thumbnails and sorting images into collections. Generated for version: -[SEE ALSO] -Full documentation: https://www.geeqie.org/help/GuideIndex.html - [BUGS] Please send bug reports and feedback to https://github.com/BestImageViewer/geeqie/issues diff --git a/scripts/image-test.py b/scripts/image-test.py index 900f2007d..01817f9c0 100755 --- a/scripts/image-test.py +++ b/scripts/image-test.py @@ -78,7 +78,7 @@ def __str__(self) -> str: def main(argv) -> int: geeqie_exe = argv[1] - test_image_path = argv[2] + test_image_path = "--file=" + argv[2] # All geeqie commands start with this. geeqie_cmd_prefix = ["xvfb-run", "--auto-servernum", geeqie_exe] @@ -93,9 +93,9 @@ def main(argv) -> int: # try/finally to ensure we clean up geeqie_proc try: start_time = time.monotonic() - # Wait up to MAX_GEEQIE_INIT_TIME_S for remote to initialize. - command_fifo_path = pathlib.Path(os.environ["XDG_CONFIG_HOME"], "geeqie/.command") + command_fifo_path = pathlib.Path(os.environ["XDG_CONFIG_HOME"], "geeqie/layouts") + while not command_fifo_path.exists(): time.sleep(1) if time.monotonic() - start_time > MAX_GEEQIE_INIT_TIME_S: @@ -107,7 +107,7 @@ def main(argv) -> int: raise GeeqieTestError("2-second post-init check", geeqie_proc) file_info_result = subprocess.run( - args=[*geeqie_cmd_prefix, "--remote", "--get-file-info"], + args=[*geeqie_cmd_prefix, "--get-file-info"], capture_output=True, text=True, timeout=MAX_REMOTE_CMD_TIME_S) # Check if Geeqie crashed (which would cause xvfb-run to terminate) @@ -116,7 +116,7 @@ def main(argv) -> int: raise GeeqieTestError("remote command processing", geeqie_proc) # Request shutdown - subprocess.run(args=[*geeqie_cmd_prefix, "--remote", "--quit"], + subprocess.run(args=[*geeqie_cmd_prefix, "--quit"], timeout=MAX_REMOTE_CMD_TIME_S) # If there's a timeout, or the exit code isn't zero, flag it. diff --git a/scripts/isolate-test.sh b/scripts/isolate-test.sh index df6019b99..1f9d128e2 100755 --- a/scripts/isolate-test.sh +++ b/scripts/isolate-test.sh @@ -25,8 +25,9 @@ ## that might be running on the host. Passes all args through and passes ## the return code back. ## -## $1 Test executable -## +## $1 Full path to dbus-session.sh +## $2 Path to test executable +## $3 Path to file to test ## set -e @@ -63,12 +64,8 @@ chmod 0700 "$XDG_RUNTIME_DIR" cd mkdir -p "$XDG_CONFIG_HOME" -# This will automatically pass the command name and args in the expected order. -# And `set -e` (above) means that we'll automatically exit with the same return -# code as our sub-command. -# Start with a clean environment containing only these variables. -# -# G_DEBUG="fatal-warnings" will force an abort if a warning or -# critical error is encountered. -# https://docs.gtk.org/glib/running.html#environment-variables -env -i G_DEBUG="fatal-warnings" HOME="$HOME" XDG_CONFIG_HOME="$XDG_CONFIG_HOME" XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" "$@" +# Primary and remote instances of GtkApplication communicate with D-Bus +dbus_session_sh="$1" +shift + +exec dbus-run-session -- "$dbus_session_sh" "$@" 2>/dev/null diff --git a/scripts/lua-test.sh b/scripts/lua-test.sh index 5b953e80e..fcd02a3d3 100755 --- a/scripts/lua-test.sh +++ b/scripts/lua-test.sh @@ -26,6 +26,8 @@ ## ## Create a basic image and run all lua built-in functions on it. ## The image file and the Lua test file are created within this script. +## +## @FIXME Running on GitHub gives additional dbind-WARNINGs. The data required is the last n lines. geeqie_exe="$1" @@ -63,7 +65,7 @@ printf "%s" "$lua_test" > "$lua_test_file" xvfb-run --auto-servernum "$geeqie_exe" & # Wait for remote to initialize -while [ ! -e "$config_home/geeqie/.command" ] ; +while [ ! -e "$config_home/geeqie/layouts" ] ; do sleep 1 done @@ -71,8 +73,8 @@ done sleep 2 base_lua=$(basename "$lua_test_file") -result=$(xvfb-run --auto-servernum "$geeqie_exe" --remote --lua="$lua_test_image","$base_lua") -xvfb-run --auto-servernum "$geeqie_exe" --remote --quit +result=$(xvfb-run --auto-servernum "$geeqie_exe" --lua="$lua_test_image","$base_lua") +xvfb-run --auto-servernum "$geeqie_exe" --quit ## @FIXME Running on GitHub gives additional dbind-WARNINGs. The data required is the last n lines. result_tail=$(printf "%s" "$result" | tail --lines=7) diff --git a/scripts/test-ancillary-files.sh b/scripts/test-ancillary-files.sh index 125afd536..92e28b109 100755 --- a/scripts/test-ancillary-files.sh +++ b/scripts/test-ancillary-files.sh @@ -279,7 +279,7 @@ EOF fi # Command line completion -## Check the sections: actions, options_basic, options_remote +## Check the sections: actions, options. ## The file_types section is not checked. ## Look for options not included and options erroneously included. @@ -300,22 +300,16 @@ actions_cc=$(mktemp "${TMPDIR:-/tmp}/geeqie.XXXXXXXXXX") actions_help=$(mktemp "${TMPDIR:-/tmp}/geeqie.XXXXXXXXXX") actions_help_filtered=$(mktemp "${TMPDIR:-/tmp}/geeqie.XXXXXXXXXX") help_output=$(mktemp "${TMPDIR:-/tmp}/geeqie.XXXXXXXXXX") -options_basic_cc=$(mktemp "${TMPDIR:-/tmp}/geeqie.XXXXXXXXXX") -options_basic_help=$(mktemp "${TMPDIR:-/tmp}/geeqie.XXXXXXXXXX") -options_remote_cc=$(mktemp "${TMPDIR:-/tmp}/geeqie.XXXXXXXXXX") -options_remote_help=$(mktemp "${TMPDIR:-/tmp}/geeqie.XXXXXXXXXX") - -options_basic1=$(grep 'options_basic=' ./auto-complete/geeqie) -options_basic2=$(echo "$options_basic1" | cut -c 16-) -options_basic3=$(echo "$options_basic2" | sed "s/\x27//g") -options_basic4=$(echo "$options_basic3" | sed "s/ /\n/g") -echo "$options_basic4" | sort > "$options_basic_cc" - -options_remote1=$(grep 'options_remote=' ./auto-complete/geeqie) -options_remote2=$(echo "$options_remote1" | cut -c 17-) -options_remote3=$(echo "$options_remote2" | sed "s/\x27//g") -options_remote4=$(echo "$options_remote3" | sed "s/ /\n/g") -echo "$options_remote4" | sort > "$options_remote_cc" +options_cc=$(mktemp "${TMPDIR:-/tmp}/geeqie.XXXXXXXXXX") +options_help=$(mktemp "${TMPDIR:-/tmp}/geeqie.XXXXXXXXXX") + +options1=$(grep 'options=' ./auto-complete/geeqie) +# length(options=') = 9 +options2=$(echo "$options1" | cut -c 9-) +# \x27 is "-" character +options3=$(echo "$options2" | sed "s/\x27//g") +options4=$(echo "$options3" | sed "s/ /\n/g") +echo "$options4" | sort > "$options_cc" action_list1=$(grep 'actions=' ./auto-complete/geeqie) action_list2=$(echo "$action_list1" | cut --delimiter='=' --fields=2) @@ -323,37 +317,27 @@ action_list3=$(echo "$action_list2" | sed "s/\x27//g") action_list4=$(echo "$action_list3" | sed 's/ /\n/g') echo "$action_list4" | sort > "$actions_cc" -./scripts/isolate-test.sh xvfb-run --auto-servernum "$geeqie_exe" --help > "$help_output" +xvfb-run --auto-servernum "$geeqie_exe" --help > "$help_output" -awk -W posix -v options_basic_help="$options_basic_help" -v options_remote_help="$options_remote_help" ' +awk -W posix -v options_help="$options_help" ' BEGIN { LINT = "fatal" valid_found = 0 } -/Valid options/ {valid_found = 1} -/Remote command/ {valid_found = 0} +/Application Options/ {valid_found = 1} +/--display=/ {valid_found = 0} /--/ && valid_found { start = match($0, /--/) new=substr($0, start) {gsub(/ .*/, "", new)} {gsub(/=.*/, "=", new)} {gsub(/\[.*/, "", new)} - print new >> options_basic_help - } - -/--/ && ! valid_found { - start = match($0, /--/) - new = substr($0, start) - {gsub(/ .*/, "", new)} - {gsub(/=.*/, "=", new)} - {gsub(/\[.*/, "", new)} - print new >> options_remote_help + print new >> options_help } END { -close(options_basic_help) -close(options_remote_help) +close(options_help) } ' "$help_output" @@ -366,13 +350,13 @@ exit_status = 0 NR == FNR{a[$0] = "";next} !($0 in a) { exit_status = 1 - print "Bash completions - Basic option missing: " $0 + print "Bash completions - Option missing: " $0 } END { exit exit_status } -' "$options_basic_cc" "$options_basic_help" +' "$options_cc" "$options_help" if [ $? = 1 ] then @@ -387,83 +371,33 @@ exit_status = 0 NR == FNR{a[$0] = "";next} !($0 in a) { exit_status = 1 - print "Bash completions - Basic option error: " $0 + print "Bash completions - Option error: " $0 } END { exit exit_status } -' "$options_basic_help" "$options_basic_cc" +' "$options_help" "$options_cc" if [ $? = 1 ] then exit_status=1 fi -awk -W posix ' -BEGIN { -LINT = "fatal" -exit_status = 0 -} - -NR == FNR{a[$0] = "";next} !($0 in a) { - if (index($0, "desktop") == 0) - { - exit_status = 1 - print "Bash completions - Remote option missing: " $0 - } - } - -END { -exit exit_status -} -' "$options_remote_cc" "$options_remote_help" - -if [ $? = 1 ] -then - exit_status=1 -fi - -## @FIXME Differing configuration options are not handled -awk -W posix ' -BEGIN { -LINT = "fatal" -exit_status = 0 -} - -NR == FNR{a[$0] = "";next} !($0 in a) { - if (index($0, "desktop") == 0 && index($0, "lua") == 0) - { - exit_status = 1 - print "Bash completions - Remote option error: " $0 - } - } - -END { -exit exit_status -} -' "$options_remote_help" "$options_remote_cc" - -if [ $? = 1 ] -then - exit_status=1 -fi - -./scripts/isolate-test.sh xvfb-run --auto-servernum "$geeqie_exe" --remote --action-list --quit | cut --delimiter=' ' --fields=1 | sed '/^$/d' > "$actions_help" +xvfb-run --auto-servernum "$geeqie_exe" --action-list --quit | cut --delimiter=' ' --fields=1 | sed '/^$/d' > "$actions_help" ## @FIXME Find a better way to ignore the junk awk -W posix -v actions_help_filtered="$actions_help_filtered" ' BEGIN { LINT = "fatal" -remote_found = 0 +valid_found = 0 } -/Remote/ { - remote_found = 1 - next +/About/ { + valid_found = 1 } -/^[A-Z].*?/ && remote_found { +/^[A-Z].*?/ && valid_found { if ((index($0, "desktop") == 0) && (index($0, "glx") == 0) && (index($0, "Geeqie not running") == 0) && (index($0, "Gtk-Message") == 0)) { print $0 >> actions_help_filtered } @@ -517,12 +451,11 @@ then fi rm --force "$actions_cc" -rm --force "actions_help" -rm --force "actions_help_filtered" -rm --force "help_output" -rm --force "options_basic_cc" -rm --force "options_basic_help" -rm --force "options_remote_cc" -rm --force "options_remote_help" +rm --force "$actions_help" +rm --force "$actions_help_filtered" +rm --force "$help_output" +rm --force "$options_cc" +rm --force "$options_help" + exit "$exit_status" diff --git a/scripts/untranslated-text.sh b/scripts/untranslated-text.sh index 2495047fd..f662e164b 100755 --- a/scripts/untranslated-text.sh +++ b/scripts/untranslated-text.sh @@ -54,6 +54,7 @@ omit_text_array=" < @brief @param +BOLD_ON COPYRIGHT ColorSpace DEBUG @@ -73,9 +74,14 @@ PixbufRenderer PluginsMenu READ_ Separator +URL WRITE_ Wrap +XOFF \"Desktop\" +\"File\" +\"Geeqie\" +\"Geeqie AppImage\" \"Layout\" \"OK\" \"Xmp. diff --git a/src/cache-maint.cc b/src/cache-maint.cc index 8c6aeaedf..2bc67ffe9 100644 --- a/src/cache-maint.cc +++ b/src/cache-maint.cc @@ -65,6 +65,54 @@ struct CMData gboolean clear; gboolean metadata; gboolean remote; + GtkApplication *app; +}; + +struct CacheManager +{ + GenericDialog *dialog; + GtkWidget *folder_entry; + GtkWidget *progress; + + GList *list_todo; + + gint count_total; + gint count_done; +}; + +struct CacheOpsData +{ + GenericDialog *gd; + ThumbLoaderStd *tl; + CacheLoader *cl; + GSourceFunc destroy_func; /* Used by the command line prog. functions */ + GtkApplication *app; + + GList *list; + GList *list_dir; + + gint days; + gboolean clear; + + GtkWidget *button_close; + GtkWidget *button_stop; + GtkWidget *button_start; + GtkWidget *progress; + GtkWidget *progress_bar; + GtkWidget *spinner; + + GtkWidget *group; + GtkWidget *entry; + + gint count_total; + gint count_done; + + gboolean local; + gboolean recurse; + + gboolean remote; + + guint idle_id; /* event source id */ }; constexpr gint PURGE_DIALOG_WIDTH = 400; @@ -111,67 +159,47 @@ void cache_maintain_home_close(CMData *cm) *----------------------------------------------------------------------------- */ static gchar *cache_maintenance_path = nullptr; -static GtkStatusIcon *status_icon; -static void cache_manager_sim_remote(const gchar *path, gboolean recurse, GSourceFunc destroy_func); +static void cache_manager_sim_remote(GtkApplication *app, const gchar *path, gboolean recurse, GSourceFunc destroy_func); static gboolean cache_maintenance_sim_stop_cb(gpointer data) { + auto *cd = static_cast(data); + + g_application_withdraw_notification(G_APPLICATION(cd->app), "cache_maintenance"); + g_free(data); + exit(EXIT_SUCCESS); - return G_SOURCE_REMOVE; } static gboolean cache_maintenance_render_stop_cb(gpointer data) { - g_free(data); - gtk_status_icon_set_tooltip_text(status_icon, _("Geeqie: Creating sim data...")); - cache_manager_sim_remote(cache_maintenance_path, TRUE, cache_maintenance_sim_stop_cb); - return G_SOURCE_REMOVE; -} + auto *cd = static_cast(data); -static void cache_maintenance_clean_stop_cb(gpointer data) -{ - cache_maintain_home_close(static_cast(data)); - gtk_status_icon_set_tooltip_text(status_icon, _("Geeqie: Creating thumbs...")); - cache_manager_render_remote(cache_maintenance_path, TRUE, options->thumbnails.cache_into_dirs, cache_maintenance_render_stop_cb); -} + cache_maintenance_notification(cd->app, _("Creating sim data..."), TRUE); -static void cache_maintenance_user_cancel_cb() -{ - exit(EXIT_FAILURE); + cache_manager_sim_remote(cd->app, cache_maintenance_path, TRUE, cache_maintenance_sim_stop_cb); + + return G_SOURCE_REMOVE; } -static void cache_maintenance_status_icon_activate_cb(GtkStatusIcon *, gpointer) +static void cache_maintenance_clean_stop_cb(gpointer data) { - GtkWidget *menu; - GtkWidget *item; - - menu = gtk_menu_new(); - - item = gtk_menu_item_new_with_label(_("Exit Geeqie Cache Maintenance")); - - g_signal_connect(G_OBJECT(item), "activate", cache_maintenance_user_cancel_cb, item); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); - gtk_widget_show(item); + auto *cm = static_cast(data); - /* take ownership of menu */ - g_object_ref_sink(G_OBJECT(menu)); - gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr); + cache_maintenance_notification(cm->app, _("Creating thumbs..."), TRUE); + cache_manager_render_remote(cm->app, cache_maintenance_path, TRUE, options->thumbnails.cache_into_dirs, cache_maintenance_render_stop_cb); } -void cache_maintenance(const gchar *path) +void cache_maintenance(GtkApplication *app, const gchar *path) { cache_maintenance_path = g_strdup(path); - g_autoptr(GdkPixbuf) pixbuf_icon = pixbuf_inline(PIXBUF_INLINE_ICON); - status_icon = gtk_status_icon_new_from_pixbuf(pixbuf_icon); - gtk_status_icon_set_tooltip_text(status_icon, _("Geeqie: Cleaning thumbs...")); - gtk_status_icon_set_visible(status_icon, TRUE); - g_signal_connect(G_OBJECT(status_icon), "activate", G_CALLBACK(cache_maintenance_status_icon_activate_cb), NULL); + cache_maintenance_notification(app, _("Cleaning thumbs and sims..."), TRUE); - cache_maintain_home_remote(FALSE, FALSE, cache_maintenance_clean_stop_cb); + cache_maintain_home_remote(app, FALSE, FALSE, cache_maintenance_clean_stop_cb); } /* @@ -431,11 +459,13 @@ static void cache_maintain_home(gboolean metadata, gboolean clear, GtkWidget *pa * * */ -void cache_maintain_home_remote(gboolean metadata, gboolean clear, GDestroyNotify func) +void cache_maintain_home_remote(GtkApplication *app, gboolean metadata, gboolean clear, GDestroyNotify func) { CMData *cm = cache_maintain_data_new(clear, metadata, TRUE); if (!cm) return; + cm->app = app; + cm->idle_id = g_idle_add_full(G_PRIORITY_LOW, cache_maintain_home_cb, cm, func); } @@ -539,52 +569,6 @@ void cache_notify_cb(FileData *fd, NotifyType type, gpointer) *------------------------------------------------------------------- */ -struct CacheManager -{ - GenericDialog *dialog; - GtkWidget *folder_entry; - GtkWidget *progress; - - GList *list_todo; - - gint count_total; - gint count_done; -}; - -struct CacheOpsData -{ - GenericDialog *gd; - ThumbLoaderStd *tl; - CacheLoader *cl; - GSourceFunc destroy_func; /* Used by the command line prog. functions */ - - GList *list; - GList *list_dir; - - gint days; - gboolean clear; - - GtkWidget *button_close; - GtkWidget *button_stop; - GtkWidget *button_start; - GtkWidget *progress; - GtkWidget *progress_bar; - GtkWidget *spinner; - - GtkWidget *group; - GtkWidget *entry; - - gint count_total; - gint count_done; - - gboolean local; - gboolean recurse; - - gboolean remote; - - guint idle_id; /* event source id */ -}; - static void cache_manager_render_reset(CacheOpsData *cd) { filelist_free(cd->list); @@ -629,6 +613,11 @@ static void cache_manager_render_stop_cb(GenericDialog *, gpointer data) gq_gtk_entry_set_text(GTK_ENTRY(cd->progress), _("stopped")); cache_manager_render_finish(cd); + + if (cd->destroy_func) + { + g_idle_add(cd->destroy_func, nullptr); + } } static void cache_manager_render_folder(CacheOpsData *cd, FileData *dir_fd) @@ -879,7 +868,7 @@ static void cache_manager_render_dialog(GtkWidget *widget, const gchar *path) * * */ -void cache_manager_render_remote(const gchar *path, gboolean recurse, gboolean local, GSourceFunc destroy_func) +void cache_manager_render_remote(GtkApplication *app, const gchar *path, gboolean recurse, gboolean local, GSourceFunc destroy_func) { CacheOpsData *cd; @@ -888,6 +877,7 @@ void cache_manager_render_remote(const gchar *path, gboolean recurse, gboolean l cd->local = local; cd->remote = TRUE; cd->destroy_func = destroy_func; + cd->app = app; cache_manager_render_start_render_remote(cd, path); } @@ -1290,7 +1280,7 @@ static void cache_manager_sim_file_done_cb(CacheLoader *, gint, gpointer data) while (cache_manager_sim_file(cd)); } -static void cache_manager_sim_start_sim_remote(CacheOpsData *cd, const gchar *user_path) +static void cache_manager_sim_start_sim_remote(GtkApplication *, CacheOpsData *cd, const gchar *user_path) { gchar *path; @@ -1322,7 +1312,7 @@ static void cache_manager_sim_start_sim_remote(CacheOpsData *cd, const gchar *us * * */ -static void cache_manager_sim_remote(const gchar *path, gboolean recurse, GSourceFunc destroy_func) +static void cache_manager_sim_remote(GtkApplication *app, const gchar *path, gboolean recurse, GSourceFunc destroy_func) { CacheOpsData *cd; @@ -1330,8 +1320,9 @@ static void cache_manager_sim_remote(const gchar *path, gboolean recurse, GSourc cd->recurse = recurse; cd->remote = TRUE; cd->destroy_func = destroy_func; + cd->app = app; - cache_manager_sim_start_sim_remote(cd, path); + cache_manager_sim_start_sim_remote(app, cd, path); } static gboolean cache_manager_sim_file(CacheOpsData *cd) @@ -1715,6 +1706,35 @@ void cache_manager_show() pref_table_label(table, 1, 0, _("Run cache maintenance as a background job."), GTK_ALIGN_START); gtk_widget_set_sensitive(group, options->thumbnails.enable_caching); + /* @FIXME This feature does not work. The command line option must be used */ + gtk_widget_set_sensitive(group, FALSE); + gtk_widget_set_tooltip_text(button, _("Feature disabled in this version.\nUse command line:\nGQ_CACHE_MAINTENANCE= geeqie --cache-maintenance=")); + gtk_widget_show(cache_manager->dialog->dialog); } + +void cache_maintenance_notification(GtkApplication *app, const gchar *message, gboolean show_quit_button) +{ + GIcon *geeqie_icon; + GNotification *notification; + + notification = g_notification_new("Geeqie"); + geeqie_icon = g_themed_icon_new(PIXBUF_INLINE_ICON); + + g_notification_set_body(notification, message); + g_notification_set_icon(notification, geeqie_icon); + g_notification_set_priority(notification, G_NOTIFICATION_PRIORITY_LOW); + g_notification_set_priority(notification, G_NOTIFICATION_PRIORITY_URGENT); + g_notification_set_title(notification, _("Cache Maintenance")); + + if (show_quit_button) + { + g_notification_add_button(notification, _("Quit"), "app.quit"); + } + + g_application_send_notification(G_APPLICATION(app), "cache_maintenance", notification); + + g_object_unref(geeqie_icon); +} + /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/cache-maint.h b/src/cache-maint.h index 530ad3e6a..114af6afd 100644 --- a/src/cache-maint.h +++ b/src/cache-maint.h @@ -23,6 +23,7 @@ #define CACHE_MAINT_H #include +#include #include "typedefs.h" @@ -31,10 +32,12 @@ class FileData; void cache_notify_cb(FileData *fd, NotifyType type, gpointer data); void cache_manager_show(); -void cache_maintain_home_remote(gboolean metadata, gboolean clear, GDestroyNotify func); +void cache_maintain_home_remote(GtkApplication *app, gboolean metadata, gboolean clear, GDestroyNotify func); void cache_manager_standard_process_remote(gboolean clear); -void cache_manager_render_remote(const gchar *path, gboolean recurse, gboolean local, GSourceFunc destroy_func); -void cache_maintenance(const gchar *path); +void cache_manager_render_remote(GtkApplication *app, const gchar *path, gboolean recurse, gboolean local, GSourceFunc destroy_func); +void cache_maintenance(GtkApplication *app, const gchar *path); + +void cache_maintenance_notification(GtkApplication *app, const gchar *message, gboolean show_quit_button); #endif /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/command-line-handling.cc b/src/command-line-handling.cc new file mode 100644 index 000000000..6a280339b --- /dev/null +++ b/src/command-line-handling.cc @@ -0,0 +1,1702 @@ +/* + * Copyright (C) 2024 The Geeqie Team + * + * Author: Colin Clark + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "command-line-handling.h" + +#include + +#include "cache-maint.h" +#include "collect-io.h" +#include "collect.h" +#include "compat.h" +#include "debug.h" +#include "exif.h" +#include "filedata.h" +#include "filefilter.h" +#include "glua.h" +#include "img-view.h" +#include "intl.h" +#include "layout-image.h" +#include "layout-util.h" +#include "layout.h" +#include "logwindow.h" +#include "main-defines.h" +#include "main.h" +#include "misc.h" +#include "pixbuf-renderer.h" +#include "rcfile.h" +#include "secure-save.h" +#include "slideshow.h" +#include "ui-fileops.h" +#include "ui-misc.h" +#include "utilops.h" + +namespace +{ + +enum OUTPUT_TYPE { + GUI, /**< Option requires the GUI */ + TEXT, /**< Option only outputs text to the command line */ + N_A /**< Not Applicable */ +}; + +enum OPTION_TYPE { + PRIMARY_REMOTE, /**< Option can be used in both primary and remote instances */ + REMOTE, /**< Option can be used only in remote instances */ + NA /**< Not Applicable */ +}; + +struct CommandLineOptionEntry +{ + const gchar *option_name; + void (*func)(GtkApplication *app, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *); + OPTION_TYPE option_type; + OUTPUT_TYPE display_type; +}; + +/* https://en.wikipedia.org/wiki/ANSI_escape_code */ +#define BOLD_ON "\033[1m" +#define BOLD_OFF "\033[0m" + +LayoutWindow *lw_id = nullptr; /* points to the window set by the --id option */ + +/* To enable file names containing newlines to be processed correctly, + * the --print0 remote option sets returned data to be terminated with a null + * character rather a newline + */ +gboolean print0 = FALSE; + +/** + * @brief Ensures file path is absolute. + * @param[in] filename Filepath, absolute or relative to calling directory + * @param[in] app_command_line + * @returns absolute path + * + * If first character of input filepath is not the directory + * separator, assume it as a relative path and prepend + * the directory the remote command was initiated from + * + * Return value must be freed with g_free() + */ +gchar *set_cwd(gchar *filename, GApplicationCommandLine *app_command_line) +{ + gchar *temp; + + if (strncmp(filename, G_DIR_SEPARATOR_S, 1) != 0) + { + temp = g_build_filename(g_application_command_line_get_cwd(app_command_line), filename, NULL); + } + else + { + temp = g_strdup(filename); + } + + return temp; +} + +gboolean close_window_cb(gpointer) +{ + if (!layout_valid(&lw_id)) return FALSE; + + layout_menu_close_cb(nullptr, lw_id); + + return G_SOURCE_REMOVE; +} + +gchar *config_file_path(const gchar *param) +{ + gchar *path = nullptr; + gchar *full_name = nullptr; + + if (file_extension_match(param, ".xml")) + { + path = g_build_filename(get_window_layouts_dir(), param, NULL); + } + else if (file_extension_match(param, nullptr)) + { + full_name = g_strconcat(param, ".xml", NULL); + path = g_build_filename(get_window_layouts_dir(), full_name, NULL); + } + + if (!isfile(path)) + { + g_free(path); + path = nullptr; + } + + g_free(full_name); + return path; +} + +gboolean is_config_file(const gchar *param) +{ + gchar *name = nullptr; + + name = config_file_path(param); + if (name) + { + g_free(name); + return TRUE; + } + return FALSE; +} + +gboolean wait_cb(gpointer data) +{ + gint position = GPOINTER_TO_INT(data); + gint x = position >> 16; + gint y = position - (x << 16); + + gq_gtk_window_move(GTK_WINDOW(lw_id->window), x, y); + + return G_SOURCE_REMOVE; +} + +void gq_action(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ + gchar *text; + gboolean remote_instance; + + remote_instance = g_application_command_line_get_is_remote(app_command_line); + + g_variant_dict_lookup(command_line_options_dict, "action", "s", &text); + + layout_valid(&lw_id); + + if (g_strstr_len(text, -1, ".desktop") != nullptr) + { + file_util_start_editor_from_filelist(text, layout_selection_list(lw_id), layout_get_path(lw_id), lw_id->window); + } + else + { + GtkAction *action; + + action = gq_gtk_action_group_get_action(lw_id->action_group, text); + if (action) + { + gq_gtk_action_activate(action); + } + else + { + g_application_command_line_print(app_command_line, _("Action %s is unknown\n"), text); + if (!remote_instance) + { + exit_program(); + } + } + } + g_free(text); +} + +void gq_action_list(GtkApplication *, GApplicationCommandLine *app_command_line,GVariantDict *, GList *) +{ + gint max_length = 0; + + std::vector list = get_action_items(); + + /* Get the length required for padding */ + for (const ActionItem &action_item : list) + { + const auto length = g_utf8_strlen(action_item.name, -1); + max_length = MAX(length, max_length); + } + + /* Pad the action names to the same column for readable output */ + g_autoptr(GString) out_string = g_string_new(nullptr); + for (const ActionItem &action_item : list) + { + g_string_append_printf(out_string, "%-*s", max_length + 4, action_item.name); + out_string = g_string_append(out_string, action_item.label); + out_string = g_string_append(out_string, "\n"); + } + + g_application_command_line_print(app_command_line, "%s\n", out_string->str); +} + +void gq_back(GtkApplication *, GApplicationCommandLine *, GVariantDict *, GList *) +{ + layout_image_prev(lw_id); +} + +void gq_cache_metadata(GtkApplication *app, GApplicationCommandLine *, GVariantDict *, GList *) +{ + cache_maintain_home_remote(app, TRUE, FALSE, nullptr); +} + +void gq_cache_render(GtkApplication *app, GApplicationCommandLine *, GVariantDict *command_line_options_dict, GList *) +{ + gchar* text; + + g_variant_dict_lookup(command_line_options_dict, "cache-render", "&s", &text); + + cache_manager_render_remote(app, text, FALSE, FALSE, nullptr); +} + +void gq_cache_render_recurse(GtkApplication *app, GApplicationCommandLine *, GVariantDict *command_line_options_dict, GList *) +{ + gchar* text; + + g_variant_dict_lookup(command_line_options_dict, "cache-render-recurse", "&s", &text); + + cache_manager_render_remote(app, text, TRUE, FALSE, nullptr); +} + +void gq_cache_render_shared(GtkApplication *app, GApplicationCommandLine *, GVariantDict *command_line_options_dict, GList *) +{ + gchar* text; + + g_variant_dict_lookup(command_line_options_dict, "cache-render-shared", "&s", &text); + + if(options->thumbnails.spec_standard) + { + cache_manager_render_remote(app, text, FALSE, TRUE, nullptr); + } +} + +void gq_cache_render_shared_recurse(GtkApplication *app, GApplicationCommandLine *, GVariantDict *command_line_options_dict, GList *) +{ + gchar* text; + + g_variant_dict_lookup(command_line_options_dict, "cache-shared-recurse", "&s", &text); + + if(options->thumbnails.spec_standard) + { + cache_manager_render_remote(app, text, TRUE, TRUE, nullptr); + } +} + +void gq_cache_shared(GtkApplication *, GApplicationCommandLine *, GVariantDict *command_line_options_dict, GList *) +{ + gchar* text; + + g_variant_dict_lookup(command_line_options_dict, "cache-shared", "&s", &text); + + if (!g_strcmp0(text, "clear")) + { + cache_manager_standard_process_remote(TRUE); + } + else if (!g_strcmp0(text, "clean")) + { + cache_manager_standard_process_remote(FALSE); + } +} + +void gq_cache_thumbs(GtkApplication *app, GApplicationCommandLine *, GVariantDict *command_line_options_dict, GList *) +{ + gchar* text; + + g_variant_dict_lookup(command_line_options_dict, "cache-thumbs", "&s", &text); + + if (!g_strcmp0(text, "clear")) + { + cache_maintain_home_remote(app, FALSE, TRUE, nullptr); + } + else if (!g_strcmp0(text, "clean")) + { + cache_maintain_home_remote(app, FALSE, FALSE, nullptr); + } +} + +void gq_close_window(GtkApplication *, GApplicationCommandLine *, GVariantDict *, GList *) +{ + g_idle_add((close_window_cb), nullptr); +} + +void gq_config_load(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ + gchar *text; + g_variant_dict_lookup(command_line_options_dict, "config-load", "&s", &text); + gchar *filename = expand_tilde(text); + + if (!g_strstr_len(filename, -1, G_DIR_SEPARATOR_S)) + { + if (is_config_file(filename)) + { + gchar *tmp = config_file_path(filename); + g_free(filename); + filename = tmp; + } + } + + if (isfile(filename)) + { + load_config_from_file(filename, FALSE); + } + else + { + g_application_command_line_print(app_command_line, "remote sent filename that does not exist:\"%s\"\n", filename ? filename : "(null)"); + layout_set_path(nullptr, homedir()); + } + + g_free(filename); +} + +void gq_debug(GtkApplication *, GApplicationCommandLine *, GVariantDict *command_line_options_dict, GList *) +{ + gint debug_level; + g_variant_dict_lookup(command_line_options_dict, "debug", "i", &debug_level); + set_debug_level(debug_level); +} + +void gq_delay(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ + gchar *text; + g_variant_dict_lookup(command_line_options_dict, "delay", "&s", &text); + + gdouble t1; + gdouble t2; + gdouble t3; + gdouble n; + gint res; + + res = sscanf(text, "%lf:%lf:%lf", &t1, &t2, &t3); + if (res == 3) + { + n = (t1 * 3600) + (t2 * 60) + t3; + if (n < SLIDESHOW_MIN_SECONDS || n > SLIDESHOW_MAX_SECONDS || + t1 >= 24 || t2 >= 60 || t3 >= 60) + { + g_application_command_line_print(app_command_line, _("Remote slideshow delay out of range (%.1f to %.1f)\n"), + SLIDESHOW_MIN_SECONDS, SLIDESHOW_MAX_SECONDS); + return; + } + } + else if (res == 2) + { + n = t1 * 60 + t2; + if (n < SLIDESHOW_MIN_SECONDS || n > SLIDESHOW_MAX_SECONDS || + t1 >= 60 || t2 >= 60) + { + g_application_command_line_print(app_command_line, _("Remote slideshow delay out of range (%.1f to %.1f)\n"), + SLIDESHOW_MIN_SECONDS, SLIDESHOW_MAX_SECONDS); + return; + } + } + else if (res == 1) + { + n = t1; + if (n < SLIDESHOW_MIN_SECONDS || n > SLIDESHOW_MAX_SECONDS) + { + g_application_command_line_print(app_command_line,_("Remote slideshow delay out of range (%.1f to %.1f)\n"), + SLIDESHOW_MIN_SECONDS, SLIDESHOW_MAX_SECONDS); + return; + } + } + else + { + n = 0; + } + + options->slideshow.delay = static_cast(n * 10.0 + 0.01); +} + +void file_load_no_raise(const gchar *text, GApplicationCommandLine *app_command_line) +{ + gchar *filename; + gchar *tilde_filename; + g_autofree gchar *tmp_file = nullptr; + + tmp_file = download_web_file(text, TRUE, nullptr); + + if (!tmp_file) + { + tilde_filename = expand_tilde(text); + } + else + { + tilde_filename = g_strdup(tmp_file); + } + + filename = set_cwd(tilde_filename, app_command_line); + + if (isfile(filename)) + { + if (file_extension_match(filename, GQ_COLLECTION_EXT)) + { + collection_window_new(filename); + } + else + { + layout_set_path(lw_id, filename); + } + } + else if (isdir(filename)) + { + layout_set_path(lw_id, filename); + } + else + { + /* shoould not happen */ + g_application_command_line_print(app_command_line, "File " BOLD_ON "%s" BOLD_OFF " does not exist\n", filename); + } + + g_free(filename); + g_free(tilde_filename); +} + +void gq_file(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ + + gchar *text = nullptr; + g_variant_dict_lookup(command_line_options_dict, "file", "&s", &text); + + if (text) + { + file_load_no_raise(text, app_command_line); + gtk_window_present(GTK_WINDOW(lw_id->window)); + } +} + +void gq_File(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ + gchar *text; + g_variant_dict_lookup(command_line_options_dict, "File", "&s", &text); + + file_load_no_raise(text, app_command_line); +} + +void gq_first(GtkApplication *, GApplicationCommandLine *, GVariantDict *, GList *) +{ + layout_image_first(lw_id); +} + +void gq_fullscreen(GtkApplication *, GApplicationCommandLine *, GVariantDict *, GList *) +{ + layout_image_full_screen_toggle(lw_id); +} + +void gq_geometry(GtkApplication *, GApplicationCommandLine *, GVariantDict *command_line_options_dict, GList *) +{ + gchar **geometry; + gchar *text; + + g_variant_dict_lookup(command_line_options_dict, "geometry", "&s", &text); + + if (text[0] == '+') + { + geometry = g_strsplit_set(text, "+", 3); + if (geometry[1] != nullptr && geometry[2] != nullptr ) + { + gq_gtk_window_move(GTK_WINDOW(lw_id->window), atoi(geometry[1]), atoi(geometry[2])); + } + } + else + { + geometry = g_strsplit_set(text, "+x", 4); + if (geometry[0] != nullptr && geometry[1] != nullptr) + { + gtk_window_resize(GTK_WINDOW(lw_id->window), atoi(geometry[0]), atoi(geometry[1])); + } + if (geometry[2] != nullptr && geometry[3] != nullptr) + { + /* There is an occasional problem with a window_move immediately after a window_resize */ + g_idle_add(wait_cb, GINT_TO_POINTER((atoi(geometry[2]) << 16) + atoi(geometry[3]))); + } + } + + g_strfreev(geometry); +} + +void gq_get_collection(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ + gchar *text; + g_variant_dict_lookup(command_line_options_dict, "get-collection", "&s", &text); + + GString *contents = g_string_new(nullptr); + + if (is_collection(text)) + { + collection_contents(text, &contents); + g_application_command_line_print(app_command_line, "%s%c", contents->str, print0 ? 0 : '\n'); + } + else + { + g_application_command_line_print(app_command_line, "%s%s%s%s%s", "Collection ", BOLD_ON, text, BOLD_OFF, " does not exist\n"); + } + + g_string_free(contents, TRUE); +} + +void gq_get_collection_list(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ + gchar *text; + g_variant_dict_lookup(command_line_options_dict, "get-collection-list", "&s", &text); + + GList *collection_list = nullptr; + GList *work; + GString *out_string = g_string_new(nullptr); + + collect_manager_list(&collection_list, nullptr, nullptr); + + work = collection_list; + while (work) + { + auto collection_name = static_cast(work->data); + out_string = g_string_append(out_string, collection_name); + out_string = g_string_append_c(out_string, print0 ? 0 : '\n'); + + work = work->next; + } + + g_application_command_line_print(app_command_line, "%s%c", out_string->str, print0 ? 0 : '\n'); + + g_list_free_full(collection_list, g_free); + g_string_free(out_string, TRUE); +} + +void gq_get_destination(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ + gchar *text; + g_variant_dict_lookup(command_line_options_dict, "get-destination", "&s", &text); + + gchar *filename = expand_tilde(text); + FileData *fd = file_data_new_group(filename); + + if (fd->change && fd->change->dest) + { + g_application_command_line_print(app_command_line, "%s", fd->change->dest); + } + + g_free(filename); +} + +void gq_get_file_info(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *, GList *) +{ + FileData *fd; + GString *out_string; + FileFormatClass format_class; + + if (!layout_valid(&lw_id)) return; + + const gchar *filename = image_get_path(lw_id->image); + if (filename) + { + fd = file_data_new_group(filename); + out_string = g_string_new(nullptr); + + if (fd->pixbuf) + { + format_class = filter_file_get_class(filename); + } + else + { + format_class = FORMAT_CLASS_UNKNOWN; + } + + g_string_append_printf(out_string, _("Class: %s\n"), format_class_list[format_class]); + + if (fd->page_total > 1) + { + g_string_append_printf(out_string, _("Page no: %d/%d\n"), fd->page_num + 1, fd->page_total); + } + + if (fd->exif) + { + g_autofree gchar *country_name = exif_get_data_as_text(fd->exif, "formatted.countryname"); + if (country_name) + { + g_string_append_printf(out_string, _("Country name: %s\n"), country_name); + } + + g_autofree gchar *country_code = exif_get_data_as_text(fd->exif, "formatted.countrycode"); + if (country_code) + { + g_string_append_printf(out_string, _("Country code: %s\n"), country_code); + } + + g_autofree gchar *timezone = exif_get_data_as_text(fd->exif, "formatted.timezone"); + if (timezone) + { + g_string_append_printf(out_string, _("Timezone: %s\n"), timezone); + } + + g_autofree gchar *local_time = exif_get_data_as_text(fd->exif, "formatted.localtime"); + if (local_time) + { + g_string_append_printf(out_string, ("Local time: %s\n"), local_time); + } + } + + g_application_command_line_print(app_command_line, "%s%c", out_string->str, print0 ? 0 : '\n'); + g_string_free(out_string, TRUE); + file_data_unref(fd); + } +} + +void get_filelist(GApplicationCommandLine *app_command_line, const gchar *text, gboolean recurse) +{ + GList *list = nullptr; + FileFormatClass format_class; + FileData *dir_fd; + FileData *fd; + GList *work; + gchar *tilde_filename; + + if (strcmp(text, "") == 0) + { + if (layout_valid(&lw_id)) + { + dir_fd = file_data_new_dir(lw_id->dir_fd->path); + } + else + { + return; + } + } + else + { + tilde_filename = expand_tilde(text); + if (isdir(tilde_filename)) + { + dir_fd = file_data_new_dir(tilde_filename); + } + else + { + g_free(tilde_filename); + return; + } + g_free(tilde_filename); + } + + if (recurse) + { + list = filelist_recursive(dir_fd); + } + else + { + filelist_read(dir_fd, &list, nullptr); + } + + GString *out_string = g_string_new(nullptr); + work = list; + while (work) + { + fd = static_cast(work->data); + g_string_append(out_string, fd->path); + format_class = filter_file_get_class(fd->path); + + switch (format_class) + { + case FORMAT_CLASS_IMAGE: + out_string = g_string_append(out_string, " Class: Image"); + break; + case FORMAT_CLASS_RAWIMAGE: + out_string = g_string_append(out_string, " Class: RAW image"); + break; + case FORMAT_CLASS_META: + out_string = g_string_append(out_string, " Class: Metadata"); + break; + case FORMAT_CLASS_VIDEO: + out_string = g_string_append(out_string, " Class: Video"); + break; + case FORMAT_CLASS_COLLECTION: + out_string = g_string_append(out_string, " Class: Collection"); + break; + case FORMAT_CLASS_DOCUMENT: + out_string = g_string_append(out_string, " Class: Document"); + break; + case FORMAT_CLASS_ARCHIVE: + out_string = g_string_append(out_string, " Class: Archive"); + break; + case FORMAT_CLASS_UNKNOWN: + out_string = g_string_append(out_string, " Class: Unknown"); + break; + default: + out_string = g_string_append(out_string, " Class: Unknown"); + break; + } + out_string = g_string_append(out_string, "\n"); + work = work->next; + } + + g_application_command_line_print(app_command_line, "%s\n", out_string->str); + + g_string_free(out_string, TRUE); + filelist_free(list); + file_data_unref(dir_fd); +} + +void gq_get_filelist(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ + gchar *text; + g_variant_dict_lookup(command_line_options_dict, "get-filelist", "&s", &text); + + get_filelist(app_command_line, text, FALSE); +} + + +void gq_get_filelist_recurse(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ + gchar *text; + g_variant_dict_lookup(command_line_options_dict, "get-filelist-recurse", "&s", &text); + + get_filelist(app_command_line, text, TRUE); +} + +void gq_get_rectangle(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *, GList *) +{ + if (!options->draw_rectangle) return; + if (!layout_valid(&lw_id)) return; + + auto *pr = PIXBUF_RENDERER(lw_id->image->pr); + if (!pr) return; + + gint x1; + gint y1; + gint x2; + gint y2; + + image_get_rectangle(x1, y1, x2, y2); + + gchar *rectangle_info = g_strdup_printf(_("%dx%d+%d+%d"), + std::abs(x1 - x2), + std::abs(y1 - y2), + std::min(x1, x2), + std::min(y1, y2)); + + g_application_command_line_print(app_command_line, "%s\n", rectangle_info); + + g_free(rectangle_info); +} + +void gq_get_render_intent(GtkApplication *, GApplicationCommandLine *app_command_line,GVariantDict *, GList *) +{ + gchar *render_intent; + + switch (options->color_profile.render_intent) + { + case 0: + render_intent = g_strdup("Perceptual"); + break; + case 1: + render_intent = g_strdup("Relative Colorimetric"); + break; + case 2: + render_intent = g_strdup("Saturation"); + break; + case 3: + render_intent = g_strdup("Absolute Colorimetric"); + break; + default: + render_intent = g_strdup("none"); + break; + } + + g_application_command_line_print(app_command_line, "%s\n", render_intent); + + g_free(render_intent); +} + +void gq_get_selection(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *, GList *) +{ + if (!layout_valid(&lw_id)) return; + + GList *selected = layout_selection_list(lw_id); // Keep copy to free. + GString *out_string = g_string_new(nullptr); + + GList *work = selected; + while (work) + { + auto fd = static_cast(work->data); + g_assert(fd->magick == FD_MAGICK); + + g_string_append_printf(out_string, "%s %s\n", + fd->path, + format_class_list[filter_file_get_class(fd->path)]); + + work = work->next; + } + + g_application_command_line_print(app_command_line, "%s\n", out_string->str); + + filelist_free(selected); + g_string_free(out_string, TRUE); +} + + +void gq_get_sidecars(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ + gchar *text; + g_variant_dict_lookup(command_line_options_dict, "file", "&s", &text); + gchar *filename = expand_tilde(text); + FileData *fd = file_data_new_group(filename); + + GList *work; + if (fd->parent) + { + fd = fd->parent; + } + + g_application_command_line_print(app_command_line, "%s\n", fd->path); + + work = fd->sidecar_files; + + while (work) + { + fd = static_cast(work->data); + work = work->next; + g_application_command_line_print(app_command_line, "%s\n", fd->path); + } + g_free(filename); +} + +void gq_grep(GtkApplication *, GApplicationCommandLine *, GVariantDict *command_line_options_dict, GList *) +{ + gchar *text; + g_variant_dict_lookup(command_line_options_dict, "file", "&s", &text); + + set_regexp(g_strdup(text)); +} + +void gq_id(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ + gchar *text; + g_variant_dict_lookup(command_line_options_dict, "id", "s", &text); + + lw_id = layout_find_by_layout_id(text); + if (!lw_id) + { + g_application_command_line_print(app_command_line, "Layout window ID " BOLD_ON "%s" BOLD_OFF " does not exist\n",text); + } +} + +void gq_last(GtkApplication *, GApplicationCommandLine *, GVariantDict *, GList *) +{ + layout_image_last(lw_id); +} + +void gq_log_file(GtkApplication *, GApplicationCommandLine *, GVariantDict *command_line_options_dict, GList *) +{ + gchar *text; + g_variant_dict_lookup(command_line_options_dict, "log-file", "s", &text); + + gchar *pathl; + gchar *path = g_strdup(text); + + pathl = path_from_utf8(path); + command_line->ssi = secure_open(pathl); +} + +void gq_lua(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ +#if HAVE_LUA + gchar *result = nullptr; + gchar **lua_command; + gchar *text; + + g_variant_dict_lookup(command_line_options_dict, "lua", "&s", &text); + + lua_command = g_strsplit(text, ",", 2); + + if (lua_command[0] && lua_command[1]) + { + FileData *fd = file_data_new_group(lua_command[0]); + result = g_strdup(lua_callvalue(fd, lua_command[1], nullptr)); + if (result) + { + g_application_command_line_print(app_command_line, "%s\n", result); + } + else + { + g_application_command_line_print(app_command_line, _("lua error: no data\n")); + } + } + else + { + g_application_command_line_print(app_command_line, _("lua error: no data\n")); + } + + g_strfreev(lua_command); + g_free(result); +#else + g_application_command_line_print(app_command_line, _("Lua is not available\n")); +#endif +} + +void gq_new_window(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *, GList *) +{ + LayoutWindow *lw = nullptr; + + if (!layout_valid(&lw)) return; + + lw_id = layout_new_from_default(); + + layout_set_path(lw_id, g_application_command_line_get_cwd(app_command_line)); +} + +void gq_next(GtkApplication *, GApplicationCommandLine *, GVariantDict *, GList *) +{ + layout_image_next(lw_id); +} + +void gq_pixel_info(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *, GList *) +{ + gchar *pixel_info; + gint x_pixel; + gint y_pixel; + gint width; + gint height; + gint r_mouse; + gint g_mouse; + gint b_mouse; + gint a_mouse; + PixbufRenderer *pr; + + if (!layout_valid(&lw_id)) return; + + pr = PIXBUF_RENDERER(lw_id->image->pr); + + if (pr) + { + pixbuf_renderer_get_image_size(pr, &width, &height); + if (width < 1 || height < 1) return; + + pixbuf_renderer_get_mouse_position(pr, &x_pixel, &y_pixel); + + if (x_pixel >= 0 && y_pixel >= 0) + { + pixbuf_renderer_get_pixel_colors(pr, x_pixel, y_pixel, &r_mouse, &g_mouse, &b_mouse, &a_mouse); + + if (gdk_pixbuf_get_has_alpha(pr->pixbuf)) + { + pixel_info = g_strdup_printf(_("[%d,%d]: RGBA(%3d,%3d,%3d,%3d)"), + x_pixel, y_pixel, + r_mouse, g_mouse, b_mouse, a_mouse); + } + else + { + pixel_info = g_strdup_printf(_("[%d,%d]: RGB(%3d,%3d,%3d)"), + x_pixel, y_pixel, + r_mouse, g_mouse, b_mouse); + } + + g_application_command_line_print(app_command_line, "%s\n", pixel_info); + + g_free(pixel_info); + } + else + { + return; + } + } + else + { + return; + } +} + +void gq_print0(GtkApplication *, GApplicationCommandLine *, GVariantDict *, GList *) +{ + print0 = TRUE; + +} + +gboolean gq_quit_idle_cb(gpointer) +{ + exit_program(); + + return G_SOURCE_REMOVE; +} + +void gq_quit(GtkApplication *app, GApplicationCommandLine *, GVariantDict *, GList *) +{ + /* Schedule exit when idle. If done directly this error is generated: + * GDBus.Error:org.freedesktop.DBus.Error.NoReply: Message recipient disconnected from message bus without replying + * This gives the application enough time to finish any pending D-Bus communication. + */ + g_idle_add(gq_quit_idle_cb, app); +} + +void gq_raise(GtkApplication *, GApplicationCommandLine *,GVariantDict *, GList *) +{ + gtk_window_present(GTK_WINDOW(lw_id->window)); +} + +void gq_selection_add(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ + gchar *text; + g_variant_dict_lookup(command_line_options_dict, "selection-add", "&s", &text); + + FileData *fd_to_select = nullptr; + if (strcmp(text, "") == 0) + { + // No file specified, use current fd. + fd_to_select = layout_image_get_fd(lw_id); + } + else + { + // Search through the current file list for a file matching the specified path. + // "Match" is either a basename match or a file path match. + gchar *path = expand_tilde(text); + gchar *filename = g_path_get_basename(path); + gchar *slash_plus_filename = g_strdup_printf("%s%s", G_DIR_SEPARATOR_S, filename); + + GList *file_list = layout_list(lw_id); + for (GList *work = file_list; work && !fd_to_select; work = work->next) + { + auto fd = static_cast(work->data); + if (!strcmp(path, fd->path) || g_str_has_suffix(fd->path, slash_plus_filename)) + { + fd_to_select = file_data_ref(fd); + continue; // will exit loop. + } + + for (GList *sidecar = fd->sidecar_files; sidecar && !fd_to_select; sidecar = sidecar->next) + { + auto side_fd = static_cast(sidecar->data); + if (!strcmp(path, side_fd->path) + || g_str_has_suffix(side_fd->path, slash_plus_filename)) + { + fd_to_select = file_data_ref(side_fd); + continue; // will exit both nested loops. + } + } + } + + if (!fd_to_select) + { + g_application_command_line_print(app_command_line, "File " BOLD_ON "%s" BOLD_OFF " is not in the current folder " BOLD_ON "%s" BOLD_OFF "%c", filename, g_application_command_line_get_cwd(app_command_line), print0 ? 0 : '\n'); + } + + filelist_free(file_list); + g_free(slash_plus_filename); + g_free(filename); + g_free(path); + } + + if (fd_to_select) + { + GList *to_select = g_list_append(nullptr, fd_to_select); + // Using the "_list" variant doesn't clear the existing selection. + layout_select_list(lw_id, to_select); + filelist_free(to_select); + } +} + +void gq_selection_clear(GtkApplication *, GApplicationCommandLine *,GVariantDict *, GList *) +{ + layout_select_none(lw_id); // Checks lw_id validity internally. +} + +void gq_selection_remove(GtkApplication *, GApplicationCommandLine *app_command_line,GVariantDict *command_line_options_dict, GList *) +{ + gchar *text = nullptr; + g_variant_dict_lookup(command_line_options_dict, "selectin-remove", "s", &text); + + GList *selected = layout_selection_list(lw_id); // Keep copy to free. + if (!selected) + { + g_application_command_line_print(app_command_line, _("remote sent --selection-remove with empty selection.\n")); + + return; + } + + FileData *fd_to_deselect = nullptr; + gchar *path = nullptr; + gchar *filename = nullptr; + gchar *slash_plus_filename = nullptr; + + if (!text || strcmp(text, "") == 0) + { + // No file specified, use current fd. + fd_to_deselect = layout_image_get_fd(lw_id); + if (!fd_to_deselect) + { + filelist_free(selected); + g_application_command_line_print(app_command_line, _("remote sent \"--selection-remove:\" with no current image\n")); + return; + } + } + else + { + // Search through the selection list for a file matching the specified path. + // "Match" is either a basename match or a file path match. + path = expand_tilde(text); + filename = g_path_get_basename(path); + slash_plus_filename = g_strdup_printf("%s%s", G_DIR_SEPARATOR_S, filename); + } + + GList *prior_link = nullptr; // Stash base for link removal to avoid a second traversal. + GList *link_to_remove = nullptr; + for (GList *work = selected; work; prior_link = work, work = work->next) + { + auto fd = static_cast(work->data); + if (fd_to_deselect) + { + if (fd == fd_to_deselect) + { + link_to_remove = work; + break; + } + } + else + { + // path, filename, and slash_plus_filename should be defined. + + if (!strcmp(path, fd->path) || g_str_has_suffix(fd->path, slash_plus_filename)) + { + link_to_remove = work; + break; + } + } + } + + if (!link_to_remove) + { + if (fd_to_deselect) + { + g_application_command_line_print(app_command_line, _("remote sent \"--selection-remove=\" but current image is not selected\n")); + } + else + { + g_application_command_line_print(app_command_line, "File " BOLD_ON "%s" BOLD_OFF " is not selected\n", filename); + } + } + else + { + if (link_to_remove == selected) + { + // Remove first link. + selected = g_list_remove_link(selected, link_to_remove); + filelist_free(link_to_remove); + link_to_remove = nullptr; + } + else + { + // Remove a subsequent link. + prior_link = g_list_remove_link(prior_link, link_to_remove); + filelist_free(link_to_remove); + link_to_remove = nullptr; + } + + // Re-select all but the deselected item. + layout_select_none(lw_id); + layout_select_list(lw_id, selected); + } + + filelist_free(selected); + file_data_unref(fd_to_deselect); + g_free(slash_plus_filename); + g_free(filename); + g_free(path); +} + +void gq_show_log_window(GtkApplication *, GApplicationCommandLine *,GVariantDict *, GList *) +{ + log_window_new(lw_id); +} + +void gq_slideshow(GtkApplication *, GApplicationCommandLine *,GVariantDict *, GList *) +{ + layout_image_slideshow_toggle(lw_id); +} + +void gq_slideshow_recurse(GtkApplication *, GApplicationCommandLine *,GVariantDict *command_line_options_dict, GList *) +{ + GList *list; + gchar *tilde_filename; + gchar *text; + g_variant_dict_lookup(command_line_options_dict, "slideshow-recurse", "&s", &text); + + tilde_filename = expand_tilde(text); + + FileData *dir_fd = file_data_new_dir(tilde_filename); + g_free(tilde_filename); + + layout_valid(&lw_id); + list = filelist_recursive_full(dir_fd, lw_id->options.file_view_list_sort.method, lw_id->options.file_view_list_sort.ascend, lw_id->options.file_view_list_sort.case_sensitive); + file_data_unref(dir_fd); + if (!list) return; + + layout_image_slideshow_stop(lw_id); + layout_image_slideshow_start_from_list(lw_id, list); +} + +void gq_tell(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *, GList *) +{ + gchar *out_string; + + layout_valid(&lw_id); + + const gchar *filename = image_get_path(lw_id->image); + if (filename) + { + if (lw_id->image->collection && lw_id->image->collection->name) + { + g_autofree gchar *collection_name = remove_extension_from_path(lw_id->image->collection->name); + out_string = g_strconcat(filename, " Collection: ", collection_name, NULL); + } + else + { + out_string = g_strconcat(filename, NULL); + } + } + else + { + out_string = g_strconcat(lw_id->dir_fd->path, G_DIR_SEPARATOR_S, NULL); + } + + g_application_command_line_print(app_command_line, "%s%c", out_string, print0 ? 0 : '\n'); + g_free(out_string); +} + +void gq_tools(GtkApplication *, GApplicationCommandLine *, GVariantDict *, GList *) +{ + gboolean popped; + gboolean hidden; + + layout_tools_float_get(lw_id, &popped, &hidden) && hidden; + layout_tools_float_set(lw_id, popped, !hidden); +} + +void gq_version(GtkApplication *, GApplicationCommandLine *app_command_line,GVariantDict *, GList *) +{ + g_application_command_line_print(app_command_line, "%s %s GTK%d\n", GQ_APPNAME, VERSION, gtk_major_version); +} + +void gq_get_window_list(GtkApplication *, GApplicationCommandLine *app_command_line,GVariantDict *, GList *) +{ + GString *window_list; + + window_list = layout_get_window_list(); + + g_application_command_line_print(app_command_line, "%s\n", window_list->str); + + g_string_free(window_list, TRUE); +} + +void gq_view(GtkApplication *, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ + gchar *filename; + gchar *tilde_filename; + gchar *text; + + g_variant_dict_lookup(command_line_options_dict, "view", "&s", &text); + tilde_filename = expand_tilde(text); + + filename = set_cwd(tilde_filename, app_command_line); + + view_window_new(file_data_new_group(filename)); + g_free(filename); + g_free(tilde_filename); +} + +/** + * @brief Parse all non-option command line parameters + * @param app_command_line + * @returns List of files on command line (*gchar) + * + */ +GList *directories_collections_files(GtkApplication *app, GApplicationCommandLine *app_command_line) +{ + GList *file_list = nullptr; + const gchar *current_arg; + gboolean remote_instance; + gchar **argv=nullptr; + gchar *download_web_tmp_file; + gchar *real_path; + gint argc; + + remote_instance = g_application_command_line_get_is_remote(app_command_line); + + argv = g_application_command_line_get_arguments(app_command_line, &argc); + + for (gint i = 1; i < argc; i++) + { + current_arg = argv[i]; + real_path = realpath(current_arg, nullptr); + + if (isdir(real_path)) + { + layout_set_path(lw_id, real_path); + } + else if (is_collection(current_arg)) + { + const gchar *path = collection_path(current_arg); + collection_window_new(path); + } + else if (isfile(real_path)) + { + file_list = g_list_prepend(file_list, real_path); + } + else + { + download_web_tmp_file = download_web_file(current_arg, FALSE, nullptr); + + if (download_web_tmp_file) + { + file_list = g_list_prepend(file_list, download_web_tmp_file); + } + else + { + g_application_command_line_print(app_command_line, "%s%s%s%s%s", "Unknown parameter: ", BOLD_ON, current_arg, BOLD_OFF, "\n"); + if (!remote_instance) + { + g_application_quit(G_APPLICATION(app)); + exit(EXIT_FAILURE); + } + } + } + } + + return g_list_reverse(file_list); +} + +/** + * @brief Process command line file or dir parameters + * @param file_list (*gchar) + * + */ +void process_files(GList *file_list) +{ + if (file_list) + { + GList *work; + gboolean multiple_dirs = FALSE; + work = file_list; + + gchar *basepath; + basepath = g_path_get_dirname(static_cast(work->data)); + + /* If command line arguments contain multiple dirs, a new + * collection will be created to hold them + */ + while (work) + { + gchar *newpath; + newpath = g_path_get_dirname(static_cast(work->data)); + if (g_strcmp0(newpath, basepath) != 0) + { + multiple_dirs = TRUE; + break; + } + g_free(newpath); + work = work->next; + } + + if (multiple_dirs) + { + CollectWindow *cw; + + cw = collection_window_new(nullptr); + CollectionData *cd = nullptr; + cd = cw->cd; + g_free(cd->path); + cd->path = nullptr; + + work = file_list; + while (work) + { + collection_add(cd, file_data_new_no_grouping((gchar *)(work->data)), FALSE); + work = work->next; + } + } + else + { + GList *work; + GList *selected; + FileData *fd; + layout_valid(&lw_id); + + selected = nullptr; + work = file_list; + layout_set_path(lw_id, g_path_get_dirname(static_cast(work->data))); + while (work) + { + fd = file_data_new_simple(static_cast(work->data)); + selected = g_list_append(selected, fd); + layout_list_sync_fd(lw_id, fd); + file_data_unref(fd); + work = work->next; + } + layout_select_list(lw_id, selected); + } + } +} + +/* print0 and id are first so that they can affect other command line entries */ +CommandLineOptionEntry command_line_options[] = +{ + { "print0", gq_print0, PRIMARY_REMOTE, GUI }, + { "id", gq_id, REMOTE , N_A }, + { "action", gq_action, PRIMARY_REMOTE, GUI }, + { "action-list", gq_action_list, PRIMARY_REMOTE, TEXT }, + { "back", gq_back, PRIMARY_REMOTE, GUI }, + { "cache-metadata", gq_cache_metadata, PRIMARY_REMOTE, GUI }, + { "cache-render", gq_cache_render, PRIMARY_REMOTE, GUI }, + { "cache-render-recurse", gq_cache_render_recurse, PRIMARY_REMOTE, GUI }, + { "cache-render-shared", gq_cache_render_shared, PRIMARY_REMOTE, GUI }, + { "cache-render-shared-recurse", gq_cache_render_shared_recurse, PRIMARY_REMOTE, GUI }, + { "cache-shared", gq_cache_shared, PRIMARY_REMOTE, GUI }, + { "cache-thumbs", gq_cache_thumbs, PRIMARY_REMOTE, GUI }, + { "close-window", gq_close_window, PRIMARY_REMOTE, GUI }, + { "config-load", gq_config_load, PRIMARY_REMOTE, GUI }, +#ifdef DEBUG + { "debug", gq_debug, PRIMARY_REMOTE, GUI }, +#endif + { "delay", gq_delay, PRIMARY_REMOTE, GUI }, + { "file", gq_file, PRIMARY_REMOTE, GUI }, + { "File", gq_File, PRIMARY_REMOTE, GUI }, + { "first", gq_first, PRIMARY_REMOTE, GUI }, + { "fullscreen", gq_fullscreen, PRIMARY_REMOTE, GUI }, + { "geometry", gq_geometry, PRIMARY_REMOTE, GUI }, + { "get-collection", gq_get_collection, PRIMARY_REMOTE, TEXT }, + { "get-collection-list", gq_get_collection_list, PRIMARY_REMOTE, TEXT }, + { "get-destination", gq_get_destination, PRIMARY_REMOTE, GUI }, + { "get-file-info", gq_get_file_info, REMOTE , N_A }, + { "get-filelist", gq_get_filelist, PRIMARY_REMOTE, GUI }, + { "get-filelist-recurse", gq_get_filelist_recurse, PRIMARY_REMOTE, GUI }, + { "get-rectangle", gq_get_rectangle, REMOTE , N_A }, + { "get-render-intent", gq_get_render_intent, REMOTE , N_A }, + { "get-selection", gq_get_selection, REMOTE , N_A }, + { "get-sidecars", gq_get_sidecars, REMOTE , N_A }, + { "get-window-list", gq_get_window_list, REMOTE , N_A }, +#ifdef DEBUG + { "grep", gq_grep, PRIMARY_REMOTE, GUI }, +#endif + { "last", gq_last, PRIMARY_REMOTE, GUI }, + { "log-file", gq_log_file, PRIMARY_REMOTE, GUI }, + { "lua", gq_lua, REMOTE , N_A }, + { "new-window", gq_new_window, PRIMARY_REMOTE, GUI }, + { "next", gq_next, PRIMARY_REMOTE, GUI }, + { "pixel-info", gq_pixel_info, REMOTE , N_A }, + { "quit", gq_quit, PRIMARY_REMOTE, GUI }, + { "raise", gq_raise, PRIMARY_REMOTE, GUI }, + { "selection-add", gq_selection_add, REMOTE , N_A }, + { "selection-clear", gq_selection_clear, REMOTE , N_A }, + { "selection-remove", gq_selection_remove, REMOTE , N_A }, + { "show-log-window", gq_show_log_window, PRIMARY_REMOTE, GUI }, + { "slideshow-recurse", gq_slideshow_recurse, PRIMARY_REMOTE, GUI }, + { "slideshow", gq_slideshow, PRIMARY_REMOTE, GUI }, + { "tell", gq_tell, REMOTE , N_A }, + { "tools", gq_tools, PRIMARY_REMOTE, GUI }, + { "version", gq_version, PRIMARY_REMOTE, TEXT }, + { "view", gq_view, PRIMARY_REMOTE, GUI }, + { nullptr, nullptr, NA, N_A } +}; + +/* + * Cache Maintenance + */ + +void gq_cm_quit(GtkApplication *app, GApplicationCommandLine *, GVariantDict *, GList *) +{ + g_application_withdraw_notification(G_APPLICATION(app), "cache_maintenance"); + + g_application_quit(G_APPLICATION(app)); +} + +void gq_cm_dir(GtkApplication *app, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ + gboolean remote_instance; + gchar *buf_config_file; + gchar *folder_path = nullptr; + gchar *path; + gchar *rc_path; + gint diff_count; + gsize i = 0; + gsize size; + + remote_instance = g_application_command_line_get_is_remote(app_command_line); + + if (remote_instance) + { + g_application_command_line_print(app_command_line, _("Cache Maintenance is already running\n")); + + return; + } + + g_variant_dict_lookup(command_line_options_dict, "cache-maintenance", "&s", &path); + + folder_path = expand_tilde(path); + + if (isdir(folder_path)) + { + rc_path = g_build_filename(get_rc_dir(), RC_FILE_NAME, nullptr); + + if (isfile(rc_path)) + { + if (g_file_get_contents(rc_path, &buf_config_file, &size, nullptr)) + { + while (i < size) + { + diff_count = strncmp("", &buf_config_file[i], 9); + if (diff_count == 0) + { + break; + } + i++; + } + /* Load only the section */ + load_config_from_buf(buf_config_file, i + 9, FALSE); + + if (options->thumbnails.enable_caching) + { + cache_maintenance(app, folder_path); + } + else + { + g_autofree gchar *notification_message = g_strdup(_("Caching not enabled")); + cache_maintenance_notification(app, notification_message, FALSE); + g_application_command_line_print(app_command_line, _("Caching not enabled\n")); + + exit(EXIT_FAILURE); + } + g_free(buf_config_file); + } + else + { + g_autofree gchar *notification_message = g_strconcat(_("Cannot load "), rc_path, nullptr); + cache_maintenance_notification(app, notification_message, FALSE); + g_application_command_line_print(app_command_line, "%s%s\n", _("Cannot load "), rc_path); + + exit(EXIT_FAILURE); + } + } + else + { + g_autofree gchar *notification_message = g_strconcat(_("Configuration file path "), rc_path, _(" is not a file"), NULL, nullptr); + cache_maintenance_notification(app, notification_message, FALSE); + g_application_command_line_print(app_command_line, "%s%s%s\n", _("Configuration file path "), rc_path, _(" is not a file")); + + exit(EXIT_FAILURE); + } + g_free(rc_path); + } + else + { + g_autofree gchar *notification_message = g_strconcat("\"", folder_path, "\"", _(" is not a folder"), nullptr); + cache_maintenance_notification(app, notification_message, FALSE); + g_application_command_line_print(app_command_line, "%s%s%s%s\n", "\"", folder_path, "\"", _(" is not a folder")); + + exit(EXIT_FAILURE); + } + + g_free(folder_path); +} + +CommandLineOptionEntry command_line_options_cache_maintenance[] = +{ + { "cache-maintenance", gq_cm_dir, REMOTE, N_A }, + { "quit", gq_cm_quit, REMOTE, N_A }, + { nullptr, nullptr, NA, N_A } +}; + +} // namespace + +gint process_command_line(GtkApplication *app, GApplicationCommandLine *app_command_line, gpointer ) +{ + GVariantDict* command_line_options_dict; + gint i; + GList *file_list = nullptr; + + /* These values are used for the rest of this command line */ + /* Make lw_id point to current window + */ + layout_valid(&lw_id); + print0 = FALSE; + + command_line_options_dict = g_application_command_line_get_options_dict(app_command_line); + + /* Parse other command line arguments, which should be files, URLs, directories + * or collections. + * Create the file list before the option processing in case an + * option needs to modify the file list. + */ + file_list = directories_collections_files(app, app_command_line); + + /* Execute the command line options */ + i = 0; + while (command_line_options[i].func != nullptr) + { + if (g_variant_dict_contains(command_line_options_dict, command_line_options[i].option_name)) + { + /* Exit if option is a remote only and the instance is primary */ + if (command_line_options[i].option_type == REMOTE) + { + if (! g_application_command_line_get_is_remote(app_command_line)) + { + g_application_command_line_print(app_command_line, "%s%s%s%s%s", _("Geeqie is not running: --"), BOLD_ON, command_line_options[i].option_name, BOLD_OFF, _(" is a Remote command\n")); + + g_application_quit(G_APPLICATION(app)); + exit(EXIT_FAILURE); + } + } + + /* Instance is either primary or remote */ + command_line_options[i].func(app, app_command_line, command_line_options_dict, file_list); + + /* If the instance is a primary and the option only outputs text, + * e.g. --version, kill the application after the text is output + */ + if (! g_application_command_line_get_is_remote(app_command_line)) + { + if (command_line_options[i].display_type == TEXT) + { + g_application_quit(G_APPLICATION(app)); + exit(EXIT_SUCCESS); + } + } + } + + i++; + } + + process_files(file_list); + + g_list_free_full(file_list, g_free); + + return EXIT_SUCCESS; +} + +gint process_command_line_cache_maintenance(GtkApplication *app, GApplicationCommandLine *app_command_line, gpointer) +{ + gboolean option_found = FALSE; + gint i; + GVariantDict* command_line_options_dict; + + command_line_options_dict = g_application_command_line_get_options_dict(app_command_line); + + /* Execute the command line options */ + i = 0; + while (command_line_options_cache_maintenance[i].func != nullptr) + { + if (g_variant_dict_contains(command_line_options_dict, command_line_options_cache_maintenance[i].option_name)) + { + command_line_options_cache_maintenance[i].func(app, app_command_line, command_line_options_dict, nullptr); + + option_found = TRUE; + } + + i++; + } + + if (!option_found) + { + g_application_command_line_print(app_command_line, "%s", _("No option specified\n")); + cache_maintenance_notification(app, _("No option specified"), FALSE); + + g_application_quit(G_APPLICATION(app)); + } + + return 0; +} + +/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/command-line-handling.h b/src/command-line-handling.h new file mode 100644 index 000000000..2ea19f91f --- /dev/null +++ b/src/command-line-handling.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 The Geeqie Team + * + * Author: Colin Clark + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef COMMAND_LINE_HANDLING_H +#define COMMAND_LINE_HANDLING_H + +#include + +gint process_command_line(GtkApplication *app, GApplicationCommandLine *app_command_line, gpointer data); +gint process_command_line_cache_maintenance(GtkApplication *app, GApplicationCommandLine *app_command_line, gpointer data); + +#endif +/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/layout.cc b/src/layout.cc index c03603b10..f7ad98d0a 100644 --- a/src/layout.cc +++ b/src/layout.cc @@ -2713,7 +2713,6 @@ LayoutWindow *layout_new_with_geometry(FileData *dir_fd, LayoutOptions *lop, } } - gtk_widget_show(lw->window); layout_tools_hide(lw, lw->options.tools_hidden); image_osd_set(lw->image, static_cast(lw->options.image_overlay.state)); @@ -2724,6 +2723,19 @@ LayoutWindow *layout_new_with_geometry(FileData *dir_fd, LayoutOptions *lop, layout_window_list = g_list_append(layout_window_list, lw); + /* Refer to the activate signal in main */ +#if HAVE_GTK4 + if (g_list_length(layout_window_list) == 1) + { + gtk_widget_hide(lw->window); + } +#else + if (g_list_length(layout_window_list) > 1) + { + gtk_widget_show(lw->window); + } +#endif + file_data_register_notify_func(layout_image_notify_cb, lw, NOTIFY_PRIORITY_LOW); DEBUG_1("%s layout_new: end", get_exec_time()); diff --git a/src/main.cc b/src/main.cc index 79a081c2b..b1748bfac 100644 --- a/src/main.cc +++ b/src/main.cc @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -59,7 +60,7 @@ #include "cache.h" #include "collect-io.h" #include "collect.h" -#include "compat.h" +#include "command-line-handling.h" #include "debug.h" #include "exif.h" #include "filedata.h" @@ -67,7 +68,6 @@ #include "glua.h" #include "histogram.h" #include "history-list.h" -#include "image.h" #include "img-view.h" #include "intl.h" #include "layout-image.h" @@ -75,11 +75,7 @@ #include "layout.h" #include "main-defines.h" #include "metadata.h" -#include "misc.h" -#include "options.h" #include "pixbuf-util.h" -#include "rcfile.h" -#include "remote.h" #include "secure-save.h" #include "third-party/whereami.h" #include "thumb.h" @@ -91,7 +87,6 @@ #endif gboolean thumb_format_changed = FALSE; -static RemoteConnection *remote_connection = nullptr; gchar *gq_prefix; gchar *gq_localedir; @@ -103,8 +98,106 @@ gchar *gq_executable_path; gchar *desktop_file_template; gchar *instance_identifier; +namespace +{ + +const gchar *option_context_description = _(" \ +All other command line parameters are used as plain files if they exist, or a URL or a folder.\n \ +The name of a collection, with or without either path or extension (.gqv) may be used.\n\n \ +If more than one folder is on the command line, only the last will be used.\n\n \ +If more than one file is on the command line:\n \ + If they are in the same folder, that folder will be opened and those files will be selected.\n \ + If they are not in the same folder, a new Collection containing those files will be opened.\n\n \ +To run Geeqie as a new instance, use:\n \ +GQ_NEW_INSTANCE=y[es] geeqie\n \ +Normally a single set of configuration files is used for all instances.\n \ +However, the environment variables XDG_CONFIG_HOME, XDG_CACHE_HOME, XDG_DATA_HOME\n \ +can be used to modify this behavior on an individual basis e.g.\n \ +XDG_CONFIG_HOME=/tmp/a XDG_CACHE_HOME=/tmp/b GQ_NON_UNIQUE= geeqie\n\n \ +To disable Clutter use:\n \ +GQ_DISABLE_CLUTTER=y[es] geeqie\n\n \ +To run or stop Geeqie in cache maintenance (non-GUI) mode use:\n \ +GQ_CACHE_MAINTENANCE=y[es] geeqie --help (This is disabled in this version and will be fixed in a future version.)\n\n \ +User manual: https://www.geeqie.org/help/GuideIndex.html\n \ + : https://www.geeqie.org/help-pdf/help.pdf"); + +const gchar *option_context_description_cache_maintenance = _(" \ +This will recursively remove orphaned thumbnails and .sim files, and create thumbnails\n \ +and similarity data for all images found under FOLDER.\n \ +It may also be called from cron or anacron thus enabling automatic updating of the cached\n \ +data for all your images.\n\n \ +User manual: https://www.geeqie.org/help/GuideIndex.html\n \ + : https://www.geeqie.org/help-pdf/help.pdf"); + +GOptionEntry command_line_options[] = +{ + { "action" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("execute keyboard action (See Help/Reference/Remote Keyboard Actions)") , "" }, + { "action-list" , 0, 0, G_OPTION_ARG_NONE , nullptr, _("list available keyboard actions (some are redundant)") , nullptr }, + { "back" , 'b', 0, G_OPTION_ARG_NONE , nullptr, _("previous image") , nullptr }, + { "cache-metadata" , 0, 0, G_OPTION_ARG_NONE , nullptr, _("clean the metadata cache") , nullptr }, + { "cache-render" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("render thumbnails") , "" }, + { "cache-render-recurse" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("render thumbnails recursively") , "" }, + { "cache-render-shared" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("render thumbnails (see Help)") , "" }, + { "cache-render-shared-recurse", 0, 0, G_OPTION_ARG_STRING, nullptr, _("render thumbnails recursively (see Help)") , "" }, + { "cache-shared" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("clear or clean shared thumbnail cache") , "clean|clear" }, + { "cache-thumbs" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("clear or clean thumbnail cache") , "clear|clean" }, + { "close-window" , 0, 0, G_OPTION_ARG_NONE , nullptr, _("close window") , nullptr }, + { "config-load" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("load configuration from FILE") , "" }, +#ifdef DEBUG + { "debug" , 0, 0, G_OPTION_ARG_INT , nullptr, _("turn on debug output") , "[level]" }, +#endif + { "delay" , 'd', 0, G_OPTION_ARG_STRING, nullptr, _("set slide show delay to Hrs Mins N.M seconds,") , "<[H:][M:][N][.M]>" }, + { "file" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("open FILE or URL bring Geeqie window to the top") , "|" }, + { "File" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("open FILE or URL do not bring Geeqie window to the top") , "|" }, + { "first" , 0, 0, G_OPTION_ARG_NONE , nullptr, _("first image") , nullptr }, + { "fullscreen" , 'f', 0, G_OPTION_ARG_NONE , nullptr, _("start / toggle in full screen mode") , nullptr }, + { "geometry" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("set main window location and geometry") , "x[++]" }, + { "get-collection" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("get collection content") , "" }, + { "get-collection-list" , 0, 0, G_OPTION_ARG_NONE , nullptr, _("get collection list") , nullptr }, + { "get-destination" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("get destination path of FILE (See Plugins Configuration)") , "" }, + { "get-file-info" , 0, 0, G_OPTION_ARG_NONE , nullptr, _("get file info") , nullptr}, + { "get-filelist" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("get list of files and class") , "[]" }, + { "get-filelist-recurse" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("get list of files and class recursive") , "[]" }, + { "get-rectangle" , 0, 0, G_OPTION_ARG_NONE , nullptr, _("get rectangle coordinates") , nullptr }, + { "get-render-intent" , 0, 0, G_OPTION_ARG_NONE , nullptr, _("get render intent") , nullptr }, + { "get-selection" , 0, 0, G_OPTION_ARG_NONE , nullptr, _("get list of selected files") , nullptr }, + { "get-sidecars" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("get list of sidecars of FILE") , "" }, + { "get-window-list" , 0, 0, G_OPTION_ARG_NONE , nullptr, _("get list of windows") , nullptr }, +#ifdef DEBUG + { "grep" , 'g', 0, G_OPTION_ARG_STRING, nullptr, _("filter debug output") , "" }, +#endif + { "id" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("window id for following commands") , "" }, + { "last" , 0, 0, G_OPTION_ARG_NONE , nullptr, _("last image") , nullptr }, + { "log-file" , 'o', 0, G_OPTION_ARG_STRING, nullptr, _("open collection window for command line") , "" }, + { "lua" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("run lua script on FILE") , "," }, + { "new-window" , 0, 0, G_OPTION_ARG_NONE , nullptr, _("new window") , nullptr }, + { "next" , 'n', 0, G_OPTION_ARG_NONE , nullptr, _("next image") , nullptr }, + { "pixel-info" , 0, 0, G_OPTION_ARG_NONE , nullptr, _("print pixel info of mouse pointer on current image") , nullptr }, + { "print0" , 0, 0, G_OPTION_ARG_NONE , nullptr, _("terminate returned data with null character instead of newline") , nullptr }, + { "quit" , 'q', 0, G_OPTION_ARG_NONE , nullptr, _("quit") , nullptr }, + { "raise" , 0, 0, G_OPTION_ARG_NONE , nullptr, _("bring the Geeqie window to the top") , nullptr }, + { "selection-add" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("adds the current file (or the specified file) to the current selection") ,"[]" }, + { "selection-clear" , 0, 0, G_OPTION_ARG_NONE , nullptr, _("clears the current selection") , nullptr }, + { "selection-remove" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("removes the current file (or the specified file) from the current selection") ,"[]" }, + { "show-log-window" , 'w', 0, G_OPTION_ARG_NONE , nullptr, _("show log window") , nullptr }, + { "slideshow-recurse" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("start recursive slide show in FOLDER") ,"" }, + { "slideshow" , 's', 0, G_OPTION_ARG_NONE , nullptr, _("toggle slide show") , nullptr }, + { "tell" , 0, 0, G_OPTION_ARG_NONE , nullptr, _("print filename [and Collection] of current image") , nullptr }, + { "tools" , 't', 0, G_OPTION_ARG_NONE , nullptr, _("toggle tools") , nullptr }, + { "version" , 'v', 0, G_OPTION_ARG_NONE , nullptr, _("print version info") , nullptr }, + { "view" , 0, 0, G_OPTION_ARG_STRING, nullptr, _("open FILE in new window") , "" }, + { nullptr , 0, 0, G_OPTION_ARG_NONE , nullptr, nullptr , nullptr }, +}; + +GOptionEntry command_line_options_cache_maintenance[] = +{ + { "cache-maintenance", 'c', 0, G_OPTION_ARG_STRING, nullptr, _("execute cache maintenance recursively on FOLDER"), "" }, + { "quit" , 'q', 0, G_OPTION_ARG_NONE , nullptr, _("stop cache maintenance") , nullptr }, + { nullptr , 0, 0, G_OPTION_ARG_NONE , nullptr, nullptr , nullptr }, +}; + #if defined(SA_SIGINFO) -static void sig_handler_cb(int signo, siginfo_t *info, void *) +void sig_handler_cb(int signo, siginfo_t *info, void *) { gchar hex_char[16]; const gchar *signal_name = nullptr; @@ -228,434 +321,7 @@ void sig_handler_cb(int) } #endif /* defined(SA_SIGINFO) */ -/* - *----------------------------------------------------------------------------- - * command line parser (private) hehe, who needs popt anyway? - *----------------------------------------------------------------------------- - */ - -static void parse_command_line_add_file(const gchar *file_path, gchar **path, gchar **file, - GList **list, GList **collection_list) -{ - gchar *path_parsed; - - path_parsed = g_strdup(file_path); - parse_out_relatives(path_parsed); - - if (file_extension_match(path_parsed, GQ_COLLECTION_EXT)) - { - *collection_list = g_list_append(*collection_list, path_parsed); - } - else - { - if (!*path) *path = remove_level_from_path(path_parsed); - if (!*file) *file = g_strdup(path_parsed); - *list = g_list_prepend(*list, path_parsed); - } -} - -static void parse_command_line_add_dir(const gchar *dir, gchar **, gchar **, GList **) -{ -#if 0 - /* This is broken because file filter is not initialized yet. - */ - GList *files; - gchar *path_parsed; - FileData *dir_fd; - - path_parsed = g_strdup(dir); - parse_out_relatives(path_parsed); - dir_fd = file_data_new_dir(path_parsed); - - - if (filelist_read(dir_fd, &files, NULL)) - { - GList *work; - - files = filelist_filter(files, FALSE); - files = filelist_sort_path(files); - - work = files; - while (work) - { - FileData *fd = static_cast(work->data); - if (!*path) *path = remove_level_from_path(fd->path); - if (!*file) *file = g_strdup(fd->path); - *list = g_list_prepend(*list, fd); - - work = work->next; - } - - g_list_free(files); - } - - g_free(path_parsed); - file_data_unref(dir_fd); -#else - DEBUG_1("multiple directories specified, ignoring: %s", dir); -#endif -} - -static void parse_command_line_process_dir(const gchar *dir, gchar **path, gchar **file, - GList **list, gchar **first_dir) -{ - - if (!*list && !*first_dir) - { - *first_dir = g_strdup(dir); - } - else - { - if (*first_dir) - { - parse_command_line_add_dir(*first_dir, path, file, list); - g_free(*first_dir); - *first_dir = nullptr; - } - parse_command_line_add_dir(dir, path, file, list); - } -} - -static void parse_command_line_process_file(const gchar *file_path, gchar **path, gchar **file, - GList **list, GList **collection_list, gchar **first_dir) -{ - - if (*first_dir) - { - parse_command_line_add_dir(*first_dir, path, file, list); - g_free(*first_dir); - *first_dir = nullptr; - } - parse_command_line_add_file(file_path, path, file, list, collection_list); -} - -static void show_invalid_parameters_warning_dialog(const gchar *command_line_errors) -{ - g_autoptr(GtkWidget) dialog_warning = gtk_message_dialog_new(nullptr, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, - "%s", _("Invalid parameter(s):")); - gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog_warning), "%s", command_line_errors); - gtk_window_set_title(GTK_WINDOW(dialog_warning), GQ_APPNAME); - gq_gtk_window_set_keep_above(GTK_WINDOW(dialog_warning), TRUE); - gtk_dialog_run(GTK_DIALOG(dialog_warning)); -} - -static void parse_command_line(gint argc, gchar *argv[]) -{ - GList *list = nullptr; - GList *remote_list = nullptr; - GList *remote_errors = nullptr; - gboolean remote_do = FALSE; - gchar *first_dir = nullptr; - gchar *app_lock; - gchar *pwd; - gchar *current_dir; - gchar *geometry = nullptr; - - command_line = g_new0(CommandLine, 1); - command_line->argc = argc; - command_line->argv = argv; - command_line->regexp = nullptr; - - if (argc > 1) - { - gchar *base_dir = get_current_dir(); - g_autoptr(GString) command_line_errors = g_string_new(nullptr); - for (gint i = 1; i < argc; i++) - { - gchar *cmd_line = path_to_utf8(argv[i]); - gchar *cmd_all = g_build_filename(base_dir, cmd_line, NULL); - - if (cmd_line[0] == G_DIR_SEPARATOR && isdir(cmd_line)) - { - parse_command_line_process_dir(cmd_line, &command_line->path, &command_line->file, &list, &first_dir); - } - else if (isdir(cmd_all)) - { - parse_command_line_process_dir(cmd_all, &command_line->path, &command_line->file, &list, &first_dir); - } - else if (cmd_line[0] == G_DIR_SEPARATOR && isfile(cmd_line)) - { - parse_command_line_process_file(cmd_line, &command_line->path, &command_line->file, - &list, &command_line->collection_list, &first_dir); - } - else if (isfile(cmd_all)) - { - parse_command_line_process_file(cmd_all, &command_line->path, &command_line->file, - &list, &command_line->collection_list, &first_dir); - } - else if (download_web_file(cmd_line, FALSE, nullptr)) - { - } - else if (is_collection(cmd_line)) - { - gchar *path = nullptr; - - path = collection_path(cmd_line); - parse_command_line_process_file(path, &command_line->path, &command_line->file, - &list, &command_line->collection_list, &first_dir); - g_free(path); - } - else if (strncmp(cmd_line, "--debug", 7) == 0 && (cmd_line[7] == '\0' || cmd_line[7] == '=')) - { - /* do nothing but do not produce warnings */ - } - else if (strncmp(cmd_line, "--disable-clutter", 17) == 0 && (cmd_line[17] == '\0')) - { - /* do nothing but do not produce warnings */ - } - else if (strcmp(cmd_line, "-T") == 0 || - strcmp(cmd_line, "--with-tools") == 0) - { - command_line->tools_show = TRUE; - - remote_list = g_list_append(remote_list, g_strdup("-T")); - } - else if (strcmp(cmd_line, "-t") == 0 || - strcmp(cmd_line, "--without-tools") == 0) - { - command_line->tools_hide = TRUE; - - remote_list = g_list_append(remote_list, g_strdup("-t")); - } - else if (strcmp(cmd_line, "-f") == 0 || - strcmp(cmd_line, "--fullscreen") == 0) - { - command_line->startup_full_screen = TRUE; - } - else if (strcmp(cmd_line, "-s") == 0 || - strcmp(cmd_line, "--slideshow") == 0) - { - command_line->startup_in_slideshow = TRUE; - } - else if (strcmp(cmd_line, "-l") == 0 || - strcmp(cmd_line, "--list") == 0) - { - command_line->startup_command_line_collection = TRUE; - } - else if (strncmp(cmd_line, "--geometry=", 11) == 0) - { - if (!command_line->geometry) command_line->geometry = g_strdup(cmd_line + 11); - } - else if (strcmp(cmd_line, "-r") == 0 || - strcmp(cmd_line, "--remote") == 0) - { - if (!remote_do) - { - remote_do = TRUE; - remote_list = remote_build_list(remote_list, argc - i, &argv[i], &remote_errors); - } - } - else if ((strcmp(cmd_line, "-w") == 0) || - strcmp(cmd_line, "--show-log-window") == 0) - { - command_line->log_window_show = TRUE; - } - else if (strncmp(cmd_line, "-o", 2) == 0) - { - command_line->log_file = g_strdup(cmd_line + 2); - } - else if (strncmp(cmd_line, "--log-file=", 11) == 0) - { - command_line->log_file = g_strdup(cmd_line + 11); - } - else if (strncmp(cmd_line, "-g", 2) == 0) - { - set_regexp(g_strdup(cmd_line + 2)); - } - else if (strncmp(cmd_line, "--grep=", 7) == 0) - { - set_regexp(g_strdup(cmd_line + 7)); - } - else if (strncmp(cmd_line, "-n", 2) == 0) - { - command_line->new_instance = TRUE; - } - else if (strncmp(cmd_line, "--new-instance", 14) == 0) - { - command_line->new_instance = TRUE; - } - else if (strcmp(cmd_line, "--blank") == 0) - { - command_line->startup_blank = TRUE; - } - else if (strcmp(cmd_line, "-v") == 0 || - strcmp(cmd_line, "--version") == 0) - { - printf_term(FALSE, "%s %s GTK%u\n", GQ_APPNAME, VERSION, gtk_major_version); - exit(EXIT_SUCCESS); - } - else if (strcmp(cmd_line, "-h") == 0 || - strcmp(cmd_line, "--help") == 0) - { - printf_term(FALSE, "%s %s\n", GQ_APPNAME, VERSION); - printf_term(FALSE, _("Usage: %s [options] [path]\n\n"), GQ_APPNAME_LC); - print_term(FALSE, _("Valid options:\n")); - print_term(FALSE, _(" --blank start with blank file list\n")); - print_term(FALSE, _(" --cache-maintenance= run cache maintenance in non-GUI mode\n")); - print_term(FALSE, _(" --disable-clutter disable use of Clutter library (i.e. GPU accel.)\n")); - print_term(FALSE, _(" -f, --fullscreen start in full screen mode\n")); - print_term(FALSE, _(" --geometry=WxH+XOFF+YOFF set main window location\n")); - print_term(FALSE, _(" -h, --help show this message\n")); - print_term(FALSE, _(" -l, --list [files] [collections] open collection window for command line\n")); - print_term(FALSE, _(" -n, --new-instance open a new instance of Geeqie *\n")); - print_term(FALSE, _(" -o, --log-file= save log data to file\n")); - print_term(FALSE, _(" -r, --remote send following commands to open window\n")); - print_term(FALSE, _(" -s, --slideshow start in slideshow mode\n")); - print_term(FALSE, _(" -T, --with-tools force show of tools\n")); - print_term(FALSE, _(" -t, --without-tools force hide of tools\n")); - print_term(FALSE, _(" -v, --version print version info\n")); - print_term(FALSE, _(" -w, --show-log-window show log window\n")); -#ifdef DEBUG - print_term(FALSE, _(" --debug=[level] turn on debug output\n")); - print_term(FALSE, _(" -g, --grep= filter debug output\n")); -#endif - - print_term(FALSE, "\n"); - print_term(FALSE, "* Normally a single set of configuration files is used for all instances.\nHowever, the environment variables XDG_CONFIG_HOME, XDG_CACHE_HOME, XDG_DATA_HOME\ncan be used to modify this behavior on an individual basis e.g.\n\nXDG_CONFIG_HOME=/tmp/a XDG_CACHE_HOME=/tmp/b geeqie\n\n"); - - remote_help(); - - exit(EXIT_SUCCESS); - } - else if (!remote_do) - { - g_string_append_printf(command_line_errors, is_remote_command(cmd_line) ? _("%s\n\nThis is a --remote command option\n") : _("%s\n\nThis option is unknown\n"), cmd_line); - } - - g_free(cmd_all); - g_free(cmd_line); - } - - if (command_line_errors->len > 0) - { - show_invalid_parameters_warning_dialog(command_line_errors->str); - - exit(EXIT_FAILURE); - } - - g_free(base_dir); - parse_out_relatives(command_line->path); - parse_out_relatives(command_line->file); - } - - list = g_list_reverse(list); - - if (!command_line->path && first_dir) - { - command_line->path = first_dir; - first_dir = nullptr; - - parse_out_relatives(command_line->path); - } - g_free(first_dir); - - if (!command_line->new_instance) - { - /* If Geeqie is already running, prevent a second instance - * from being started. Open a new window instead. - */ - app_lock = g_build_filename(get_rc_dir(), ".command", NULL); - if (remote_server_exists(app_lock) && !remote_do) - { - remote_do = TRUE; - if (command_line->geometry) - { - geometry = g_strdup_printf("--geometry=%s", command_line->geometry); - remote_list = g_list_prepend(remote_list, geometry); - } - remote_list = g_list_prepend(remote_list, g_strdup("--new-window")); - } - g_free(app_lock); - } - - if (remote_do) - { - if (remote_errors) - { - g_autoptr(GString) command_line_errors = g_string_new(nullptr); - - for (GList *work = remote_errors; work; work = work->next) - { - auto opt = static_cast(work->data); - - g_string_append_printf(command_line_errors, "%s\n", opt); - } - - show_invalid_parameters_warning_dialog(command_line_errors->str); - - exit(EXIT_FAILURE); - } - - /* prepend the current dir the remote command was made from, - * for use by any remote command that needs it - */ - current_dir = g_get_current_dir(); - pwd = g_strconcat("--PWD=", current_dir, NULL); - remote_list = g_list_prepend(remote_list, pwd); - - remote_control(argv[0], remote_list, command_line->path, list, command_line->collection_list); - /* There is no return to this point - */ - g_free(pwd); - g_free(current_dir); - } - g_free(geometry); - g_list_free(remote_list); - - if (list && list->next) - { - command_line->cmd_list = list; - } - else - { - g_list_free_full(list, g_free); - command_line->cmd_list = nullptr; - } - - if (command_line->startup_blank) - { - g_free(command_line->path); - command_line->path = nullptr; - g_free(command_line->file); - command_line->file = nullptr; - filelist_free(command_line->cmd_list); - command_line->cmd_list = nullptr; - g_list_free_full(command_line->collection_list, g_free); - command_line->collection_list = nullptr; - } -} - -static void parse_command_line_for_debug_option(gint argc, gchar *argv[]) -{ -#ifdef DEBUG - const gchar *debug_option = "--debug"; - const gint len = strlen(debug_option); - - for (gint i = 1; i < argc; i++) - { - // TODO(xsdg): Replace this with a regex match. Simpler and less error-prone. - const gchar *cmd_line = argv[i]; - if (strncmp(cmd_line, debug_option, len) == 0) - { - const gint cmd_line_len = strlen(cmd_line); - - /* we now increment the debug state for verbosity */ - if (cmd_line_len == len) - debug_level_add(1); - else if (cmd_line[len] == '=' && g_ascii_isdigit(cmd_line[len+1])) - { - gint n = atoi(cmd_line + len + 1); - if (n < 0) n = 1; - debug_level_add(n); - } - } - } - - DEBUG_1("debugging output enabled (level %d)", get_debug_level()); -#endif -} - -static gboolean search_command_line_for_option(const gint argc, const gchar* const argv[], const gchar* option_name) +gboolean search_command_line_for_option(const gint argc, const gchar* const argv[], const gchar* option_name) { const gint name_len = strlen(option_name); @@ -677,19 +343,25 @@ static gboolean search_command_line_for_option(const gint argc, const gchar* con return FALSE; } -static gboolean search_command_line_for_unit_test_option(gint argc, gchar *argv[]) +gboolean search_command_line_for_unit_test_option(gint argc, gchar *argv[]) { return search_command_line_for_option(argc, argv, "--run-unit-tests"); } -#if HAVE_CLUTTER -static gboolean search_command_line_for_clutter_option(gint argc, gchar *argv[]) +/** + * @brief Notification Quit button pressed + * @param action + * @param parameter + * @param app + * + * + */ +void quit_activated_cb(GSimpleAction *, GVariant *, gpointer app) { - return search_command_line_for_option(argc, argv, "--disable-clutter"); + g_application_quit(G_APPLICATION(app)); } -#endif -static gboolean parse_command_line_for_cache_maintenance_option(gint argc, gchar *argv[]) +gboolean parse_command_line_for_cache_maintenance_option(gint argc, gchar *argv[]) { const gchar *cache_maintenance_option = "--cache-maintenance="; const gint len = strlen(cache_maintenance_option); @@ -706,64 +378,6 @@ static gboolean parse_command_line_for_cache_maintenance_option(gint argc, gchar return FALSE; } -static void process_command_line_for_cache_maintenance_option(gint argc, gchar *argv[]) -{ - if (argc < 2) - { - print_term(TRUE, _("No path parameter given\n")); - exit(EXIT_FAILURE); - } - - const gchar *cache_maintenance_option = "--cache-maintenance="; - const gint len = strlen(cache_maintenance_option); - - g_autofree gchar *folder_path = expand_tilde(argv[1] + len); - if (!isdir(folder_path)) - { - print_term(TRUE, g_strconcat(argv[1] + len, _(" is not a folder\n"), NULL)); - exit(EXIT_FAILURE); - } - - g_autofree gchar *rc_path = g_build_filename(get_rc_dir(), RC_FILE_NAME, NULL); - if (!isfile(rc_path)) - { - print_term(TRUE, g_strconcat(_("Configuration file path "), rc_path, _(" is not a file\n"), NULL)); - exit(EXIT_FAILURE); - } - - g_autofree gchar *buf_config_file = nullptr; - gsize size; - if (!g_file_get_contents(rc_path, &buf_config_file, &size, nullptr)) - { - print_term(TRUE, g_strconcat(_("Cannot load "), rc_path, "\n", NULL)); - exit(EXIT_FAILURE); - } - - /* Load only the section */ - const gchar *global_tag_end = ""; - const gint global_tag_end_len = strlen(global_tag_end); - - gsize global_section_size = size; - for (gsize i = 0; i < size; i++) - { - if (strncmp(global_tag_end, buf_config_file + i, global_tag_end_len) == 0) - { - global_section_size = i + global_tag_end_len; - break; - } - } - - load_config_from_buf(buf_config_file, global_section_size, FALSE); - - if (!options->thumbnails.enable_caching) - { - print_term(TRUE, "Caching not enabled\n"); - exit(EXIT_FAILURE); - } - - cache_maintenance(folder_path); -} - /* *----------------------------------------------------------------------------- * startup, init, and exit @@ -773,39 +387,33 @@ static void process_command_line_for_cache_maintenance_option(gint argc, gchar * #define RC_HISTORY_NAME "history" #define RC_MARKS_NAME "marks" -static void setup_env_path() +void setup_env_path() { const gchar *old_path = g_getenv("PATH"); - g_autofree gchar *path = g_strconcat(gq_bindir, ":", old_path, NULL); - g_setenv("PATH", path, TRUE); -} - -static const gchar *get_history_path() -{ -#if USE_XDG - static gchar *history_path = g_build_filename(xdg_data_home_get(), GQ_APPNAME_LC, RC_HISTORY_NAME, NULL); -#else - static gchar *history_path = g_build_filename(get_rc_dir(), RC_HISTORY_NAME, NULL); -#endif - - return history_path; + gchar *path = g_strconcat(gq_bindir, ":", old_path, NULL); + g_setenv("PATH", path, TRUE); + g_free(path); } -static void keys_load() +void keys_load() { - const gchar *path = get_history_path(); + gchar *path; + path = g_build_filename(get_rc_dir(), RC_HISTORY_NAME, NULL); history_list_load(path); + g_free(path); } -static void keys_save() +void keys_save() { - const gchar *path = get_history_path(); + gchar *path; + path = g_build_filename(get_rc_dir(), RC_HISTORY_NAME, NULL); history_list_save(path); + g_free(path); } -static void marks_load() +void marks_load() { gchar *path; @@ -814,7 +422,7 @@ static void marks_load() g_free(path); } -static void marks_save(gboolean save) +void marks_save(gboolean save) { gchar *path; @@ -823,7 +431,7 @@ static void marks_save(gboolean save) g_free(path); } -static void mkdir_if_not_exists(const gchar *path) +void mkdir_if_not_exists(const gchar *path) { if (isdir(path)) return; @@ -835,12 +443,11 @@ static void mkdir_if_not_exists(const gchar *path) } } - /* We add to duplicate and modify gtk_accel_map_print() and gtk_accel_map_save() * to improve the reliability in special cases (especially when disk is full) * These functions are now using secure saving stuff. */ -static void gq_accel_map_print( +void gq_accel_map_print( gpointer data, const gchar *accel_path, guint accel_key, @@ -873,7 +480,7 @@ static void gq_accel_map_print( g_string_free(gstring, TRUE); } -static gboolean gq_accel_map_save(const gchar *path) +gboolean gq_accel_map_save(const gchar *path) { gchar *pathl; SecureSaveInfo *ssi; @@ -911,12 +518,12 @@ static gboolean gq_accel_map_save(const gchar *path) return TRUE; } -static gchar *accep_map_filename() +gchar *accep_map_filename() { return g_build_filename(get_rc_dir(), "accels", NULL); } -static void accel_map_save() +void accel_map_save() { gchar *path; @@ -925,7 +532,7 @@ static void accel_map_save() g_free(path); } -static void accel_map_load() +void accel_map_load() { gchar *path; gchar *pathl; @@ -937,7 +544,7 @@ static void accel_map_load() g_free(path); } -static void gtkrc_load() +void gtkrc_load() { gchar *path; gchar *pathl; @@ -952,7 +559,7 @@ static void gtkrc_load() g_free(path); } -static void exit_program_final() +void exit_program_final() { LayoutWindow *lw = nullptr; GList *list; @@ -963,8 +570,6 @@ static void exit_program_final() /* make sure that external editors are loaded, we would save incomplete configuration otherwise */ layout_editors_reload_finish(); - remote_close(remote_connection); - collect_manager_flush(); /* Save the named windows */ @@ -1014,25 +619,26 @@ static void exit_program_final() secure_close(command_line->ssi); - gtk_main_quit(); + exit(EXIT_SUCCESS); } -static GenericDialog *exit_dialog = nullptr; +GenericDialog *exit_dialog = nullptr; -static void exit_confirm_cancel_cb(GenericDialog *gd, gpointer) +void exit_confirm_cancel_cb(GenericDialog *gd, gpointer) { exit_dialog = nullptr; generic_dialog_close(gd); } -static void exit_confirm_exit_cb(GenericDialog *gd, gpointer) +void exit_confirm_exit_cb(GenericDialog *gd, gpointer) { exit_dialog = nullptr; generic_dialog_close(gd); + exit_program_final(); } -static gint exit_confirm_dlg() +gint exit_confirm_dlg() { GtkWidget *parent; LayoutWindow *lw; @@ -1086,26 +692,46 @@ static gint exit_confirm_dlg() return TRUE; } -static void exit_program_write_metadata_cb(gint success, const gchar *, gpointer) +void exit_program_write_metadata_cb(gint success, const gchar *, gpointer) { if (success) exit_program(); } -void exit_program() -{ - layout_image_full_screen_stop(nullptr); - - if (metadata_write_queue_confirm(FALSE, exit_program_write_metadata_cb, nullptr)) return; - - marks_save(options->marks_save); +/* This code attempts to handle situation when a file mmaped by image_loader + * or by exif loader is truncated by some other process. + * This code is incorrect according to POSIX, because: + * + * mmap is not async-signal-safe and thus may not be called from a signal handler + * + * mmap must be called with a valid file descriptor. POSIX requires that + * a fildes argument of -1 must cause mmap to return EBADF. + * + * See https://github.com/BestImageViewer/geeqie/issues/1052 for discussion of + * an alternative approach. + */ +/** @FIXME this probably needs some better ifdefs. Please report any compilation problems */ +/** @FIXME This section needs revising */ - if (exit_confirm_dlg()) return; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#if defined(SIGBUS) && defined(SA_SIGINFO) +void sigbus_handler_cb_unused(int, siginfo_t *info, void *) +{ + /* + * @FIXME Design and implement a POSIX-acceptable approach, + * after first documenting the sitations where SIGBUS occurs. + * See https://github.com/BestImageViewer/geeqie/issues/1052 for discussion + */ - exit_program_final(); + DEBUG_1("SIGBUS %p NOT HANDLED", info->si_addr); + exit(EXIT_FAILURE); } +#endif + +#pragma GCC diagnostic pop #if !HAVE_DEVELOPER -static void setup_sig_handler() +void setup_sig_handler() { struct sigaction sigsegv_action; sigfillset(&sigsegv_action.sa_mask); @@ -1121,7 +747,7 @@ static void setup_sig_handler() } #endif -static void set_theme_bg_color() +void set_theme_bg_color() { GdkRGBA bg_color; GdkRGBA theme_color; @@ -1152,7 +778,7 @@ static void set_theme_bg_color() view_window_colors_update(); } -static gboolean theme_change_cb(GObject *, GParamSpec *, gpointer) +gboolean theme_change_cb(GObject *, GParamSpec *, gpointer) { set_theme_bg_color(); @@ -1168,7 +794,7 @@ static gboolean theme_change_cb(GObject *, GParamSpec *, gpointer) * They are now variables, all defined relative to one level above the * directory that the executable is run from. */ -static void create_application_paths() +void create_application_paths() { gchar *dirname; gint length; @@ -1194,39 +820,36 @@ static void create_application_paths() g_free(path); } -gint main(gint argc, gchar *argv[]) +gint command_line_cb(GtkApplication *app, GApplicationCommandLine *app_command_line, gpointer) { - // We handle unit tests here because it takes the place of running the - // rest of the app. - if (search_command_line_for_unit_test_option(argc, argv)) - { -#if ENABLE_UNIT_TESTS - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -#else - fprintf(stderr, "Unit tests are not enabled in this build.\n"); - return 1; -#endif - } - - CollectionData *cd = nullptr; - CollectionData *first_collection = nullptr; - gboolean disable_clutter = FALSE; - gboolean single_dir = TRUE; - gchar *buf; - GdkScreen *screen; - GtkCssProvider *provider; - GtkSettings *default_settings; - LayoutWindow *lw; + gint ret; + + ret = process_command_line((app), app_command_line, nullptr); + + g_application_activate(G_APPLICATION(app)); + + return ret; +} - gdk_set_allowed_backends("x11,*"); +gint shutdown_cache_maintenance_cb(GtkApplication *, gpointer) +{ + exit(EXIT_SUCCESS); +} - gdk_threads_init(); - gdk_threads_enter(); +gint command_line_cache_maintenance_cb(GtkApplication *app, GApplicationCommandLine *app_command_line, gpointer) +{ + gint ret; + + ret = process_command_line_cache_maintenance(app, app_command_line, nullptr); + + return ret; +} +void startup_common(GtkApplication *, gpointer) +{ /* seg. fault handler */ #if HAVE_DEVELOPER - backward::SignalHandling sh{}; + backward::SignalHandling sh {}; #else setup_sig_handler(); #endif @@ -1254,6 +877,11 @@ gint main(gint argc, gchar *argv[]) /* setup random seed for random slideshow */ srand(time(nullptr)); +#if 0 + /* See later comment; this handler leads to UB. */ + setup_sigbus_handler(); +#endif + /* register global notify functions */ file_data_register_notify_func(cache_notify_cb, nullptr, NOTIFY_PRIORITY_HIGH); file_data_register_notify_func(thumb_notify_cb, nullptr, NOTIFY_PRIORITY_HIGH); @@ -1261,32 +889,10 @@ gint main(gint argc, gchar *argv[]) file_data_register_notify_func(collect_manager_notify_cb, nullptr, NOTIFY_PRIORITY_LOW); file_data_register_notify_func(metadata_notify_cb, nullptr, NOTIFY_PRIORITY_LOW); - gtkrc_load(); - parse_command_line_for_debug_option(argc, argv); - DEBUG_1("%s main: gtk_init", get_exec_time()); -#if HAVE_CLUTTER - if (search_command_line_for_clutter_option(argc, argv)) - { - disable_clutter = TRUE; - gtk_init(&argc, &argv); - } - else - { - if (gtk_clutter_init(&argc, &argv) != CLUTTER_INIT_SUCCESS) - { - log_printf("Can't initialize clutter-gtk.\nStart Geeqie with the option \"geeqie --disable-clutter\""); - runcmd("zenity --error --title=\"Geeqie\" --text \"Can't initialize clutter-gtk.\n\nStart Geeqie with the option:\n geeqie --disable-clutter\" --width=300"); - exit(EXIT_FAILURE); - } - } -#else - gtk_init(&argc, &argv); -#endif - if (gtk_major_version < GTK_MAJOR_VERSION || - (gtk_major_version == GTK_MAJOR_VERSION && gtk_minor_version < GTK_MINOR_VERSION) ) + (gtk_major_version == GTK_MAJOR_VERSION && gtk_minor_version < GTK_MINOR_VERSION) ) { log_printf("!!! This is a friendly warning.\n"); log_printf("!!! The version of GTK+ in use now is older than when %s was compiled.\n", GQ_APPNAME); @@ -1302,11 +908,6 @@ gint main(gint argc, gchar *argv[]) DEBUG_1("%s main: setting default options before commandline handling", get_exec_time()); options = init_options(nullptr); setup_default_options(options); - if (disable_clutter) - { - options->disable_gpu = TRUE; - } - /* Generate a unique identifier used by the open archive function */ instance_identifier = g_strdup_printf("%x", g_random_int()); @@ -1320,223 +921,243 @@ gint main(gint argc, gchar *argv[]) setup_env_path(); - screen = gdk_screen_get_default(); - provider = gtk_css_provider_new(); - gtk_css_provider_load_from_resource(provider, GQ_RESOURCE_PATH_UI "/custom.css"); - gtk_style_context_add_provider_for_screen(screen, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - - if (parse_command_line_for_cache_maintenance_option(argc, argv)) - { - process_command_line_for_cache_maintenance_option(argc, argv); - } - else - { - DEBUG_1("%s main: parse_command_line", get_exec_time()); - parse_command_line(argc, argv); - - keys_load(); - accel_map_load(); + keys_load(); + accel_map_load(); - /* restore session from the config file */ + command_line = g_new0(CommandLine, 1); + const gchar *gq_disable_clutter = g_getenv("GQ_DISABLE_CLUTTER"); - DEBUG_1("%s main: load_options", get_exec_time()); - if (!load_options(options)) - { - /* load_options calls these functions after it parses global options, we have to call it here if it fails */ - filter_add_defaults(); - filter_rebuild(); - } + if (gq_disable_clutter && (gq_disable_clutter[0] == 'y' || gq_disable_clutter[0] == 'Y')) + { + options->disable_gpu = TRUE; + } - #if HAVE_CLUTTER +#if HAVE_CLUTTER /** @FIXME For the background of this see: * https://github.com/BestImageViewer/geeqie/issues/397 * The feature CLUTTER_FEATURE_SWAP_EVENTS indictates if the * system is liable to exhibit this problem. * The user is provided with an override in Preferences/Behavior */ - if (!options->override_disable_gpu && !options->disable_gpu) + if (!options->override_disable_gpu && !options->disable_gpu) + { + DEBUG_1("CLUTTER_FEATURE_SWAP_EVENTS %d",clutter_feature_available(CLUTTER_FEATURE_SWAP_EVENTS)); + if (clutter_feature_available(CLUTTER_FEATURE_SWAP_EVENTS) != 0) { - DEBUG_1("CLUTTER_FEATURE_SWAP_EVENTS %d",clutter_feature_available(CLUTTER_FEATURE_SWAP_EVENTS)); - if (clutter_feature_available(CLUTTER_FEATURE_SWAP_EVENTS) != 0) - { - options->disable_gpu = TRUE; - } + options->disable_gpu = TRUE; } - #endif + } +#endif +} - /* handle missing config file and commandline additions*/ - if (!layout_window_list) - { - /* broken or no config file or no section */ - layout_new_from_default(); - } +void activate_cb(GtkApplication *, gpointer) +{ + LayoutWindow *lw = nullptr; - layout_editors_reload_start(); + layout_valid(&lw); + + /* If Geeqie is not running and a command line option like --version + * is executed, display of the Geeqie window has to be inhibited. + * + * The startup signal is issued before the command_line signal, therefore + * the window layout processing is done before the command line processing. + * + * Function layout_new_with_geometry() does not execute a gtk_window_show() + * if this is the first window - i.e. Geeqie is not yet fully running. + * + * The activate signal is issued in command_line_cb() after the + * command line signal has been processed. This function will + * issue the gtk_window_show() command. + * + * In the case of a text-output option that does not require the window, + * the shutdown happens in process_command_line(). + */ + if (lw->window) + { + gtk_widget_show(lw->window); + } +} - /* If no --list option, open a separate collection window for each - * .gqv file on the command line - */ - if (command_line->collection_list && !command_line->startup_command_line_collection) - { - GList *work; +void startup_cb(GtkApplication *app, gpointer) +{ + GtkSettings *default_settings; - work = command_line->collection_list; - while (work) - { - CollectWindow *cw; - const gchar *path; + startup_common(app, nullptr); - path = static_cast(work->data); - work = work->next; + /* restore session from the config file */ - cw = collection_window_new(path); - if (!first_collection && cw) first_collection = cw->cd; - } - } + if (!load_options(options)) + { + /* load_options calls these functions after it parses global options, we have to call it here if it fails */ + filter_add_defaults(); + filter_rebuild(); + } - if (command_line->log_file) - { - gchar *pathl; - gchar *path = g_strdup(command_line->log_file); + /* handle missing config file and commandline additions*/ + if (!layout_window_list) + { + /* broken or no config file or no section */ + layout_new_from_default(); + } - pathl = path_from_utf8(path); - command_line->ssi = secure_open(pathl); - } + layout_editors_reload_start(); - /* If there is a files list on the command line and no --list option, - * check if they are all in the same folder - */ - if (command_line->cmd_list && !(command_line->startup_command_line_collection)) - { - GList *work; - gchar *path = nullptr; + marks_load(); - work = command_line->cmd_list; + default_settings = gtk_settings_get_default(); - while (work && single_dir) - { - gchar *dirname; - - dirname = g_path_get_dirname(static_cast(work->data)); - if (!path) - { - path = g_strdup(dirname); - } - else - { - if (g_strcmp0(path, dirname) != 0) - { - single_dir = FALSE; - } - } - g_free(dirname); - work = work->next; - } - g_free(path); - } + g_signal_connect(default_settings, "notify::gtk-theme-name", G_CALLBACK(theme_change_cb), nullptr); + set_theme_bg_color(); - /* Files from multiple folders, or --list option given - * then open an unnamed collection and insert all files - */ - if ((command_line->cmd_list && !single_dir) || (command_line->startup_command_line_collection && command_line->cmd_list)) + /* Show a fade-out notification window if the server has a newer AppImage version */ + if (options->appimage_notifications) + { + if (g_getenv("APPDIR") && strstr(g_getenv("APPDIR"), "/tmp/.mount_Geeqie")) { - GList *work; - CollectWindow *cw; + appimage_notification(app); + } + else if (g_strstr_len(gq_executable_path, -1, "squashfs-root")) + { + /* Probably running an extracted AppImage */ + appimage_notification(app); + } + } - cw = collection_window_new(nullptr); - cd = cw->cd; + gtk_application_window_new(app); +} - collection_path_changed(cd); +void startup_cache_maintenance_cb(GtkApplication *app, gpointer) +{ + startup_common(app, nullptr); - work = command_line->cmd_list; - while (work) - { - FileData *fd; + g_application_hold(G_APPLICATION(app)); +} - fd = file_data_new_simple(static_cast(work->data)); - collection_add(cd, fd, FALSE); - file_data_unref(fd); - work = work->next; - } +} // namespace - work = command_line->collection_list; - while (work) - { - collection_load(cd, static_cast(work->data), COLLECTION_LOAD_APPEND); - work = work->next; - } +void exit_program() +{ + layout_image_full_screen_stop(nullptr); - if (cd->list) layout_image_set_collection(nullptr, cd, static_cast(cd->list->data)); + if (metadata_write_queue_confirm(FALSE, exit_program_write_metadata_cb, nullptr)) return; - /* mem leak, we never unref this collection when !startup_command_line_collection - * (the image view of the main window does not hold a ref to the collection) - * this is sort of unavoidable, for if it did hold a ref, next/back - * may not work as expected when closing collection windows. - * - * collection_unref(cd); - */ + marks_save(options->marks_save); - } - else if (first_collection) - { - layout_image_set_collection(nullptr, first_collection, - collection_get_first(first_collection)); - } + if (exit_confirm_dlg()) + { + return; + } - /* If the files on the command line are from one folder, select those files - * unless it is a command line collection - then leave focus on collection window - */ - lw = nullptr; - layout_valid(&lw); + exit_program_final(); +} + +gint main(gint argc, gchar *argv[]) +{ + gdk_set_allowed_backends("x11, *"); + + gint status; + gchar *version_string; + GtkApplication *app; + // We handle unit tests here because it takes the place of running the + // rest of the app. + if (search_command_line_for_unit_test_option(argc, argv)) + { +#if ENABLE_UNIT_TESTS + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +#else + fprintf(stderr, "Unit tests are not enabled in this build.\n"); + return 1; +#endif + } - if (single_dir && command_line->cmd_list && !command_line->startup_command_line_collection) +#if HAVE_CLUTTER + const gchar *gq_disable_clutter = g_getenv("GQ_DISABLE_CLUTTER"); + + if (!gq_disable_clutter || tolower(gq_disable_clutter[0]) != 'y') + { + if (clutter_init(nullptr, nullptr) != CLUTTER_INIT_SUCCESS) { - GList *work; - GList *selected; - FileData *fd; + fprintf(stderr, + _("Can't initialize clutter-gtk. \n \ + To start Geeqie use: \n \ + GQ_DISABLE_CLUTTER=y geeqie\n\n")); - selected = nullptr; - work = command_line->cmd_list; - while (work) - { - fd = file_data_new_simple(static_cast(work->data)); - selected = g_list_append(selected, fd); - file_data_unref(fd); - work = work->next; - } - layout_select_list(lw, selected); + return EXIT_FAILURE; } + } +#endif + const gchar *gq_cache_maintenance = g_getenv("GQ_CACHE_MAINTENANCE"); + if (gq_cache_maintenance && tolower(gq_cache_maintenance[0]) == 'y') + { + /* Disabled at the moment */ + log_printf("Command line cache maintenance is disabled in this version.\nThis will be fixed in a future version."); + return 1; - buf = g_build_filename(get_rc_dir(), ".command", NULL); - remote_connection = remote_server_init(buf, cd); - g_free(buf); + app = gtk_application_new("org.geeqie.cache-maintenance", static_cast( G_APPLICATION_HANDLES_COMMAND_LINE | G_APPLICATION_SEND_ENVIRONMENT)); + g_application_add_main_option_entries(G_APPLICATION(app), command_line_options_cache_maintenance); - marks_load(); + g_application_set_option_context_parameter_string (G_APPLICATION(app), \ +_("\n\nUsage for cache maintenance:\n \ +GQ_CACHE_MAINTENANCE= geeqie OPTION")); - default_settings = gtk_settings_get_default(); - g_signal_connect(default_settings, "notify::gtk-theme-name", G_CALLBACK(theme_change_cb), NULL); - set_theme_bg_color(); + version_string = g_strconcat( \ +_("Geeqie Cache Maintenance. \n \ +Version: Geeqie "), VERSION, nullptr); + + g_application_set_option_context_summary (G_APPLICATION(app), version_string); + g_free(version_string); + g_application_set_option_context_description (G_APPLICATION(app),option_context_description_cache_maintenance); + + g_signal_connect(app, "startup", G_CALLBACK(startup_cache_maintenance_cb), nullptr); + g_signal_connect(app, "command-line", G_CALLBACK(command_line_cache_maintenance_cb), nullptr); + g_signal_connect(app, "shutdown", G_CALLBACK(shutdown_cache_maintenance_cb), nullptr); + + /* The quit action is linked to the Quit button on the notifications */ + GSimpleAction *quit_action = g_simple_action_new("quit", nullptr); + g_signal_connect(quit_action, "activate", G_CALLBACK(quit_activated_cb), app); + g_action_map_add_action(G_ACTION_MAP(app), G_ACTION(quit_action)); + + status = g_application_run(G_APPLICATION(app), argc, argv); + + g_object_unref(app); + + return status; } - /* Show a fade-out notification window if the server has a newer AppImage version */ - if (options->appimage_notifications) + const gchar *gq_new_instance = g_getenv("GQ_NEW_INSTANCE"); + if (gq_new_instance && tolower(gq_new_instance[0]) == 'y') { - if (g_getenv("APPDIR") && strstr(g_getenv("APPDIR"), "/tmp/.mount_Geeqie")) - { - appimage_notification(); - } - else if (g_strstr_len(gq_executable_path, -1, "squashfs-root")) - { - /* Probably running an extracted AppImage */ - appimage_notification(); - } + app = gtk_application_new("org.geeqie.Geeqie", static_cast(G_APPLICATION_HANDLES_COMMAND_LINE | G_APPLICATION_NON_UNIQUE | G_APPLICATION_SEND_ENVIRONMENT)); } + else + { + app = gtk_application_new("org.geeqie.Geeqie", static_cast(G_APPLICATION_HANDLES_COMMAND_LINE | G_APPLICATION_SEND_ENVIRONMENT)) ; + } + + g_application_add_main_option_entries(G_APPLICATION(app), command_line_options); + + g_application_set_option_context_parameter_string (G_APPLICATION(app), "[path...]"); - DEBUG_1("%s main: gtk_main", get_exec_time()); - gtk_main(); + version_string = g_strconcat( \ +_("Geeqie is an image viewer.\n \ +Version: Geeqie "), VERSION, nullptr); - gdk_threads_leave(); - return 0; + g_application_set_option_context_summary (G_APPLICATION(app), version_string); + g_free(version_string); + + g_application_set_option_context_description (G_APPLICATION(app), option_context_description); + + g_signal_connect(app, "activate", G_CALLBACK(activate_cb), nullptr); + g_signal_connect(app, "command-line", G_CALLBACK(command_line_cb), nullptr); + g_signal_connect(app, "startup", G_CALLBACK(startup_cb), nullptr); + + status = g_application_run(G_APPLICATION(app), argc, argv); + + g_object_unref(app); + + return status; } + /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/meson.build b/src/meson.build index 011d7eaf3..f3bf38998 100644 --- a/src/meson.build +++ b/src/meson.build @@ -47,6 +47,8 @@ main_sources = files('advanced-exif.cc', 'collect-table.h', 'color-man.cc', 'color-man.h', +'command-line-handling.cc', +'command-line-handling.h', 'compat.cc', 'compat.h', 'debug.cc', @@ -138,8 +140,6 @@ main_sources = files('advanced-exif.cc', 'print.h', 'rcfile.cc', 'rcfile.h', -'remote.cc', -'remote.h', 'renderer-tiles.cc', 'renderer-tiles.h', 'search-and-run.cc', diff --git a/src/remote.cc b/src/remote.cc deleted file mode 100644 index 19982522d..000000000 --- a/src/remote.cc +++ /dev/null @@ -1,2063 +0,0 @@ -/* - * Copyright (C) 2004 John Ellis - * Copyright (C) 2008 - 2016 The Geeqie Team - * - * Author: John Ellis - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "remote.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include "cache-maint.h" -#include "collect-io.h" -#include "collect.h" -#include "compat.h" -#include "debug.h" -#include "exif.h" -#include "filedata.h" -#include "filefilter.h" -#include "glua.h" -#include "image.h" -#include "img-view.h" -#include "intl.h" -#include "layout-image.h" -#include "layout-util.h" -#include "layout.h" -#include "main-defines.h" -#include "main.h" -#include "misc.h" -#include "options.h" -#include "pixbuf-renderer.h" -#include "rcfile.h" -#include "slideshow.h" -#include "typedefs.h" -#include "ui-fileops.h" -#include "ui-misc.h" -#include "utilops.h" -#include "view-file.h" - -#ifndef UNIX_PATH_MAX -#define UNIX_PATH_MAX 108 -#endif - -namespace -{ - -constexpr guint SERVER_MAX_CLIENTS = 8; -constexpr int REMOTE_SERVER_BACKLOG = 4; - -} // namespace - -static RemoteConnection *remote_client_open(const gchar *path); -static gint remote_client_send(RemoteConnection *rc, const gchar *text); -static void gr_raise(const gchar *text, GIOChannel *channel, gpointer data); - -static LayoutWindow *lw_id = nullptr; /* points to the window set by the --id option */ - -struct RemoteClient { - gint fd; - guint channel_id; /* event source id */ - RemoteConnection *rc; -}; - -struct RemoteData { - CollectionData *command_collection; - GList *file_list; - gboolean single_dir; -}; - -/* To enable file names containing newlines to be processed correctly, - * the --print0 remote option sets returned data to be terminated with a null - * character rather a newline - */ -static gboolean print0 = FALSE; - -/* Remote commands from main.cc are prepended with the current dir the remote - * command was made from. Some remote commands require this. The - * value is stored here - */ -static gchar *pwd = nullptr; - -/** - * @brief Ensures file path is absolute. - * @param[in] filename Filepath, absolute or relative to calling directory - * @returns absolute path - * - * If first character of input filepath is not the directory - * separator, assume it as a relative path and prepend - * the directory the remote command was initiated from - * - * Return value must be freed with g_free() - */ -static gchar *set_pwd(gchar *filename) -{ - gchar *temp; - - if (strncmp(filename, G_DIR_SEPARATOR_S, 1) != 0) - { - temp = g_build_filename(pwd, filename, NULL); - } - else - { - temp = g_strdup(filename); - } - - return temp; -} - -static gboolean remote_server_client_cb(GIOChannel *source, GIOCondition condition, gpointer data) -{ - auto client = static_cast(data); - RemoteConnection *rc; - GIOStatus status = G_IO_STATUS_NORMAL; - - lw_id = nullptr; - rc = client->rc; - - if (condition & G_IO_IN) - { - gchar *buffer = nullptr; - GError *error = nullptr; - gsize termpos; - /** @FIXME it should be possible to terminate the command with a null character */ - g_io_channel_set_line_term(source, "", -1); - while ((status = g_io_channel_read_line(source, &buffer, nullptr, &termpos, &error)) == G_IO_STATUS_NORMAL) - { - if (buffer) - { - buffer[termpos] = '\0'; - - if (buffer[0] != '\0') - { - if (rc->read_func) rc->read_func(rc, buffer, source, rc->read_data); - g_io_channel_write_chars(source, "", -1, nullptr, nullptr); /* empty line finishes the command */ - g_io_channel_flush(source, nullptr); - } - g_free(buffer); - - buffer = nullptr; - } - } - - if (error) - { - log_printf("error reading socket: %s\n", error->message); - g_error_free(error); - } - } - - if (condition & G_IO_HUP || status == G_IO_STATUS_EOF || status == G_IO_STATUS_ERROR) - { - rc->clients = g_list_remove(rc->clients, client); - - DEBUG_1("HUP detected, closing client."); - DEBUG_1("client count %u", g_list_length(rc->clients)); - - g_source_remove(client->channel_id); - close(client->fd); - g_free(client); - } - - return TRUE; -} - -static void remote_server_client_add(RemoteConnection *rc, gint fd) -{ - RemoteClient *client; - GIOChannel *channel; - - if (g_list_length(rc->clients) > SERVER_MAX_CLIENTS) - { - log_printf("maximum remote clients of %d exceeded, closing connection\n", SERVER_MAX_CLIENTS); - close(fd); - return; - } - - client = g_new0(RemoteClient, 1); - client->rc = rc; - client->fd = fd; - - channel = g_io_channel_unix_new(fd); - client->channel_id = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, static_cast(G_IO_IN | G_IO_HUP), - remote_server_client_cb, client, nullptr); - g_io_channel_unref(channel); - - rc->clients = g_list_append(rc->clients, client); - DEBUG_1("client count %u", g_list_length(rc->clients)); -} - -static void remote_server_clients_close(RemoteConnection *rc) -{ - while (rc->clients) - { - auto client = static_cast(rc->clients->data); - - rc->clients = g_list_remove(rc->clients, client); - - g_source_remove(client->channel_id); - close(client->fd); - g_free(client); - } -} - -static gboolean remote_server_read_cb(GIOChannel *, GIOCondition, gpointer data) -{ - auto rc = static_cast(data); - gint fd; - guint alen; - - fd = accept(rc->fd, nullptr, &alen); - if (fd == -1) - { - log_printf("error accepting socket: %s\n", strerror(errno)); - return TRUE; - } - - remote_server_client_add(rc, fd); - - return TRUE; -} - -gboolean remote_server_exists(const gchar *path) -{ - RemoteConnection *rc; - - /* verify server up */ - rc = remote_client_open(path); - remote_close(rc); - - if (rc) return TRUE; - - /* unable to connect, remove socket file to free up address */ - unlink(path); - return FALSE; -} - -static RemoteConnection *remote_server_open(const gchar *path) -{ - RemoteConnection *rc; - struct sockaddr_un addr; - gint fd; - GIOChannel *channel; - - if (strlen(path) >= sizeof(addr.sun_path)) - { - log_printf("Address is too long: %s\n", path); - return nullptr; - } - - if (remote_server_exists(path)) - { - log_printf("Address already in use: %s\n", path); - return nullptr; - } - - fd = socket(PF_UNIX, SOCK_STREAM, 0); - if (fd == -1) return nullptr; - - addr.sun_family = AF_UNIX; -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wstringop-truncation" - strncpy(addr.sun_path, path, sizeof(addr.sun_path)); -#pragma GCC diagnostic pop - if (bind(fd, reinterpret_cast(&addr), sizeof(addr)) == -1 || - listen(fd, REMOTE_SERVER_BACKLOG) == -1) - { - log_printf("error subscribing to socket: %s\n", strerror(errno)); - close(fd); - return nullptr; - } - - rc = g_new0(RemoteConnection, 1); - - rc->server = TRUE; - rc->fd = fd; - rc->path = g_strdup(path); - - channel = g_io_channel_unix_new(rc->fd); - g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, nullptr); - - rc->channel_id = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, G_IO_IN, - remote_server_read_cb, rc, nullptr); - g_io_channel_unref(channel); - - return rc; -} - -static void remote_server_subscribe(RemoteConnection *rc, RemoteConnection::ReadFunc *func, gpointer data) -{ - if (!rc || !rc->server) return; - - rc->read_func = func; - rc->read_data = data; -} - - -static RemoteConnection *remote_client_open(const gchar *path) -{ - RemoteConnection *rc; - struct stat st; - struct sockaddr_un addr; - gint fd; - - if (strlen(path) >= sizeof(addr.sun_path)) return nullptr; - - if (stat(path, &st) != 0 || !S_ISSOCK(st.st_mode)) return nullptr; - - fd = socket(PF_UNIX, SOCK_STREAM, 0); - if (fd == -1) return nullptr; - - addr.sun_family = AF_UNIX; -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wstringop-truncation" - strncpy(addr.sun_path, path, sizeof(addr.sun_path)); -#pragma GCC diagnostic pop - if (connect(fd, reinterpret_cast(&addr), sizeof(addr)) == -1) - { - DEBUG_1("error connecting to socket: %s", strerror(errno)); - close(fd); - return nullptr; - } - - rc = g_new0(RemoteConnection, 1); - rc->server = FALSE; - rc->fd = fd; - rc->path = g_strdup(path); - - return rc; -} - -static sig_atomic_t sigpipe_occurred = FALSE; - -static void sighandler_sigpipe(gint) -{ - sigpipe_occurred = TRUE; -} - -static gboolean remote_client_send(RemoteConnection *rc, const gchar *text) -{ - struct sigaction new_action; - struct sigaction old_action; - gboolean ret = FALSE; - GError *error = nullptr; - GIOChannel *channel; - - if (!rc || rc->server) return FALSE; - if (!text) return TRUE; - - sigpipe_occurred = FALSE; - - new_action.sa_handler = sighandler_sigpipe; - sigemptyset(&new_action.sa_mask); - new_action.sa_flags = 0; - - /* setup our signal handler */ - sigaction(SIGPIPE, &new_action, &old_action); - - channel = g_io_channel_unix_new(rc->fd); - - g_io_channel_write_chars(channel, text, -1, nullptr, &error); - g_io_channel_write_chars(channel, "", -1, nullptr, &error); - g_io_channel_flush(channel, &error); - - if (error) - { - log_printf("error reading socket: %s\n", error->message); - g_error_free(error); - ret = FALSE;; - } - else - { - ret = TRUE; - } - - if (ret) - { - gchar *buffer = nullptr; - gsize termpos; - g_io_channel_set_line_term(channel, "", -1); - while (g_io_channel_read_line(channel, &buffer, nullptr, &termpos, &error) == G_IO_STATUS_NORMAL) - { - if (buffer) - { - if (g_strstr_len(buffer, -1, "") == buffer) /* empty line finishes the command */ - { - g_free(buffer); - fflush(stdout); - break; - } - buffer[termpos] = '\0'; - if (g_strstr_len(buffer, -1, "print0") != nullptr) - { - print0 = TRUE; - } - else - { - if (print0) - { - printf("%s%c", buffer, 0); - } - else - { - printf("%s\n", buffer); - } - } - g_free(buffer); - buffer = nullptr; - } - } - - if (error) - { - log_printf("error reading socket: %s\n", error->message); - g_error_free(error); - ret = FALSE; - } - } - - - /* restore the original signal handler */ - sigaction(SIGPIPE, &old_action, nullptr); - g_io_channel_unref(channel); - return ret; -} - -void remote_close(RemoteConnection *rc) -{ - if (!rc) return; - - if (rc->server) - { - remote_server_clients_close(rc); - - g_source_remove(rc->channel_id); - unlink(rc->path); - } - - g_free(rc->read_data); - - close(rc->fd); - - g_free(rc->path); - g_free(rc); -} - -/* - *----------------------------------------------------------------------------- - * remote functions - *----------------------------------------------------------------------------- - */ - -static void gr_image_next(const gchar *, GIOChannel *, gpointer) -{ - layout_image_next(lw_id); -} - -static void gr_new_window(const gchar *, GIOChannel *, gpointer) -{ - LayoutWindow *lw = nullptr; - - if (!layout_valid(&lw)) return; - - lw_id = layout_new_from_default(); - - layout_set_path(lw_id, pwd); -} - -static gboolean gr_close_window_cb(gpointer) -{ - if (!layout_valid(&lw_id)) return FALSE; - - layout_menu_close_cb(nullptr, lw_id); - - return G_SOURCE_REMOVE; -} - -static void gr_close_window(const gchar *, GIOChannel *, gpointer) -{ - g_idle_add((gr_close_window_cb), nullptr); -} - -static void gr_image_prev(const gchar *, GIOChannel *, gpointer) -{ - layout_image_prev(lw_id); -} - -static void gr_image_first(const gchar *, GIOChannel *, gpointer) -{ - layout_image_first(lw_id); -} - -static void gr_image_last(const gchar *, GIOChannel *, gpointer) -{ - layout_image_last(lw_id); -} - -static void gr_fullscreen_toggle(const gchar *, GIOChannel *, gpointer) -{ - layout_image_full_screen_toggle(lw_id); -} - -static void gr_fullscreen_start(const gchar *, GIOChannel *, gpointer) -{ - layout_image_full_screen_start(lw_id); -} - -static void gr_fullscreen_stop(const gchar *, GIOChannel *, gpointer) -{ - layout_image_full_screen_stop(lw_id); -} - -static void gr_lw_id(const gchar *text, GIOChannel *, gpointer) -{ - lw_id = layout_find_by_layout_id(text); - if (!lw_id) - { - log_printf("remote sent window ID that does not exist:\"%s\"\n",text); - } - layout_valid(&lw_id); -} - -static void gr_slideshow_start_rec(const gchar *text, GIOChannel *, gpointer) -{ - GList *list; - gchar *tilde_filename; - - tilde_filename = expand_tilde(text); - - FileData *dir_fd = file_data_new_dir(tilde_filename); - g_free(tilde_filename); - - layout_valid(&lw_id); - list = filelist_recursive_full(dir_fd, lw_id->options.file_view_list_sort.method, lw_id->options.file_view_list_sort.ascend, lw_id->options.file_view_list_sort.case_sensitive); - file_data_unref(dir_fd); - if (!list) return; - - layout_image_slideshow_stop(lw_id); - layout_image_slideshow_start_from_list(lw_id, list); -} - -static void gr_cache_thumb(const gchar *text, GIOChannel *, gpointer) -{ - if (!g_strcmp0(text, "clear")) - { - cache_maintain_home_remote(FALSE, TRUE, nullptr); - } - else if (!g_strcmp0(text, "clean")) - { - cache_maintain_home_remote(FALSE, FALSE, nullptr); - } -} - -static void gr_cache_shared(const gchar *text, GIOChannel *, gpointer) -{ - if (!g_strcmp0(text, "clear")) - cache_manager_standard_process_remote(TRUE); - else if (!g_strcmp0(text, "clean")) - cache_manager_standard_process_remote(FALSE); -} - -static void gr_cache_metadata(const gchar *, GIOChannel *, gpointer) -{ - cache_maintain_home_remote(TRUE, FALSE, nullptr); -} - -static void gr_cache_render(const gchar *text, GIOChannel *, gpointer) -{ - cache_manager_render_remote(text, FALSE, FALSE, nullptr); -} - -static void gr_cache_render_recurse(const gchar *text, GIOChannel *, gpointer) -{ - cache_manager_render_remote(text, TRUE, FALSE, nullptr); -} - -static void gr_cache_render_standard(const gchar *text, GIOChannel *, gpointer) -{ - if(options->thumbnails.spec_standard) - { - cache_manager_render_remote(text, FALSE, TRUE, nullptr); - } -} - -static void gr_cache_render_standard_recurse(const gchar *text, GIOChannel *, gpointer) -{ - if(options->thumbnails.spec_standard) - { - cache_manager_render_remote(text, TRUE, TRUE, nullptr); - } -} - -static void gr_slideshow_toggle(const gchar *, GIOChannel *, gpointer) -{ - layout_image_slideshow_toggle(lw_id); -} - -static void gr_slideshow_start(const gchar *, GIOChannel *, gpointer) -{ - layout_image_slideshow_start(lw_id); -} - -static void gr_slideshow_stop(const gchar *, GIOChannel *, gpointer) -{ - layout_image_slideshow_stop(lw_id); -} - -static void gr_slideshow_delay(const gchar *text, GIOChannel *, gpointer) -{ - gdouble t1; - gdouble t2; - gdouble t3; - gdouble n; - gint res; - - res = sscanf(text, "%lf:%lf:%lf", &t1, &t2, &t3); - if (res == 3) - { - n = (t1 * 3600) + (t2 * 60) + t3; - if (n < SLIDESHOW_MIN_SECONDS || n > SLIDESHOW_MAX_SECONDS || - t1 >= 24 || t2 >= 60 || t3 >= 60) - { - printf_term(TRUE, "Remote slideshow delay out of range (%.1f to %.1f)\n", - SLIDESHOW_MIN_SECONDS, SLIDESHOW_MAX_SECONDS); - return; - } - } - else if (res == 2) - { - n = t1 * 60 + t2; - if (n < SLIDESHOW_MIN_SECONDS || n > SLIDESHOW_MAX_SECONDS || - t1 >= 60 || t2 >= 60) - { - printf_term(TRUE, "Remote slideshow delay out of range (%.1f to %.1f)\n", - SLIDESHOW_MIN_SECONDS, SLIDESHOW_MAX_SECONDS); - return; - } - } - else if (res == 1) - { - n = t1; - if (n < SLIDESHOW_MIN_SECONDS || n > SLIDESHOW_MAX_SECONDS) - { - printf_term(TRUE, "Remote slideshow delay out of range (%.1f to %.1f)\n", - SLIDESHOW_MIN_SECONDS, SLIDESHOW_MAX_SECONDS); - return; - } - } - else - { - n = 0; - } - - options->slideshow.delay = static_cast(n * 10.0 + 0.01); -} - -static void gr_tools_show(const gchar *, GIOChannel *, gpointer) -{ - gboolean popped; - gboolean hidden; - - if (layout_tools_float_get(lw_id, &popped, &hidden) && hidden) - { - layout_tools_float_set(lw_id, popped, FALSE); - } -} - -static void gr_tools_hide(const gchar *, GIOChannel *, gpointer) -{ - gboolean popped; - gboolean hidden; - - if (layout_tools_float_get(lw_id, &popped, &hidden) && !hidden) - { - layout_tools_float_set(lw_id, popped, TRUE); - } -} - -static gboolean gr_quit_idle_cb(gpointer) -{ - exit_program(); - - return G_SOURCE_REMOVE; -} - -static void gr_quit(const gchar *, GIOChannel *, gpointer) -{ - /* schedule exit when idle, if done from within a - * remote handler remote_close will crash - */ - g_idle_add(gr_quit_idle_cb, nullptr); -} - -static void gr_file_load_no_raise(const gchar *text, GIOChannel *, gpointer) -{ - gchar *filename; - gchar *tilde_filename; - - if (!download_web_file(text, TRUE, nullptr)) - { - tilde_filename = expand_tilde(text); - filename = set_pwd(tilde_filename); - - if (isfile(filename)) - { - if (file_extension_match(filename, GQ_COLLECTION_EXT)) - { - collection_window_new(filename); - } - else - { - layout_set_path(lw_id, filename); - } - } - else if (isdir(filename)) - { - layout_set_path(lw_id, filename); - } - else - { - log_printf("remote sent filename that does not exist:\"%s\"\n", filename); - layout_set_path(lw_id, homedir()); - } - - g_free(filename); - g_free(tilde_filename); - } -} - -static void gr_file_load(const gchar *text, GIOChannel *channel, gpointer data) -{ - gr_file_load_no_raise(text, channel, data); - - gr_raise(text, channel, data); -} - -static void gr_pixel_info(const gchar *, GIOChannel *channel, gpointer) -{ - gchar *pixel_info; - gint x_pixel; - gint y_pixel; - gint width; - gint height; - gint r_mouse; - gint g_mouse; - gint b_mouse; - gint a_mouse; - PixbufRenderer *pr; - - if (!layout_valid(&lw_id)) return; - - pr = PIXBUF_RENDERER(lw_id->image->pr); - - if (pr) - { - pixbuf_renderer_get_image_size(pr, &width, &height); - if (width < 1 || height < 1) return; - - pixbuf_renderer_get_mouse_position(pr, &x_pixel, &y_pixel); - - if (x_pixel >= 0 && y_pixel >= 0) - { - pixbuf_renderer_get_pixel_colors(pr, x_pixel, y_pixel, &r_mouse, &g_mouse, &b_mouse, &a_mouse); - - if (gdk_pixbuf_get_has_alpha(pr->pixbuf)) - { - pixel_info = g_strdup_printf(_("[%d,%d]: RGBA(%3d,%3d,%3d,%3d)"), - x_pixel, y_pixel, - r_mouse, g_mouse, b_mouse, a_mouse); - } - else - { - pixel_info = g_strdup_printf(_("[%d,%d]: RGB(%3d,%3d,%3d)"), - x_pixel, y_pixel, - r_mouse, g_mouse, b_mouse); - } - - g_io_channel_write_chars(channel, pixel_info, -1, nullptr, nullptr); - g_io_channel_write_chars(channel, "", -1, nullptr, nullptr); - - g_free(pixel_info); - } - else - { - return; - } - } - else - { - return; - } -} - -static void gr_rectangle(const gchar *, GIOChannel *channel, gpointer) -{ - if (!options->draw_rectangle) return; - if (!layout_valid(&lw_id)) return; - - auto *pr = PIXBUF_RENDERER(lw_id->image->pr); - if (!pr) return; - - gint x1; - gint y1; - gint x2; - gint y2; - image_get_rectangle(x1, y1, x2, y2); - - gchar *rectangle_info = g_strdup_printf(_("%dx%d+%d+%d"), - std::abs(x1 - x2), - std::abs(y1 - y2), - std::min(x1, x2), - std::min(y1, y2)); - - g_io_channel_write_chars(channel, rectangle_info, -1, nullptr, nullptr); - g_io_channel_write_chars(channel, "", -1, nullptr, nullptr); - - g_free(rectangle_info); -} - -static void gr_render_intent(const gchar *, GIOChannel *channel, gpointer) -{ - gchar *render_intent; - - switch (options->color_profile.render_intent) - { - case 0: - render_intent = g_strdup("Perceptual"); - break; - case 1: - render_intent = g_strdup("Relative Colorimetric"); - break; - case 2: - render_intent = g_strdup("Saturation"); - break; - case 3: - render_intent = g_strdup("Absolute Colorimetric"); - break; - default: - render_intent = g_strdup("none"); - break; - } - - g_io_channel_write_chars(channel, render_intent, -1, nullptr, nullptr); - g_io_channel_write_chars(channel, "", -1, nullptr, nullptr); - - g_free(render_intent); -} - -static void get_filelist(const gchar *text, GIOChannel *channel, gboolean recurse) -{ - GList *list = nullptr; - FileFormatClass format_class; - FileData *dir_fd; - FileData *fd; - GList *work; - gchar *tilde_filename; - - if (strcmp(text, "") == 0) - { - if (layout_valid(&lw_id)) - { - dir_fd = file_data_new_dir(lw_id->dir_fd->path); - } - else - { - return; - } - } - else - { - tilde_filename = expand_tilde(text); - if (isdir(tilde_filename)) - { - dir_fd = file_data_new_dir(tilde_filename); - } - else - { - g_free(tilde_filename); - return; - } - g_free(tilde_filename); - } - - if (recurse) - { - list = filelist_recursive(dir_fd); - } - else - { - filelist_read(dir_fd, &list, nullptr); - } - - GString *out_string = g_string_new(nullptr); - work = list; - while (work) - { - fd = static_cast(work->data); - g_string_append(out_string, fd->path); - format_class = filter_file_get_class(fd->path); - - switch (format_class) - { - case FORMAT_CLASS_IMAGE: - out_string = g_string_append(out_string, " Class: Image"); - break; - case FORMAT_CLASS_RAWIMAGE: - out_string = g_string_append(out_string, " Class: RAW image"); - break; - case FORMAT_CLASS_META: - out_string = g_string_append(out_string, " Class: Metadata"); - break; - case FORMAT_CLASS_VIDEO: - out_string = g_string_append(out_string, " Class: Video"); - break; - case FORMAT_CLASS_COLLECTION: - out_string = g_string_append(out_string, " Class: Collection"); - break; - case FORMAT_CLASS_DOCUMENT: - out_string = g_string_append(out_string, " Class: Document"); - break; - case FORMAT_CLASS_ARCHIVE: - out_string = g_string_append(out_string, " Class: Archive"); - break; - case FORMAT_CLASS_UNKNOWN: - out_string = g_string_append(out_string, " Class: Unknown"); - break; - default: - out_string = g_string_append(out_string, " Class: Unknown"); - break; - } - out_string = g_string_append(out_string, "\n"); - work = work->next; - } - - g_io_channel_write_chars(channel, out_string->str, -1, nullptr, nullptr); - g_io_channel_write_chars(channel, "", -1, nullptr, nullptr); - - g_string_free(out_string, TRUE); - filelist_free(list); - file_data_unref(dir_fd); -} - -static void gr_get_selection(const gchar *, GIOChannel *channel, gpointer) -{ - if (!layout_valid(&lw_id)) return; - - GList *selected = layout_selection_list(lw_id); // Keep copy to free. - GString *out_string = g_string_new(nullptr); - - GList *work = selected; - while (work) - { - auto fd = static_cast(work->data); - g_assert(fd->magick == FD_MAGICK); - - g_string_append_printf(out_string, "%s %s\n", - fd->path, - format_class_list[filter_file_get_class(fd->path)]); - - work = work->next; - } - - g_io_channel_write_chars(channel, out_string->str, -1, nullptr, nullptr); - g_io_channel_write_chars(channel, "", -1, nullptr, nullptr); - - filelist_free(selected); - g_string_free(out_string, TRUE); -} - -static void gr_selection_add(const gchar *text, GIOChannel *, gpointer) -{ - if (!layout_valid(&lw_id)) return; - - FileData *fd_to_select = nullptr; - if (strcmp(text, "") == 0) - { - // No file specified, use current fd. - fd_to_select = layout_image_get_fd(lw_id); - } - else - { - // Search through the current file list for a file matching the specified path. - // "Match" is either a basename match or a file path match. - gchar *path = expand_tilde(text); - gchar *filename = g_path_get_basename(path); - gchar *slash_plus_filename = g_strdup_printf("%s%s", G_DIR_SEPARATOR_S, filename); - - GList *file_list = layout_list(lw_id); - for (GList *work = file_list; work && !fd_to_select; work = work->next) - { - auto fd = static_cast(work->data); - if (!strcmp(path, fd->path) || g_str_has_suffix(fd->path, slash_plus_filename)) - { - fd_to_select = file_data_ref(fd); - continue; // will exit loop. - } - - for (GList *sidecar = fd->sidecar_files; sidecar && !fd_to_select; sidecar = sidecar->next) - { - auto side_fd = static_cast(sidecar->data); - if (!strcmp(path, side_fd->path) - || g_str_has_suffix(side_fd->path, slash_plus_filename)) - { - fd_to_select = file_data_ref(side_fd); - continue; // will exit both nested loops. - } - } - } - - if (!fd_to_select) - { - log_printf("remote sent --selection-add filename that could not be found: \"%s\"\n", - filename); - } - - filelist_free(file_list); - g_free(slash_plus_filename); - g_free(filename); - g_free(path); - } - - if (fd_to_select) - { - GList *to_select = g_list_append(nullptr, fd_to_select); - // Using the "_list" variant doesn't clear the existing selection. - layout_select_list(lw_id, to_select); - filelist_free(to_select); - } -} - -static void gr_selection_clear(const gchar *, GIOChannel *, gpointer) -{ - layout_select_none(lw_id); // Checks lw_id validity internally. -} - -static void gr_selection_remove(const gchar *text, GIOChannel *, gpointer) -{ - if (!layout_valid(&lw_id)) return; - - GList *selected = layout_selection_list(lw_id); // Keep copy to free. - if (!selected) - { - log_printf("remote sent --selection-remove with empty selection."); - return; - } - - FileData *fd_to_deselect = nullptr; - gchar *path = nullptr; - gchar *filename = nullptr; - gchar *slash_plus_filename = nullptr; - if (strcmp(text, "") == 0) - { - // No file specified, use current fd. - fd_to_deselect = layout_image_get_fd(lw_id); - if (!fd_to_deselect) - { - log_printf("remote sent \"--selection-remove:\" with no current image"); - filelist_free(selected); - return; - } - } - else - { - // Search through the selection list for a file matching the specified path. - // "Match" is either a basename match or a file path match. - path = expand_tilde(text); - filename = g_path_get_basename(path); - slash_plus_filename = g_strdup_printf("%s%s", G_DIR_SEPARATOR_S, filename); - } - - GList *prior_link = nullptr; // Stash base for link removal to avoid a second traversal. - GList *link_to_remove = nullptr; - for (GList *work = selected; work; prior_link = work, work = work->next) - { - auto fd = static_cast(work->data); - if (fd_to_deselect) - { - if (fd == fd_to_deselect) - { - link_to_remove = work; - break; - } - } - else - { - // path, filename, and slash_plus_filename should be defined. - - if (!strcmp(path, fd->path) || g_str_has_suffix(fd->path, slash_plus_filename)) - { - link_to_remove = work; - break; - } - } - } - - if (!link_to_remove) - { - if (fd_to_deselect) - { - log_printf("remote sent \"--selection-remove=\" but current image is not selected"); - } - else - { - log_printf("remote sent \"--selection-remove=%s\" but that filename is not selected", - filename); - } - } - else - { - if (link_to_remove == selected) - { - // Remove first link. - selected = g_list_remove_link(selected, link_to_remove); - filelist_free(link_to_remove); - link_to_remove = nullptr; - } - else - { - // Remove a subsequent link. - prior_link = g_list_remove_link(prior_link, link_to_remove); - filelist_free(link_to_remove); - link_to_remove = nullptr; - } - - // Re-select all but the deselected item. - layout_select_none(lw_id); - layout_select_list(lw_id, selected); - } - - filelist_free(selected); - file_data_unref(fd_to_deselect); - g_free(slash_plus_filename); - g_free(filename); - g_free(path); -} - -static void gr_collection(const gchar *text, GIOChannel *channel, gpointer) -{ - GString *contents = g_string_new(nullptr); - - if (is_collection(text)) - { - collection_contents(text, &contents); - } - else - { - return; - } - - g_io_channel_write_chars(channel, contents->str, -1, nullptr, nullptr); - g_io_channel_write_chars(channel, "", -1, nullptr, nullptr); - - g_string_free(contents, TRUE); -} - -static void gr_collection_list(const gchar *, GIOChannel *channel, gpointer) -{ - - GList *collection_list = nullptr; - GList *work; - GString *out_string = g_string_new(nullptr); - - collect_manager_list(&collection_list, nullptr, nullptr); - - work = collection_list; - while (work) - { - auto collection_name = static_cast(work->data); - out_string = g_string_append(out_string, collection_name); - out_string = g_string_append(out_string, "\n"); - - work = work->next; - } - - g_io_channel_write_chars(channel, out_string->str, -1, nullptr, nullptr); - g_io_channel_write_chars(channel, "", -1, nullptr, nullptr); - - g_list_free_full(collection_list, g_free); - g_string_free(out_string, TRUE); -} - -static gboolean wait_cb(gpointer data) -{ - gint position = GPOINTER_TO_INT(data); - gint x = position >> 16; - gint y = position - (x << 16); - - gq_gtk_window_move(GTK_WINDOW(lw_id->window), x, y); - - return G_SOURCE_REMOVE; -} - -static void gr_geometry(const gchar *text, GIOChannel *, gpointer) -{ - gchar **geometry; - - if (!layout_valid(&lw_id) || !text) - { - return; - } - - if (text[0] == '+') - { - geometry = g_strsplit_set(text, "+", 3); - if (geometry[1] != nullptr && geometry[2] != nullptr ) - { - gq_gtk_window_move(GTK_WINDOW(lw_id->window), atoi(geometry[1]), atoi(geometry[2])); - } - } - else - { - geometry = g_strsplit_set(text, "+x", 4); - if (geometry[0] != nullptr && geometry[1] != nullptr) - { - gtk_window_resize(GTK_WINDOW(lw_id->window), atoi(geometry[0]), atoi(geometry[1])); - } - if (geometry[2] != nullptr && geometry[3] != nullptr) - { - /* There is an occasional problem with a window_move immediately after a window_resize */ - g_idle_add(wait_cb, GINT_TO_POINTER((atoi(geometry[2]) << 16) + atoi(geometry[3]))); - } - } - g_strfreev(geometry); -} - -static void gr_filelist(const gchar *text, GIOChannel *channel, gpointer) -{ - get_filelist(text, channel, FALSE); -} - -static void gr_filelist_recurse(const gchar *text, GIOChannel *channel, gpointer) -{ - get_filelist(text, channel, TRUE); -} - -static void gr_file_tell(const gchar *, GIOChannel *channel, gpointer) -{ - gchar *out_string; - - if (!layout_valid(&lw_id)) return; - - const gchar *filename = image_get_path(lw_id->image); - if (filename) - { - if (lw_id->image->collection && lw_id->image->collection->name) - { - g_autofree gchar *collection_name = remove_extension_from_path(lw_id->image->collection->name); - out_string = g_strconcat(filename, " Collection: ", collection_name, NULL); - } - else - { - out_string = g_strconcat(filename, NULL); - } - } - else - { - out_string = g_strconcat(lw_id->dir_fd->path, G_DIR_SEPARATOR_S, NULL); - } - - g_io_channel_write_chars(channel, out_string, -1, nullptr, nullptr); - g_io_channel_write_chars(channel, "", -1, nullptr, nullptr); - - g_free(out_string); -} - -static void gr_file_info(const gchar *, GIOChannel *channel, gpointer) -{ - FileData *fd; - GString *out_string; - FileFormatClass format_class; - - if (!layout_valid(&lw_id)) return; - - const gchar *filename = image_get_path(lw_id->image); - if (filename) - { - fd = file_data_new_group(filename); - out_string = g_string_new(nullptr); - - if (fd->pixbuf) - { - format_class = filter_file_get_class(filename); - } - else - { - format_class = FORMAT_CLASS_UNKNOWN; - } - - g_string_append_printf(out_string, _("Class: %s\n"), format_class_list[format_class]); - - if (fd->page_total > 1) - { - g_string_append_printf(out_string, _("Page no: %d/%d\n"), fd->page_num + 1, fd->page_total); - } - - if (fd->exif) - { - g_autofree gchar *country_name = exif_get_data_as_text(fd->exif, "formatted.countryname"); - if (country_name) - { - g_string_append_printf(out_string, _("Country name: %s\n"), country_name); - } - - g_autofree gchar *country_code = exif_get_data_as_text(fd->exif, "formatted.countrycode"); - if (country_code) - { - g_string_append_printf(out_string, _("Country code: %s\n"), country_code); - } - - g_autofree gchar *timezone = exif_get_data_as_text(fd->exif, "formatted.timezone"); - if (timezone) - { - g_string_append_printf(out_string, _("Timezone: %s\n"), timezone); - } - - g_autofree gchar *local_time = exif_get_data_as_text(fd->exif, "formatted.localtime"); - if (local_time) - { - g_string_append_printf(out_string, ("Local time: %s\n"), local_time); - } - } - - g_io_channel_write_chars(channel, out_string->str, -1, nullptr, nullptr); - g_io_channel_write_chars(channel, "", -1, nullptr, nullptr); - - g_string_free(out_string, TRUE); - file_data_unref(fd); - } -} - -static gchar *config_file_path(const gchar *param) -{ - gchar *path = nullptr; - gchar *full_name = nullptr; - - if (file_extension_match(param, ".xml")) - { - path = g_build_filename(get_window_layouts_dir(), param, NULL); - } - else if (file_extension_match(param, nullptr)) - { - full_name = g_strconcat(param, ".xml", NULL); - path = g_build_filename(get_window_layouts_dir(), full_name, NULL); - } - - if (!isfile(path)) - { - g_free(path); - path = nullptr; - } - - g_free(full_name); - return path; -} - -static gboolean is_config_file(const gchar *param) -{ - gchar *name = nullptr; - - name = config_file_path(param); - if (name) - { - g_free(name); - return TRUE; - } - return FALSE; -} - -static void gr_config_load(const gchar *text, GIOChannel *, gpointer) -{ - gchar *filename = expand_tilde(text); - - if (!g_strstr_len(filename, -1, G_DIR_SEPARATOR_S)) - { - if (is_config_file(filename)) - { - gchar *tmp = config_file_path(filename); - g_free(filename); - filename = tmp; - } - } - - if (isfile(filename)) - { - load_config_from_file(filename, FALSE); - } - else - { - log_printf("remote sent filename that does not exist:\"%s\"\n", filename ? filename : "(null)"); - layout_set_path(nullptr, homedir()); - } - - g_free(filename); -} - -static void gr_window_list(const gchar *, GIOChannel *channel, gpointer) -{ - GString *window_list; - - window_list = layout_get_window_list(); - - g_io_channel_write_chars(channel, window_list->str, -1, nullptr, nullptr); - g_io_channel_write_chars(channel, "", -1, nullptr, nullptr); - - g_string_free(window_list, TRUE); -} - -static void gr_get_sidecars(const gchar *text, GIOChannel *channel, gpointer) -{ - gchar *filename = expand_tilde(text); - FileData *fd = file_data_new_group(filename); - - GList *work; - if (fd->parent) fd = fd->parent; - - g_io_channel_write_chars(channel, fd->path, -1, nullptr, nullptr); - g_io_channel_write_chars(channel, "", -1, nullptr, nullptr); - - work = fd->sidecar_files; - - while (work) - { - fd = static_cast(work->data); - work = work->next; - g_io_channel_write_chars(channel, fd->path, -1, nullptr, nullptr); - g_io_channel_write_chars(channel, "", -1, nullptr, nullptr); - } - g_free(filename); -} - -static void gr_get_destination(const gchar *text, GIOChannel *channel, gpointer) -{ - gchar *filename = expand_tilde(text); - FileData *fd = file_data_new_group(filename); - - if (fd->change && fd->change->dest) - { - g_io_channel_write_chars(channel, fd->change->dest, -1, nullptr, nullptr); - g_io_channel_write_chars(channel, "", -1, nullptr, nullptr); - } - g_free(filename); -} - -static void gr_file_view(const gchar *text, GIOChannel *, gpointer) -{ - gchar *filename; - gchar *tilde_filename = expand_tilde(text); - - filename = set_pwd(tilde_filename); - - view_window_new(file_data_new_group(filename)); - g_free(filename); - g_free(tilde_filename); -} - -static void gr_list_clear(const gchar *, GIOChannel *, gpointer data) -{ - auto remote_data = static_cast(data); - - remote_data->command_collection = nullptr; - remote_data->file_list = nullptr; - remote_data->single_dir = TRUE; -} - -static void gr_list_add(const gchar *text, GIOChannel *, gpointer data) -{ - auto remote_data = static_cast(data); - gboolean is_new = TRUE; - gchar *path = nullptr; - FileData *fd; - FileData *first; - - /** @FIXME Should check if file is in current dir, has tilde or is relative */ - if (!isfile(text)) - { - log_printf("Warning: File does not exist --remote --list-add:%s", text); - - return; - } - - /* If there is a files list on the command line - * check if they are all in the same folder - */ - if (remote_data->single_dir) - { - GList *work; - work = remote_data->file_list; - while (work && remote_data->single_dir) - { - gchar *dirname; - dirname = g_path_get_dirname((static_cast(work->data))->path); - if (!path) - { - path = g_strdup(dirname); - } - else - { - if (g_strcmp0(path, dirname) != 0) - { - remote_data->single_dir = FALSE; - } - } - g_free(dirname); - work = work->next; - } - g_free(path); - } - - gchar *pathname = g_path_get_dirname(text); - layout_set_path(lw_id, pathname); - g_free(pathname); - - fd = file_data_new_simple(text); - remote_data->file_list = g_list_append(remote_data->file_list, fd); - file_data_unref(fd); - - vf_select_none(lw_id->vf); - remote_data->file_list = g_list_reverse(remote_data->file_list); - - layout_select_list(lw_id, remote_data->file_list); - layout_refresh(lw_id); - first = static_cast(g_list_first(vf_selection_get_list(lw_id->vf))->data); - layout_set_fd(lw_id, first); - - CollectionData *cd; - CollectWindow *cw; - - if (!remote_data->command_collection && !remote_data->single_dir) - { - cw = collection_window_new(nullptr); - cd = cw->cd; - - collection_path_changed(cd); - - remote_data->command_collection = cd; - } - else if (!remote_data->single_dir) - { - is_new = (!collection_get_first(remote_data->command_collection)); - } - - if (!remote_data->single_dir) - { - layout_image_set_collection(lw_id, remote_data->command_collection, collection_get_first(remote_data->command_collection)); - if (collection_add(remote_data->command_collection, file_data_new_group(text), FALSE) && is_new) - { - layout_image_set_collection(lw_id, remote_data->command_collection, collection_get_first(remote_data->command_collection)); - } - } -} - -static void gr_action(const gchar *text, GIOChannel *, gpointer) -{ - GtkAction *action; - - if (!layout_valid(&lw_id)) - { - return; - } - - if (g_strstr_len(text, -1, ".desktop") != nullptr) - { - file_util_start_editor_from_filelist(text, layout_selection_list(lw_id), layout_get_path(lw_id), lw_id->window); - } - else - { - action = gq_gtk_action_group_get_action(lw_id->action_group, text); - if (action) - { - gq_gtk_action_activate(action); - } - else - { - log_printf("Action %s unknown", text); - } - } -} - -static void gr_action_list(const gchar *, GIOChannel *channel, gpointer) -{ - gint max_length = 0; - - if (!layout_valid(&lw_id)) - { - return; - } - - std::vector list = get_action_items(); - - /* Get the length required for padding */ - for (const ActionItem &action_item : list) - { - const auto length = g_utf8_strlen(action_item.name, -1); - max_length = MAX(length, max_length); - } - - /* Pad the action names to the same column for readable output */ - g_autoptr(GString) out_string = g_string_new(nullptr); - for (const ActionItem &action_item : list) - { - g_string_append_printf(out_string, "%-*s", max_length + 4, action_item.name); - out_string = g_string_append(out_string, action_item.label); - out_string = g_string_append(out_string, "\n"); - } - - g_io_channel_write_chars(channel, out_string->str, -1, nullptr, nullptr); - g_io_channel_write_chars(channel, "", -1, nullptr, nullptr); -} - -static void gr_raise(const gchar *, GIOChannel *, gpointer) -{ - if (layout_valid(&lw_id)) - { - gtk_window_present(GTK_WINDOW(lw_id->window)); - } -} - -static void gr_pwd(const gchar *text, GIOChannel *, gpointer) -{ - LayoutWindow *lw = nullptr; - - layout_valid(&lw); - - g_free(pwd); - pwd = g_strdup(text); - lw_id = lw; -} - -static void gr_print0(const gchar *, GIOChannel *channel, gpointer) -{ - g_io_channel_write_chars(channel, "print0", -1, nullptr, nullptr); - g_io_channel_write_chars(channel, "", -1, nullptr, nullptr); -} - -#if HAVE_LUA -static void gr_lua(const gchar *text, GIOChannel *channel, gpointer) -{ - gchar *result = nullptr; - gchar **lua_command; - - lua_command = g_strsplit(text, ",", 2); - - if (lua_command[0] && lua_command[1]) - { - FileData *fd = file_data_new_group(lua_command[0]); - result = g_strdup(lua_callvalue(fd, lua_command[1], nullptr)); - if (result) - { - g_io_channel_write_chars(channel, result, -1, nullptr, nullptr); - } - else - { - g_io_channel_write_chars(channel, N_("lua error: no data"), -1, nullptr, nullptr); - } - } - else - { - g_io_channel_write_chars(channel, N_("lua error: no data"), -1, nullptr, nullptr); - } - - g_io_channel_write_chars(channel, "", -1, nullptr, nullptr); - - g_strfreev(lua_command); - g_free(result); -} -#endif - -struct RemoteCommandEntry { - const gchar *opt_s; - const gchar *opt_l; - void (*func)(const gchar *text, GIOChannel *channel, gpointer data); - gboolean needs_extra; - gboolean prefer_command_line; - const gchar *parameter; - const gchar *description; -}; - -static RemoteCommandEntry remote_commands[] = { - /* short, long callback, extra, prefer, parameter, description */ - { nullptr, "--action=", gr_action, TRUE, FALSE, N_(""), N_("execute keyboard action (See Help/Reference/Remote Keyboard Actions)") }, - { nullptr, "--action-list", gr_action_list, FALSE, FALSE, nullptr, N_("list available keyboard actions (some are redundant)") }, - { "-b", "--back", gr_image_prev, FALSE, FALSE, nullptr, N_("previous image") }, - { nullptr, "--close-window", gr_close_window, FALSE, FALSE, nullptr, N_("close window") }, - { nullptr, "--config-load=", gr_config_load, TRUE, FALSE, N_("|layout ID"), N_("load configuration from FILE") }, - { nullptr, "--cache-metadata", gr_cache_metadata, FALSE, FALSE, nullptr, N_("clean the metadata cache") }, - { nullptr, "--cache-render=", gr_cache_render, TRUE, FALSE, N_(" "), N_(" render thumbnails") }, - { nullptr, "--cache-render-recurse=", gr_cache_render_recurse, TRUE, FALSE, N_(" "), N_("render thumbnails recursively") }, - { nullptr, "--cache-render-shared=", gr_cache_render_standard,TRUE, FALSE, N_(" "), N_(" render thumbnails (see Help)") }, - { nullptr, "--cache-render-shared-recurse=", gr_cache_render_standard_recurse, TRUE, FALSE, N_(""), N_(" render thumbnails recursively (see Help)") }, - { nullptr, "--cache-shared=", gr_cache_shared, TRUE, FALSE, N_("clean|clear"), N_("clean or clear shared thumbnail cache") }, - { nullptr, "--cache-thumbs=", gr_cache_thumb, TRUE, FALSE, N_("clean|clear"), N_("clean or clear thumbnail cache") }, - { "-d", "--delay=", gr_slideshow_delay, TRUE, FALSE, N_("<[H:][M:][N][.M]>"), N_("set slide show delay to Hrs Mins N.M seconds") }, - { nullptr, "--first", gr_image_first, FALSE, FALSE, nullptr, N_("first image") }, - { "-f", "--fullscreen", gr_fullscreen_toggle, FALSE, TRUE, nullptr, N_("toggle full screen") }, - { nullptr, "--file=", gr_file_load, TRUE, FALSE, N_("|"), N_("open FILE or URL, bring Geeqie window to the top") }, - { nullptr, "file=", gr_file_load, TRUE, FALSE, N_("|"), N_("open FILE or URL, bring Geeqie window to the top") }, - { nullptr, "--File=", gr_file_load_no_raise, TRUE, FALSE, N_("|"), N_("open FILE or URL, do not bring Geeqie window to the top") }, - { nullptr, "File=", gr_file_load_no_raise, TRUE, FALSE, N_("|"), N_("open FILE or URL, do not bring Geeqie window to the top") }, - { nullptr, "--fullscreen-start", gr_fullscreen_start, FALSE, FALSE, nullptr, N_("start full screen") }, - { nullptr, "--fullscreen-stop", gr_fullscreen_stop, FALSE, FALSE, nullptr, N_("stop full screen") }, - { nullptr, "--geometry=", gr_geometry, TRUE, FALSE, N_(""), N_("set window geometry") }, - { nullptr, "--get-collection=", gr_collection, TRUE, FALSE, N_(""), N_("get collection content") }, - { nullptr, "--get-collection-list", gr_collection_list, FALSE, FALSE, nullptr, N_("get collection list") }, - { nullptr, "--get-destination=", gr_get_destination, TRUE, FALSE, N_(""), N_("get destination path of FILE (See Plugins Configuration)") }, - { nullptr, "--get-file-info", gr_file_info, FALSE, FALSE, nullptr, N_("get file info") }, - { nullptr, "--get-filelist=", gr_filelist, TRUE, FALSE, N_("[]"), N_("get list of files and class") }, - { nullptr, "--get-filelist-recurse=", gr_filelist_recurse, TRUE, FALSE, N_("[]"), N_("get list of files and class recursive") }, - { nullptr, "--get-rectangle", gr_rectangle, FALSE, FALSE, nullptr, N_("get rectangle co-ordinates") }, - { nullptr, "--get-render-intent", gr_render_intent, FALSE, FALSE, nullptr, N_("get render intent") }, - { nullptr, "--get-selection", gr_get_selection, FALSE, FALSE, nullptr, N_("get list of selected files") }, - { nullptr, "--get-sidecars=", gr_get_sidecars, TRUE, FALSE, N_(""), N_("get list of sidecars of FILE") }, - { nullptr, "--get-window-list", gr_window_list, FALSE, FALSE, nullptr, N_("get window list") }, - { nullptr, "--id=", gr_lw_id, TRUE, FALSE, N_(""), N_("window id for following commands") }, - { nullptr, "--last", gr_image_last, FALSE, FALSE, nullptr, N_("last image") }, - { nullptr, "--list-add=", gr_list_add, TRUE, FALSE, N_(""), N_("add FILE to command line collection list") }, - { nullptr, "--list-clear", gr_list_clear, FALSE, FALSE, nullptr, N_("clear command line collection list") }, -#if HAVE_LUA - { nullptr, "--lua=", gr_lua, TRUE, FALSE, N_(","), N_("run lua script on FILE") }, -#endif - { nullptr, "--new-window", gr_new_window, FALSE, FALSE, nullptr, N_("new window") }, - { "-n", "--next", gr_image_next, FALSE, FALSE, nullptr, N_("next image") }, - { nullptr, "--pixel-info", gr_pixel_info, FALSE, FALSE, nullptr, N_("print pixel info of mouse pointer on current image") }, - { nullptr, "--print0", gr_print0, TRUE, FALSE, nullptr, N_("terminate returned data with null character instead of newline") }, - { nullptr, "--PWD=", gr_pwd, TRUE, FALSE, N_(""), N_("use PWD as working directory for following commands") }, - { "-q", "--quit", gr_quit, FALSE, FALSE, nullptr, N_("quit") }, - { nullptr, "--raise", gr_raise, FALSE, FALSE, nullptr, N_("bring the Geeqie window to the top") }, - { nullptr, "raise", gr_raise, FALSE, FALSE, nullptr, N_("bring the Geeqie window to the top") }, - { nullptr, "--selection-add=", gr_selection_add, TRUE, FALSE, N_("[]"), N_("adds the current file (or the specified file) to the current selection") }, - { nullptr, "--selection-clear", gr_selection_clear, FALSE, FALSE, nullptr, N_("clears the current selection") }, - { nullptr, "--selection-remove=", gr_selection_remove, TRUE, FALSE, N_("[]"), N_("removes the current file (or the specified file) from the current selection") }, - { "-s", "--slideshow", gr_slideshow_toggle, FALSE, TRUE, nullptr, N_("toggle slide show") }, - { nullptr, "--slideshow-recurse=", gr_slideshow_start_rec, TRUE, FALSE, N_(""), N_("start recursive slide show in FOLDER") }, - { nullptr, "--slideshow-start", gr_slideshow_start, FALSE, FALSE, nullptr, N_("start slide show") }, - { nullptr, "--slideshow-stop", gr_slideshow_stop, FALSE, FALSE, nullptr, N_("stop slide show") }, - { nullptr, "--tell", gr_file_tell, FALSE, FALSE, nullptr, N_("print filename [and Collection] of current image") }, - { "-T", "--tools-show", gr_tools_show, FALSE, TRUE, nullptr, N_("show tools") }, - { "-t", "--tools-hide", gr_tools_hide, FALSE, TRUE, nullptr, N_("hide tools") }, - { nullptr, "--view=", gr_file_view, TRUE, FALSE, N_(""), N_("open FILE in new window") }, - { nullptr, "view=", gr_file_view, TRUE, FALSE, N_(""), N_("open FILE in new window") }, - { nullptr, nullptr, nullptr, FALSE, FALSE, nullptr, nullptr } -}; - -static RemoteCommandEntry *remote_command_find(const gchar *text, const gchar **offset) -{ - gint i = 0; - - while (remote_commands[i].func != nullptr) - { - if (remote_commands[i].needs_extra) - { - if (remote_commands[i].opt_s && - strncmp(remote_commands[i].opt_s, text, strlen(remote_commands[i].opt_s)) == 0) - { - if (offset) *offset = text + strlen(remote_commands[i].opt_s); - return &remote_commands[i]; - } - - if (remote_commands[i].opt_l && - strncmp(remote_commands[i].opt_l, text, strlen(remote_commands[i].opt_l)) == 0) - { - if (offset) *offset = text + strlen(remote_commands[i].opt_l); - return &remote_commands[i]; - } - } - else - { - if ((remote_commands[i].opt_s && strcmp(remote_commands[i].opt_s, text) == 0) || - (remote_commands[i].opt_l && strcmp(remote_commands[i].opt_l, text) == 0)) - { - if (offset) *offset = text; - return &remote_commands[i]; - } - } - - i++; - } - - return nullptr; -} - -gboolean is_remote_command(const gchar *text) -{ - RemoteCommandEntry *entry = nullptr; - - entry = remote_command_find(text, nullptr); - - return entry ? TRUE : FALSE; -} - -static void remote_cb(RemoteConnection *, const gchar *text, GIOChannel *channel, gpointer data) -{ - RemoteCommandEntry *entry; - const gchar *offset; - - entry = remote_command_find(text, &offset); - if (entry && entry->func) - { - entry->func(offset, channel, data); - } - else - { - log_printf("unknown remote command:%s\n", text); - } -} - -void remote_help() -{ - gint i; - gchar *s_opt_param; - gchar *l_opt_param; - - print_term(FALSE, _("Remote command list:\n")); - - i = 0; - while (remote_commands[i].func != nullptr) - { - if (remote_commands[i].description) - { - s_opt_param = g_strdup(remote_commands[i].opt_s ? remote_commands[i].opt_s : "" ); - l_opt_param = g_strconcat(remote_commands[i].opt_l, remote_commands[i].parameter, NULL); - - if (g_str_has_prefix(l_opt_param, "--")) - { - printf_term(FALSE, " %-4s %-40s%-s\n", s_opt_param, l_opt_param, _(remote_commands[i].description)); - } - g_free(s_opt_param); - g_free(l_opt_param); - } - i++; - } - printf_term(FALSE, "%s", _("\n\n All other command line parameters are used as plain files if they exist.\n\n The name of a collection, with or without either path or extension (.gqv) may be used.\n")); -} - -GList *remote_build_list(GList *list, gint argc, gchar *argv[], GList **errors) -{ - gint i; - - i = 1; - while (i < argc) - { - RemoteCommandEntry *entry; - - entry = remote_command_find(argv[i], nullptr); - if (entry) - { - list = g_list_append(list, argv[i]); - } - else if (errors && !isname(argv[i])) - { - *errors = g_list_append(*errors, argv[i]); - } - i++; - } - - return list; -} - -/** - * @param arg_exec Binary (argv0) - * @param remote_list Evaluated and recognized remote commands - * @param path The current path - * @param cmd_list List of all non collections in Path (gchar *path) - * @param collection_list List of all collections in argv - */ -void remote_control(const gchar *arg_exec, GList *remote_list, const gchar *path, - GList *cmd_list, GList *collection_list) -{ - RemoteConnection *rc; - gboolean started = FALSE; - gchar *buf; - - buf = g_build_filename(get_rc_dir(), ".command", NULL); - rc = remote_client_open(buf); - if (!rc) - { - GString *command; - GList *work; - gint retry_count = 12; - gboolean blank = FALSE; - - printf_term(FALSE, _("Remote %s not running, starting..."), GQ_APPNAME); - - command = g_string_new(arg_exec); - - work = remote_list; - while (work) - { - gchar *text; - RemoteCommandEntry *entry; - - text = static_cast(work->data); - work = work->next; - - entry = remote_command_find(text, nullptr); - if (entry) - { - /* If Geeqie is not running, stop the --new-window command opening a second window */ - if (g_strcmp0(text, "--new-window") == 0) - { - remote_list = g_list_remove(remote_list, text); - } - if (entry->prefer_command_line) - { - remote_list = g_list_remove(remote_list, text); - g_string_append(command, " "); - g_string_append(command, text); - } - if (entry->opt_l && strcmp(entry->opt_l, "file=") == 0) - { - blank = TRUE; - } - } - } - - if (blank || cmd_list || path) g_string_append(command, " --blank"); - if (get_debug_level()) g_string_append(command, " --debug"); - - g_string_append(command, " &"); - runcmd(command->str); - g_string_free(command, TRUE); - - while (!rc && retry_count > 0) - { - usleep((retry_count > 10) ? 500000 : 1000000); - rc = remote_client_open(buf); - if (!rc) print_term(FALSE, "."); - retry_count--; - } - - print_term(FALSE, "\n"); - - started = TRUE; - } - g_free(buf); - - if (rc) - { - GList *work; - const gchar *prefix; - gboolean use_path = TRUE; - gboolean sent = FALSE; - - work = remote_list; - while (work) - { - gchar *text; - RemoteCommandEntry *entry; - - text = static_cast(work->data); - work = work->next; - - entry = remote_command_find(text, nullptr); - if (entry && - entry->opt_l && - strcmp(entry->opt_l, "file=") == 0) use_path = FALSE; - - remote_client_send(rc, text); - - sent = TRUE; - } - - if (cmd_list && cmd_list->next) - { - prefix = "--list-add="; - remote_client_send(rc, "--list-clear"); - } - else - { - prefix = "file="; - } - - work = cmd_list; - while (work) - { - gchar *text; - - text = g_strconcat(prefix, work->data, NULL); - remote_client_send(rc, text); - g_free(text); - work = work->next; - - sent = TRUE; - } - - if (path && !cmd_list && use_path) - { - gchar *text; - - text = g_strdup_printf("file=%s", path); - remote_client_send(rc, text); - g_free(text); - - sent = TRUE; - } - - work = collection_list; - while (work) - { - const gchar *name; - gchar *text; - - name = static_cast(work->data); - work = work->next; - - text = g_strdup_printf("file=%s", name); - remote_client_send(rc, text); - g_free(text); - - sent = TRUE; - } - - if (!started && !sent) - { - remote_client_send(rc, "raise"); - } - } - else - { - print_term(TRUE, _("Remote not available\n")); - } - - _exit(0); -} - -RemoteConnection *remote_server_init(gchar *path, CollectionData *command_collection) -{ - RemoteConnection *remote_connection = remote_server_open(path); - auto remote_data = g_new(RemoteData, 1); - - remote_data->command_collection = command_collection; - - remote_server_subscribe(remote_connection, remote_cb, remote_data); - return remote_connection; -} -/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/remote.h b/src/remote.h deleted file mode 100644 index 4b40a6b22..000000000 --- a/src/remote.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2004 John Ellis - * Copyright (C) 2008 - 2016 The Geeqie Team - * - * Author: John Ellis - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef REMOTE_H -#define REMOTE_H - -#include - -struct CollectionData; - -struct RemoteConnection { - gint server; - gint fd; - gchar *path; - - gint channel_id; - - using ReadFunc = void (RemoteConnection *, const gchar *, GIOChannel *, gpointer); - ReadFunc *read_func; - gpointer read_data; - - GList *clients; -}; - - -void remote_close(RemoteConnection *rc); -GList *remote_build_list(GList *list, gint argc, gchar *argv[], GList **errors); -void remote_help(); -void remote_control(const gchar *arg_exec, GList *remote_list, const gchar *path, - GList *cmd_list, GList *collection_list); - -RemoteConnection *remote_server_init(gchar *path, CollectionData *command_collection); -gboolean remote_server_exists(const gchar *path); -gboolean is_remote_command(const gchar *text); - -#endif -/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/ui-fileops.cc b/src/ui-fileops.cc index 5933dc18b..6273f2dc3 100644 --- a/src/ui-fileops.cc +++ b/src/ui-fileops.cc @@ -947,7 +947,16 @@ static void download_web_file_cancel_button_cb(GenericDialog *, gpointer data) g_cancellable_cancel(web->cancellable); } -gboolean download_web_file(const gchar *text, gboolean minimized, gpointer data) +/** + * @brief Determine if file is a web URL + * @param text File name to check + * @param minimized + * @param data Layout window or null + * @returns Full path to the created temporary file or null + * + * If the file is a web file, start a background load to a temporary file. + */ +gchar *download_web_file(const gchar *text, gboolean minimized, gpointer data) { g_autofree gchar *scheme = g_uri_parse_scheme(text); if (g_strcmp0("http", scheme) != 0 && g_strcmp0("https", scheme) != 0) @@ -969,7 +978,7 @@ gboolean download_web_file(const gchar *text, gboolean minimized, gpointer data) if (error) { log_printf("Error: could not create temporary file n%s\n", error->message); - return FALSE; + return nullptr; } auto *web = g_new0(WebData, 1); @@ -997,7 +1006,7 @@ gboolean download_web_file(const gchar *text, gboolean minimized, gpointer data) web->cancellable = g_cancellable_new(); g_file_copy_async(web->web_file, web->tmp_g_file, G_FILE_COPY_OVERWRITE, G_PRIORITY_LOW, web->cancellable, web_file_progress_cb, web, web_file_async_ready_cb, web); - return TRUE; + return g_file_get_path(web->tmp_g_file); } gboolean rmdir_recursive(GFile *file, GCancellable *cancellable, GError **error) diff --git a/src/ui-fileops.h b/src/ui-fileops.h index df3c9cfe9..70840859d 100644 --- a/src/ui-fileops.h +++ b/src/ui-fileops.h @@ -106,7 +106,7 @@ gboolean recursive_mkdir_if_not_exists(const gchar *path, mode_t mode); gchar *md5_text_from_file_utf8(const gchar *path, const gchar *error_text); gboolean md5_get_digest_from_file_utf8(const gchar *path, guchar digest[16]); -gboolean download_web_file(const gchar *text, gboolean minimized, gpointer data); +gchar *download_web_file(const gchar *text, gboolean minimized, gpointer data); gboolean rmdir_recursive(GFile *file, GCancellable *cancellable, GError **error); gint scale_factor(); diff --git a/src/ui-utildlg.cc b/src/ui-utildlg.cc index 02d4b089c..1876cb600 100644 --- a/src/ui-utildlg.cc +++ b/src/ui-utildlg.cc @@ -647,7 +647,7 @@ static void appimage_notification_func(gpointer data, gpointer) } } -void appimage_notification() +void appimage_notification(GtkApplication *) { AppImageData *appimage_data; diff --git a/src/ui-utildlg.h b/src/ui-utildlg.h index b6a043ea0..3a7d6dc47 100644 --- a/src/ui-utildlg.h +++ b/src/ui-utildlg.h @@ -95,6 +95,7 @@ void file_dialog_sync_history(FileDialog *fd, gboolean dir_only); void generic_dialog_windows_load_config(const gchar **attribute_names, const gchar **attribute_values); void generic_dialog_windows_write_config(GString *outstr, gint indent); -void appimage_notification(); +void appimage_notification(GtkApplication *app); + #endif /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/ui/appimage-notification.ui b/src/ui/appimage-notification.ui deleted file mode 100644 index f115f3d20..000000000 --- a/src/ui/appimage-notification.ui +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - False - 300 - 40 - True - False - False - north-east - - - True - False - A new Geeqie AppImage is available - - - - diff --git a/src/ui/meson.build b/src/ui/meson.build index 549df3aea..a51da49d0 100644 --- a/src/ui/meson.build +++ b/src/ui/meson.build @@ -26,5 +26,4 @@ ui_resources_c = custom_target('ui_resources_c', project_sources += ui_resources_h project_sources += ui_resources_c -ui_sources += files('appimage-notification.ui', -'search-and-run.ui') +ui_sources += files('search-and-run.ui') diff --git a/src/ui/ui.gresource.xml b/src/ui/ui.gresource.xml index f688180c4..c7ea0321c 100644 --- a/src/ui/ui.gresource.xml +++ b/src/ui/ui.gresource.xml @@ -23,7 +23,6 @@ - appimage-notification.ui custom.css menu-classic.ui menu-hamburger.ui