From 54d84a83affa3bc779429eaa15429b03cc562f84 Mon Sep 17 00:00:00 2001 From: DogiFnf Date: Sun, 5 Apr 2026 16:52:57 +0300 Subject: [PATCH 1/2] Add fast screenshot mode and auto-save functionality --- data/help.txt | 2 ++ gradia/gradia.in | 6 +++-- gradia/main.py | 15 ++++++++----- gradia/ui/image_exporters.py | 43 ++++++++++++++++++++++++++++++++++++ gradia/ui/image_loaders.py | 1 + gradia/ui/window.py | 8 +++++++ 6 files changed, 68 insertions(+), 7 deletions(-) diff --git a/data/help.txt b/data/help.txt index 084b7ed..55a70d4 100644 --- a/data/help.txt +++ b/data/help.txt @@ -4,6 +4,7 @@ Usage: gradia [OPTIONS] [FILES...] Options: -h, --help Show this help message and exit + --fast Save screenshot with default settings without opening editor --screenshot[=MODE] Take a screenshot on startup MODE can be: INTERACTIVE (default) - Interactive screenshot @@ -20,6 +21,7 @@ Examples: gradia --screenshot=FULL Take a full screen screenshot gradia --screenshot --delay=3000 Take screenshot after 3 second delay gradia --screenshot=FULL --delay=1500 Take full screenshot after 1.5 second delay + gradia --fast --screenshot-file=/path/to/screenshot.png Save screenshot with defaults cat image.png | gradia Open image from standard input (stdin) Report bugs to: https://github.com/alexandervanhee/gradia diff --git a/gradia/gradia.in b/gradia/gradia.in index b00af5b..566e0cf 100755 --- a/gradia/gradia.in +++ b/gradia/gradia.in @@ -87,8 +87,8 @@ if __name__ == '__main__': except ValueError: print("Invalid delay value. Using default of 0.") delay = 0 - if mode in ('INTERACTIVE', 'FULL'): - flags = Xdp.ScreenshotFlags.INTERACTIVE if mode == 'INTERACTIVE' else Xdp.ScreenshotFlags.NONE + if mode in ('INTERACTIVE', 'FULL', 'FAST'): + flags = Xdp.ScreenshotFlags.INTERACTIVE if mode in ['INTERACTIVE', 'FAST'] else Xdp.ScreenshotFlags.NONE screenshotter = QuickStartScreenshotTaker() screenshot_path = screenshotter.take_screenshot(flags=flags, delay_ms=delay) if screenshot_path is None: @@ -96,6 +96,8 @@ if __name__ == '__main__': sys.exit(1) # Add the screenshot path to command line arguments sys.argv.append(f"--screenshot-file={screenshot_path}") + if mode == 'FAST': + sys.argv.append("--fast") gi.require_version('Gtk', '4.0') gi.require_version('Adw', '1') from gi.repository import Gio diff --git a/gradia/main.py b/gradia/main.py index b2f1c8f..a040a3a 100644 --- a/gradia/main.py +++ b/gradia/main.py @@ -61,11 +61,14 @@ def do_command_line(self, command_line: Gio.ApplicationCommandLine) -> int: files_to_open = [] screenshot_file = None + fast = False for arg in args: if arg.startswith("--screenshot-file="): screenshot_file = arg.split("=", 1)[1] logging.info(f"Screenshot file detected: {screenshot_file}") + elif arg == '--fast': + fast = True elif not arg.startswith("--"): try: file = Gio.File.new_for_commandline_arg(arg) @@ -82,7 +85,7 @@ def do_command_line(self, command_line: Gio.ApplicationCommandLine) -> int: for path in files_to_open: self._open_window(file_path=path) elif screenshot_file: - self._open_window(start_screenshot=screenshot_file) + self._open_window(start_screenshot=screenshot_file, fast=fast) else: self.activate() @@ -113,8 +116,8 @@ def do_activate(self): else: self._open_window(None) - def _open_window(self, file_path: Optional[str] = None, start_screenshot: Optional[str] = None): - logging.info(f"Opening window with file_path={file_path}") + def _open_window(self, file_path: Optional[str] = None, start_screenshot: Optional[str] = None, fast: bool = False): + logging.info(f"Opening window with file_path={file_path}, fast={fast}") temp_dir = tempfile.mkdtemp() logging.debug(f"Created temp directory: {temp_dir}") self.temp_dirs.append(temp_dir) @@ -124,9 +127,11 @@ def _open_window(self, file_path: Optional[str] = None, start_screenshot: Option version=self.version, application=self, file_path=file_path, - start_screenshot=start_screenshot + start_screenshot=start_screenshot, + fast=fast ) - window.show() + if not fast: + window.show() def on_shutdown(self, application): logging.info("Application shutdown started, cleaning temp directories…") diff --git a/gradia/ui/image_exporters.py b/gradia/ui/image_exporters.py index 5a3477b..bc3fffa 100644 --- a/gradia/ui/image_exporters.py +++ b/gradia/ui/image_exporters.py @@ -514,3 +514,46 @@ def close_handler(self, copy: bool, save: bool, callback: callable = None): def is_export_available(self) -> bool: """Check if export operations are available""" return bool(self.window.processed_pixbuf) + + def auto_save_to_screenshot_folder(self) -> None: + """Automatically save processed image to screenshot folder""" + logger.info("Starting auto-save to screenshot folder") + if not self.is_export_available(): + logger.warning("Export not available, no processed pixbuf") + return + + from gradia.utils.timestamp_filename import TimestampedFilenameGenerator + import os + from gi.repository import GLib + + + if hasattr(self.window, 'start_screenshot') and self.window.start_screenshot: + save_path = self.window.start_screenshot + logger.info(f"Saving back to original file: {save_path}") + else: + folder = self.window.settings.screenshot_folder + if not folder: + folder = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES) + if not folder: + folder = os.path.expanduser("~/Pictures/Screenshots") + if not os.path.exists(folder): + os.makedirs(folder) + logger.info(f"Saving to folder: {folder}") + + # Generate filename + generator = TimestampedFilenameGenerator() + base_name = generator.generate(_("Edited Screenshot From %Y-%m-%d %H-%M-%S")) + ext = SUPPORTED_EXPORT_FORMATS[self.window.settings.export_format]['extensions'][0] + filename = base_name + ext + save_path = os.path.join(folder, filename) + logger.info(f"Generated save path: {save_path}") + + # Save + pixbuf = self.file_exporter.get_processed_pixbuf() + try: + pixbuf.savev(save_path, self.window.settings.export_format, [], []) + self.window._show_notification(_("Image saved to screenshot folder")) + logger.info(f"Auto-saved image to: {save_path}") + except Exception as e: + logger.error(f"Failed to auto-save image: {e}") + self.window._show_notification(_("Failed to save image")) diff --git a/gradia/ui/image_loaders.py b/gradia/ui/image_loaders.py index 73709da..70e5d00 100644 --- a/gradia/ui/image_loaders.py +++ b/gradia/ui/image_loaders.py @@ -307,6 +307,7 @@ def _handle_screenshot_uri(self, uri: str) -> None: self.window._show_notification(_("Failed to process screenshot")) def load_path_as_screenshot(self, file_path: str) -> None: + logger.info(f"Loading screenshot from path: {file_path}") try: file = Gio.File.new_for_path(file_path) uri = file.get_uri() diff --git a/gradia/ui/window.py b/gradia/ui/window.py index 89c186e..9694073 100644 --- a/gradia/ui/window.py +++ b/gradia/ui/window.py @@ -76,6 +76,7 @@ def __init__( version: str, file_path: Optional[str] = None, start_screenshot: Optional[str] = None, + fast: bool = False, **kwargs ) -> None: super().__init__(**kwargs) @@ -85,6 +86,7 @@ def __init__( self.app: Adw.Application = kwargs['application'] self.temp_dir: str = temp_dir self.version: str = version + self.fast = fast self.start_screenshot = start_screenshot self.file_path: Optional[str] = file_path self.image: Optional[LoadedImage] = None @@ -374,6 +376,8 @@ def after_process(): self.export_manager.copy_to_clipboard(silent=True) self._set_export_ready(True) self.lookup_action("open-folder").set_enabled(image.has_proper_folder()) + if self.fast: + self._auto_save_and_exit() self.process_image(callback=after_process) @@ -444,6 +448,10 @@ def _set_loading_state(self, is_loading: bool) -> None: child: str = getattr(self, "_previous_stack_child", self.PAGE_IMAGE) self.image_stack.set_visible_child_name(child) + def _auto_save_and_exit(self) -> None: + self.export_manager.auto_save_to_screenshot_folder() + self.app.quit() + def _set_export_ready(self, enabled: bool) -> None: self.image_ready = True for action_name in ["save", "copy"]: From eab9d3db95aa4d36d85ecc0b98986dffc91ddd0c Mon Sep 17 00:00:00 2001 From: DogiFnf Date: Sun, 5 Apr 2026 18:41:08 +0300 Subject: [PATCH 2/2] Add fast screenshot mode preferences --- data/be.alexandervanhee.gradia.gschema.xml | 8 ++++++ data/ui/preferences_window.blp | 16 +++++++++++ gradia/backend/settings.py | 16 +++++++++++ gradia/gradia.in | 14 +++++++-- gradia/ui/image_exporters.py | 2 +- gradia/ui/preferences/preferences_window.py | 32 +++++++++++++++++++++ 6 files changed, 85 insertions(+), 3 deletions(-) diff --git a/data/be.alexandervanhee.gradia.gschema.xml b/data/be.alexandervanhee.gradia.gschema.xml index 4e4adc5..e4e1a04 100644 --- a/data/be.alexandervanhee.gradia.gschema.xml +++ b/data/be.alexandervanhee.gradia.gschema.xml @@ -60,6 +60,14 @@ true Whether to compress the exported file (if supported) + + 'INTERACTIVE' + Mode used for fast screenshots + + + true + Whether to overwrite the original screenshot file when using fast screenshot mode + false Whether to trash all taken screenshots from the current session on close. diff --git a/data/ui/preferences_window.blp b/data/ui/preferences_window.blp index e0e6869..a671a84 100644 --- a/data/ui/preferences_window.blp +++ b/data/ui/preferences_window.blp @@ -38,6 +38,22 @@ template $GradiaPreferencesWindow: Adw.PreferencesDialog { } } } + + Adw.ComboRow fast_screenshot_mode_combo { + name: "fast_screenshot_mode_combo"; + title: _("Fast Screenshot Mode"); + use-underline: true; + subtitle: _("Choose the default mode for fast screenshots"); + } + + Adw.SwitchRow overwrite_fast_screenshot_switch { + name: "overwrite_fast_screenshot_switch"; + title: _("Overwrite Original Screenshot on Fast Save"); + use-underline: true; + subtitle: _("Replace the original screenshot file when using fast screenshot mode"); + tooltip-text: _("Use the source screenshot file as the save destination for fast screenshots"); + activatable: true; + } } Adw.PreferencesGroup { diff --git a/gradia/backend/settings.py b/gradia/backend/settings.py index 06ef0bc..5436d7b 100644 --- a/gradia/backend/settings.py +++ b/gradia/backend/settings.py @@ -83,6 +83,22 @@ def export_format(self, value: str) -> None: def export_compress(self) -> bool: return self._settings.get_boolean("export-compress") + @property + def fast_screenshot_mode(self) -> str: + return self._settings.get_string("fast-screenshot-mode") + + @fast_screenshot_mode.setter + def fast_screenshot_mode(self, value: str) -> None: + self._settings.set_string("fast-screenshot-mode", value) + + @property + def fast_screenshot_overwrite_original(self) -> bool: + return self._settings.get_boolean("fast-screenshot-overwrite-original") + + @fast_screenshot_overwrite_original.setter + def fast_screenshot_overwrite_original(self, value: bool) -> None: + self._settings.set_boolean("fast-screenshot-overwrite-original", value) + @property def delete_screenshots_on_close(self) -> bool: return self._settings.get_boolean("trash-screenshots-on-close") diff --git a/gradia/gradia.in b/gradia/gradia.in index 566e0cf..c67aeec 100755 --- a/gradia/gradia.in +++ b/gradia/gradia.in @@ -26,7 +26,7 @@ import logging from datetime import datetime import gi gi.require_version('Xdp', '1.0') -from gi.repository import Xdp, GLib +from gi.repository import Xdp, GLib, Gio logging.getLogger("PIL").setLevel(logging.WARNING) VERSION = '@VERSION@' pkgdatadir = '@PKGDATA_DIR@' @@ -36,6 +36,7 @@ signal.signal(signal.SIGINT, signal.SIG_DFL) locale.bindtextdomain('gradia', localedir) locale.textdomain('gradia') gettext.install('gradia', localedir) +from gradia.constants import app_id class QuickStartScreenshotTaker: def __init__(self): self.portal = Xdp.Portal() @@ -88,7 +89,16 @@ if __name__ == '__main__': print("Invalid delay value. Using default of 0.") delay = 0 if mode in ('INTERACTIVE', 'FULL', 'FAST'): - flags = Xdp.ScreenshotFlags.INTERACTIVE if mode in ['INTERACTIVE', 'FAST'] else Xdp.ScreenshotFlags.NONE + if mode == 'FAST': + settings = Gio.Settings.new(app_id) + fast_mode = settings.get_string('fast-screenshot-mode') + if fast_mode == 'FULL': + flags = Xdp.ScreenshotFlags.NONE + else: + flags = Xdp.ScreenshotFlags.INTERACTIVE + else: + flags = Xdp.ScreenshotFlags.INTERACTIVE if mode == 'INTERACTIVE' else Xdp.ScreenshotFlags.NONE + screenshotter = QuickStartScreenshotTaker() screenshot_path = screenshotter.take_screenshot(flags=flags, delay_ms=delay) if screenshot_path is None: diff --git a/gradia/ui/image_exporters.py b/gradia/ui/image_exporters.py index bc3fffa..8999655 100644 --- a/gradia/ui/image_exporters.py +++ b/gradia/ui/image_exporters.py @@ -527,7 +527,7 @@ def auto_save_to_screenshot_folder(self) -> None: from gi.repository import GLib - if hasattr(self.window, 'start_screenshot') and self.window.start_screenshot: + if hasattr(self.window, 'start_screenshot') and self.window.start_screenshot and self.window.settings.fast_screenshot_overwrite_original: save_path = self.window.start_screenshot logger.info(f"Saving back to original file: {save_path}") else: diff --git a/gradia/ui/preferences/preferences_window.py b/gradia/ui/preferences/preferences_window.py index eb5acdb..0821a9f 100644 --- a/gradia/ui/preferences/preferences_window.py +++ b/gradia/ui/preferences/preferences_window.py @@ -40,6 +40,8 @@ class PreferencesWindow(Adw.PreferencesDialog): delete_screenshot_switch: Adw.SwitchRow = Gtk.Template.Child() overwrite_screenshot_switch: Adw.SwitchRow = Gtk.Template.Child() confirm_upload_switch: Adw.SwitchRow = Gtk.Template.Child() + fast_screenshot_mode_combo: Adw.ComboRow = Gtk.Template.Child() + overwrite_fast_screenshot_switch: Adw.SwitchRow = Gtk.Template.Child() save_format_combo: Adw.ComboRow = Gtk.Template.Child() provider_name: Gtk.Label = Gtk.Template.Child() exiting_combo: Adw.ComboRow = Gtk.Template.Child() @@ -66,6 +68,7 @@ def __init__(self, parent_window: Adw.ApplicationWindow, **kwargs): self.add_controller(shortcut_controller) def _setup_widgets(self): + self._setup_fast_screenshot_mode_combo() self._setup_save_format_combo() self._setup_exiting_combo() self._setup_provider_display() @@ -106,6 +109,34 @@ def _setup_save_format_combo(self): self.save_format_combo.connect("notify::selected", self._on_save_format_changed) + def _setup_fast_screenshot_mode_combo(self): + current_mode = self.settings.fast_screenshot_mode + string_list = Gtk.StringList() + + fast_mode_options = [ + ("INTERACTIVE", _("Interactive")), + ("FULL", _("Full Screen")) + ] + self.fast_mode_keys = [key for key, _ in fast_mode_options] + + for key, display_name in fast_mode_options: + string_list.append(display_name) + + self.fast_screenshot_mode_combo.set_model(string_list) + + try: + current_index = self.fast_mode_keys.index(current_mode) + self.fast_screenshot_mode_combo.set_selected(current_index) + except ValueError: + self.fast_screenshot_mode_combo.set_selected(0) + + self.fast_screenshot_mode_combo.connect("notify::selected", self._on_fast_screenshot_mode_changed) + + def _on_fast_screenshot_mode_changed(self, combo_row, pspec) -> None: + selected = combo_row.get_selected() + if selected < len(self.fast_mode_keys): + self.settings.fast_screenshot_mode = self.fast_mode_keys[selected] + def _setup_exiting_combo(self): current_exit_method = self.settings.exit_method string_list = Gtk.StringList() @@ -161,6 +192,7 @@ def _bind_settings(self): self.settings.bind_switch(self.delete_screenshot_switch,"trash-screenshots-on-close") self.settings.bind_switch(self.confirm_upload_switch,"show-export-confirm-dialog") self.settings.bind_switch(self.overwrite_screenshot_switch,"overwrite-screenshot") + self.settings.bind_switch(self.overwrite_fast_screenshot_switch,"fast-screenshot-overwrite-original") @Gtk.Template.Callback() def on_choose_provider_clicked(self, button: Gtk.Button) -> None: