diff --git a/auto-complete/geeqie b/auto-complete/geeqie index 7f71d1b35..92d4ce4df 100755 --- 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..daf5b04c7 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', @@ -717,7 +725,7 @@ if option.enabled() should_fail = image_name.startswith('fail') test_cmd = [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']) + test('Image_ ' + image_name, isolate_test_sh, args: test_cmd, should_fail : should_fail, timeout: 100, is_parallel : false, 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..efb99ef52 --- /dev/null +++ b/org.geeqie.cache-maintenance.desktop.in @@ -0,0 +1,15 @@ +[Desktop Entry] +Name=Geeqie Cache Maintenance +GenericName=Image Viewer +Comment=View and manage images +Exec=/bin/true +Icon=geeqie +Type=Application +Terminal=false +# Startup notification disabled, since the remote -r switch may not open a new window... +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/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..e2910578c 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] @@ -88,18 +88,19 @@ def main(argv) -> int: # signal to the geeqie process. See # . # So we should modify or replace xvfb-run to allow us to kill an errant geeqie process. - geeqie_proc = subprocess.Popen(args=[*geeqie_cmd_prefix, test_image_path]) + geeqie_proc = subprocess.Popen(args=[*geeqie_cmd_prefix, test_image_path], env=os.environ) # 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") - while not command_fifo_path.exists(): - time.sleep(1) - if time.monotonic() - start_time > MAX_GEEQIE_INIT_TIME_S: - raise GeeqieTestError("init (before creating command fifo)", geeqie_proc) + # FIXME + # ~ 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: + # ~ raise GeeqieTestError("init (before creating command fifo)", geeqie_proc) # We make sure Geeqie can stay alive for 2 seconds after initialization. time.sleep(2) @@ -107,8 +108,8 @@ 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"], - capture_output=True, text=True, timeout=MAX_REMOTE_CMD_TIME_S) + args=[*geeqie_cmd_prefix, "--get-file-info"], + capture_output=True, text=True, timeout=MAX_REMOTE_CMD_TIME_S, env=os.environ) # Check if Geeqie crashed (which would cause xvfb-run to terminate) time.sleep(1) @@ -116,7 +117,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..f0a9c6fa1 100755 --- a/scripts/isolate-test.sh +++ b/scripts/isolate-test.sh @@ -28,6 +28,8 @@ ## $1 Test executable ## ## +## @FIXME It should be possible to use dbus-run-session, thus allowing +## is_parallel=true in meson.build for image tests set -e @@ -63,6 +65,13 @@ chmod 0700 "$XDG_RUNTIME_DIR" cd mkdir -p "$XDG_CONFIG_HOME" +# Primary and remote instances of GtkApplication communicate with dbus +dbus-launch + +# Inhibit shellcheck warning SC2154 - var is referenced but not assigned +echo "${DBUS_SESSION_BUS_ADDRESS:-}" +echo "${DBUS_SESSION_BUS_PID:-}" + # 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. @@ -71,4 +80,5 @@ mkdir -p "$XDG_CONFIG_HOME" # 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" "$@" +env -i DBUS_SESSION_BUS_PID="$DBUS_SESSION_BUS_PID" DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" G_DEBUG="fatal-warnings" HOME="$HOME" XDG_CONFIG_HOME="$XDG_CONFIG_HOME" XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" "$@" + 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..c3515d892 100755 --- a/scripts/test-ancillary-files.sh +++ b/scripts/test-ancillary-files.sh @@ -31,6 +31,7 @@ ## ui ## xml ## +## @FIXME Find a better way to ignore the junk if [ -n "$1" ] then @@ -279,7 +280,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 +301,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) @@ -325,35 +320,25 @@ echo "$action_list4" | sort > "$actions_cc" ./scripts/isolate-test.sh 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 +351,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 +372,34 @@ 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" +./scripts/isolate-test.sh 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 + #~ next } -/^[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 } @@ -520,9 +456,7 @@ 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 "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..25c4609fe --- /dev/null +++ b/src/command-line-handling.cc @@ -0,0 +1,1742 @@ +/* + * 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 +{ + +struct CommandLineOptionEntry +{ + const gchar *option_name; + void (*func)(GtkApplication *app, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *); +}; + +#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; +} + +/** + * @brief If this is not a remote instance, exit the applcation + * @param GtkApplication + * @param app_command_line + * @param option + * + * + */ +void remote_command(GtkApplication *, GApplicationCommandLine *app_command_line, const gchar *option) +{ + gboolean remote_instance; + + remote_instance = g_application_command_line_get_is_remote(app_command_line); + + if (!remote_instance) + { + g_application_command_line_print(app_command_line, "%s%s%s%s%s", _("Geeqie is not running: --"), BOLD_ON, option, BOLD_OFF, _(" is a Remote command\n")); + + exit(EXIT_FAILURE); + } +} + +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 *app, 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) + { + g_application_quit(G_APPLICATION(app)); + } + } + } + g_free(text); +} + +void gq_action_list(GtkApplication *app, GApplicationCommandLine *app_command_line,GVariantDict *, GList *) +{ + gint max_length = 0; + GString *out_string = g_string_new(nullptr); + gboolean remote_instance; + remote_instance = g_application_command_line_get_is_remote(app_command_line); + + 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 */ + 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); + + g_string_free(out_string, TRUE); + + if (!remote_instance) + { + g_application_quit(G_APPLICATION(app)); + } +} + +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); + 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 *app, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ + gchar *text; + g_variant_dict_lookup(command_line_options_dict, "get-collection", "&s", &text); + + gboolean remote_instance; + remote_instance = g_application_command_line_get_is_remote(app_command_line); + + 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); + + if (!remote_instance) + { + g_application_quit(G_APPLICATION(app)); + } +} + +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 *app, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ + remote_command(app, app_command_line, "get-selection"); + + 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 *app, GApplicationCommandLine *app_command_line, GVariantDict *, GList *) +{ + gchar *filename; + FileData *fd; + gchar *country_name; + gchar *country_code; + gchar *timezone; + gchar *local_time; + GString *out_string; + FileFormatClass format_class; + + remote_command(app, app_command_line, "get-file-info"); + + if (!layout_valid(&lw_id)) return; + + if (image_get_path(lw_id->image)) + { + filename = g_strdup(image_get_path(lw_id->image)); + + fd = file_data_new_group(filename); + out_string = g_string_new(nullptr); + + if (fd->pixbuf) + { +DEBUG_0(" " ); + format_class = filter_file_get_class(image_get_path(lw_id->image)); + } + else + { +DEBUG_0(" " ); + 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) + { + 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_free(country_name); + } + + country_code = exif_get_data_as_text(fd->exif, "formatted.countrycode"); + if (country_name) + { + g_string_append_printf(out_string, _("Country code: %s\n"), country_code); + g_free(country_code); + } + + timezone = exif_get_data_as_text(fd->exif, "formatted.timezone"); + if (timezone) + { + g_string_append_printf(out_string, _("Timezone: %s\n"), timezone); + g_free(timezone); + } + + 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_free(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); + g_free(filename); + } +} + + + + + +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 *app, GApplicationCommandLine *app_command_line, GVariantDict *, GList *) +{ + remote_command(app, app_command_line, "get-rectangle"); + + 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 *app, GApplicationCommandLine *app_command_line,GVariantDict *, GList *) +{ + gchar *render_intent; + + remote_command(app, app_command_line, "get-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 *app, GApplicationCommandLine *app_command_line, GVariantDict *, GList *) +{ + remote_command(app, app_command_line, "get-selection"); + + 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 *app, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ + remote_command(app, app_command_line, "get-sidecars"); + + 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 *app, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ + remote_command(app, app_command_line, "id="); + + 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 *app, GApplicationCommandLine *app_command_line, GVariantDict *command_line_options_dict, GList *) +{ +#if HAVE_LUA + gchar *result = nullptr; + gchar **lua_command; + gchar *text; + + remote_command(app, app_command_line, "lua"); + + 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 *app, 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; + + remote_command(app, app_command_line, "pixel-info"); + + 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 *, 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 + */ + g_idle_add(gq_quit_idle_cb, nullptr); +} + +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 *app, GApplicationCommandLine *app_command_line,GVariantDict *, GList *) +{ + remote_command(app, app_command_line, "selection-clear"); + + layout_select_none(lw_id); // Checks lw_id validity internally. +} + +void gq_selection_remove(GtkApplication *app, GApplicationCommandLine *app_command_line,GVariantDict *command_line_options_dict, GList *) +{ + remote_command(app, app_command_line, "get-remove"); + + 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 *app, GApplicationCommandLine *app_command_line, GVariantDict *, GList *) +{ + gchar *out_string; + gchar *collection_name = nullptr; + + remote_command(app, app_command_line, "tell"); + + layout_valid(&lw_id); + + if (image_get_path(lw_id->image)) + { + if (lw_id->image->collection && lw_id->image->collection->name) + { + collection_name = remove_extension_from_path(lw_id->image->collection->name); + out_string = g_strconcat(image_get_path(lw_id->image), " Collection: ", collection_name, NULL); + } + else + { + out_string = g_strconcat(image_get_path(lw_id->image), 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(collection_name); + 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 *app, GApplicationCommandLine *app_command_line,GVariantDict *, GList *) +{ + gboolean remote_instance; + + remote_instance = g_application_command_line_get_is_remote(app_command_line); + + g_application_command_line_print(app_command_line, "%s %s GTK%d\n", GQ_APPNAME, VERSION, gtk_major_version); + + if (!remote_instance) + { + g_application_quit(G_APPLICATION(app)); + } +} + +void gq_get_window_list(GtkApplication *app, GApplicationCommandLine *app_command_line,GVariantDict *, GList *) +{ + remote_command(app, app_command_line, "get-window-list"); + + 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)) + { + + CollectWindow *cw; + + const gchar *path = collection_path(current_arg); + collection_window_new(path); + } + else if (isfile(real_path)) + { + file_list = g_list_append(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_append(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)); + } + } + } + } + + return 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)); + 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 }, + { "id", gq_id }, + { "action", gq_action }, + { "action-list", gq_action_list }, + { "back", gq_back }, + { "cache-metadata", gq_cache_metadata }, + { "cache-render", gq_cache_render }, + { "cache-render-recurse", gq_cache_render_recurse }, + { "cache-render-shared", gq_cache_render_shared }, + { "cache-render-shared-recurse", gq_cache_render_shared_recurse }, + { "cache-shared", gq_cache_shared }, + { "cache-thumbs", gq_cache_thumbs }, + { "close-window", gq_close_window }, + { "config-load", gq_config_load }, + { "debug", gq_debug }, + { "delay", gq_delay }, + { "file", gq_file }, + { "File", gq_File }, + { "first", gq_first }, + { "fullscreen", gq_fullscreen }, + { "geometry", gq_geometry }, + { "get-collection", gq_get_collection }, + { "get-collection-list", gq_get_collection_list }, + { "get-destination", gq_get_destination }, + { "get-file-info", gq_get_file_info }, + { "get-filelist", gq_get_filelist }, + { "get-filelist-recurse", gq_get_filelist_recurse }, + { "get-rectangle", gq_get_rectangle }, + { "get-render-intent", gq_get_render_intent }, + { "get-render-intent", gq_get_render_intent }, + { "get-selection", gq_get_selection }, + { "get-sidecars", gq_get_sidecars }, + { "get-window-list", gq_get_window_list }, + { "grep", gq_grep }, + { "last", gq_last }, + { "log-file", gq_log_file }, + { "lua", gq_lua }, + { "new-window", gq_new_window }, + { "next", gq_next }, + { "pixel-info", gq_pixel_info }, + { "quit", gq_quit }, + { "raise", gq_raise }, + { "selection-add", gq_selection_add }, + { "selection-clear", gq_selection_clear }, + { "selection-remove", gq_selection_remove }, + { "show-log-window", gq_show_log_window }, + { "slideshow-recurse", gq_slideshow_recurse }, + { "slideshow", gq_slideshow }, + { "tell", gq_tell }, + { "tools", gq_tools }, + { "version", gq_version }, + { "view", gq_view }, + { nullptr, nullptr } +}; + +/* + * 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 }, + { "quit", gq_cm_quit }, + { nullptr, nullptr } +}; + +} // 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. An option handler might clear this 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)) + { + command_line_options[i].func(app, app_command_line, command_line_options_dict, file_list); + } + + i++; + } + + process_files(file_list); + + 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..ebce3a9db 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,20 @@ 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_GTK3 + if (g_list_length(layout_window_list) > 1) + { + gtk_widget_show(lw->window); + } +#endif +#if HAVE_GTK4 + if (g_list_length(layout_window_list) == 1) + { + gtk_widget_hide(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..398796d3c 100644 --- a/src/main.cc +++ b/src/main.cc @@ -59,7 +59,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 +67,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 +74,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 +86,6 @@ #endif gboolean thumb_format_changed = FALSE; -static RemoteConnection *remote_connection = nullptr; gchar *gq_prefix; gchar *gq_localedir; @@ -103,8 +97,104 @@ 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_NON_UNIQUE= 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= geeqie\n\n \ +To run or stop Geeqie in cache maintenance (non-GUI) mode use:\n \ +GQ_CACHE_MAINTENANCE= geeqie --help\n\n \ +User manual: https://www.geeqie.org/help/GuideIndex.html\n"); + +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"); + +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 +318,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 +340,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 +375,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 +384,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 +419,7 @@ static void marks_load() g_free(path); } -static void marks_save(gboolean save) +void marks_save(gboolean save) { gchar *path; @@ -823,7 +428,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 +440,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 +477,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 +515,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 +529,7 @@ static void accel_map_save() g_free(path); } -static void accel_map_load() +void accel_map_load() { gchar *path; gchar *pathl; @@ -937,7 +541,7 @@ static void accel_map_load() g_free(path); } -static void gtkrc_load() +void gtkrc_load() { gchar *path; gchar *pathl; @@ -952,7 +556,7 @@ static void gtkrc_load() g_free(path); } -static void exit_program_final() +void exit_program_final() { LayoutWindow *lw = nullptr; GList *list; @@ -963,8 +567,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 +616,25 @@ 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 +688,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 +743,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 +774,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 +790,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 +816,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); - gdk_set_allowed_backends("x11,*"); + g_application_activate(G_APPLICATION(app)); - gdk_threads_init(); - gdk_threads_enter(); + return ret; +} + +gint shutdown_cache_maintenance_cb(GtkApplication *, gpointer) +{ + exit(EXIT_SUCCESS); +} + +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 +873,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 +885,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 +904,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 +917,227 @@ 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); + keys_load(); + accel_map_load(); - if (parse_command_line_for_cache_maintenance_option(argc, argv)) + command_line = g_new0(CommandLine, 1); + + if (g_getenv("GQ_DISABLE_CLUTTER")) { - process_command_line_for_cache_maintenance_option(argc, argv); + options->disable_gpu = TRUE; } - else - { - DEBUG_1("%s main: parse_command_line", get_exec_time()); - parse_command_line(argc, argv); - - keys_load(); - accel_map_load(); - /* restore session from the config file */ - - - 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 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 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; + /* If Geeqie is not running and a command line option like --version + * is executed, display of the Geeqie window has to be inhibited. + * The activate signal is issued after the command line signal has been + * processed. + */ + if (lw->window) + { + gtk_widget_show(lw->window); + } +} - work = command_line->collection_list; - while (work) - { - CollectWindow *cw; - const gchar *path; +void startup_cb(GtkApplication *app, gpointer) +{ + GtkSettings *default_settings; - path = static_cast(work->data); - work = work->next; + startup_common(app, nullptr); - cw = collection_window_new(path); - if (!first_collection && cw) first_collection = cw->cd; - } - } + /* restore session from the config file */ - if (command_line->log_file) - { - gchar *pathl; - gchar *path = g_strdup(command_line->log_file); + 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(); + } - pathl = path_from_utf8(path); - command_line->ssi = secure_open(pathl); - } + /* handle missing config file and commandline additions*/ + if (!layout_window_list) + { + /* broken or no config file or no section */ + layout_new_from_default(); + } - /* 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; + layout_editors_reload_start(); - work = command_line->cmd_list; + marks_load(); - 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); - } + default_settings = gtk_settings_get_default(); - /* 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)) + g_signal_connect(default_settings, "notify::gtk-theme-name", G_CALLBACK(theme_change_cb), nullptr); + set_theme_bg_color(); + + /* 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); - */ + options->marks_save ? marks_save(TRUE) : marks_save(FALSE); - } - else if (first_collection) - { - layout_image_set_collection(nullptr, first_collection, - collection_get_first(first_collection)); - } + if (exit_confirm_dlg()) + { + return; + } + + exit_program_final(); +} - /* 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); +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 + if (! g_getenv("GQ_DISABLE_CLUTTER")) + { + 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=1 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 + + if (g_getenv("GQ_CACHE_MAINTENANCE")) + { + /* Disabled at the moment */ + log_printf("Command line cache maintenance is disabled"); + 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) + if (g_getenv("GQ_NON_UNIQUE")) { - 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, "startup", G_CALLBACK(startup_cb), nullptr); + g_signal_connect(app, "command-line", G_CALLBACK(command_line_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/ui-fileops.cc b/src/ui-fileops.cc index 5933dc18b..3ff52f3a2 100644 --- a/src/ui-fileops.cc +++ b/src/ui-fileops.cc @@ -947,8 +947,19 @@ 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) { + gchar *tmp_file_path = nullptr; + g_autofree gchar *scheme = g_uri_parse_scheme(text); if (g_strcmp0("http", scheme) != 0 && g_strcmp0("https", scheme) != 0) { @@ -969,7 +980,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 +1008,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