diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4ef337c6d..860d39264 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,15 +8,15 @@ repos: - "--markdown-linebreak-ext=md" exclude: COPYING - repo: https://github.com/PyCQA/flake8.git - rev: 3.9.2 + rev: 7.3.0 hooks: - id: flake8 - repo: https://github.com/PyCQA/pylint.git - rev: v2.13.6 + rev: v3.3.9 hooks: - id: pylint - repo: https://github.com/psf/black.git - rev: 22.10.0 + rev: 24.10.0 hooks: - id: black - repo: https://github.com/hackedd/fiximports.git diff --git a/guake/data/guake.glade b/guake/data/guake.glade index d031fcadf..4ba11fb0f 100644 --- a/guake/data/guake.glade +++ b/guake/data/guake.glade @@ -1,17 +1,17 @@ - + True - False + False gtk-preferences True - False - True - True + False + True + True @@ -19,52 +19,52 @@ gtk-about True - False - True - True + False + True + True True - False + False Quit True - False - False + False + False - False - True + False + True Guake! - True - True - True + True + True + True False True - False + False vertical notebook-teminals True - True - True - True - bottom - False - False - True + True + True + True + bottom + False + False + True True diff --git a/guake/gsettings.py b/guake/gsettings.py index 80b004b15..02b6cc5b8 100644 --- a/guake/gsettings.py +++ b/guake/gsettings.py @@ -146,7 +146,12 @@ def alignment_changed(self, settings, key, user_data): """If the gconf var window_halignment be changed, this method will be called and will call the move function in guake. """ - RectCalculator.set_final_window_rect(self.settings, self.guake.window) + # A persisted setting change should take priority over any in-memory + # size the user picked by dragging, so drop the stale override. + self.guake.settings_override = {} + RectCalculator.set_final_window_rect( + self.settings, self.guake.window, self.guake.settings_override + ) self.guake.set_tab_position() self.guake.force_move_if_shown() @@ -155,7 +160,12 @@ def size_changed(self, settings, key, user_data): this method will be called and will call the resize function in guake. """ - RectCalculator.set_final_window_rect(self.settings, self.guake.window) + # A persisted setting change should take priority over any in-memory + # size the user picked by dragging, so drop the stale override. + self.guake.settings_override = {} + RectCalculator.set_final_window_rect( + self.settings, self.guake.window, self.guake.settings_override + ) def cursor_blink_mode_changed(self, settings, key, user_data): """Called when cursor blink mode settings has been changed""" diff --git a/guake/guake_app.py b/guake/guake_app.py index f81e7bcb1..0231753ca 100644 --- a/guake/guake_app.py +++ b/guake/guake_app.py @@ -75,6 +75,7 @@ from guake.utils import FullscreenManager from guake.utils import HidePrevention from guake.utils import RectCalculator +from guake.utils import SettingsOverride from guake.utils import TabNameUtils from guake.utils import get_server_time from guake.utils import save_tabs_when_changed @@ -190,6 +191,21 @@ def load_schema(): self.mainframe = self.get_widget("mainframe") self.mainframe.remove(self.get_widget("notebook-teminals")) + # Window is undecorated (no WM title bar/borders), so provide a thin + # drag handle at the bottom edge to let users resize it manually. + # This is for issue: https://github.com/Guake/guake/issues/2337 + self.resize_handle = Gtk.EventBox() + self.resize_handle.set_size_request(-1, 8) + self.resize_handle.connect( + "realize", + lambda widget: widget.get_window().set_cursor( + Gdk.Cursor.new_from_name(Gdk.Display.get_default(), "ns-resize") + ), + ) + self.resize_handle.connect("button-press-event", self.on_resize_handle_button_press) + self.mainframe.pack_end(self.resize_handle, False, False, 0) + self.resize_handle.show() + # Pending restore for terminal split after show-up # [(RootTerminalBox, TerminaBox, panes), ...] self.pending_restore_page_split = [] @@ -223,6 +239,11 @@ def load_schema(): # Debounce accel_search_terminal self.prev_accel_search_terminal_time = 0.0 + # holds the GLib source id for the pending resize-debounce timeout + # used to capture resizing activities for https://github.com/Guake/guake/issues/2337 + self.resize_debounce_source_id = None + self.settings_override: SettingsOverride = {} + # holds the timestamp of the losefocus event self.losefocus_time = 0 @@ -237,6 +258,7 @@ def load_schema(): self.window.connect("focus-out-event", self.on_window_losefocus) self.window.connect("focus-in-event", self.on_window_takefocus) + self.window.connect("configure-event", self.on_window_configure) # Handling the delete-event of the main window to avoid # problems when closing it. @@ -544,6 +566,45 @@ def losefocus_callback(sleep_time): def on_window_takefocus(self, window, event): self.takefocus_time = get_server_time(self.window) + def on_resize_handle_button_press(self, widget, event): + if event.button == 1: + self.window.begin_resize_drag( + Gdk.WindowEdge.SOUTH, event.button, int(event.x_root), int(event.y_root), event.time + ) + return True + + def on_window_configure(self, window, event): + """Captures resizing activity for https://github.com/Guake/guake/issues/2337. + + Using Debounce strategy with GLib to capture event when user has completed + resizing their window. + """ + if self.resize_debounce_source_id is not None: + GLib.source_remove(self.resize_debounce_source_id) + + self.resize_debounce_source_id = GLib.timeout_add( + 300, self.on_resize_debounced, event.width, event.height + ) + return False + + def on_resize_debounced(self, width, height): + self.resize_debounce_source_id = None + + workarea = RectCalculator.get_final_window_monitor( + self.settings, self.window + ).get_workarea() + if workarea.height <= 0 or workarea.width <= 0: + log.warning("Skipping resize-override update: invalid workarea %r", workarea) + return GLib.SOURCE_REMOVE + + self.settings_override = SettingsOverride( + height_percentage=min(round(height / workarea.height * 100), 100), + width_percentage=min(round(width / workarea.width * 100), 100), + ) + + log.debug("Resize settled at %sx%s -> %r", width, height, self.settings_override) + return GLib.SOURCE_REMOVE + def show_menu(self, status_icon, button, activate_time): """Show the tray icon menu.""" menu = self.get_widget("tray-menu") @@ -696,7 +757,9 @@ def show(self): # setting window in all desktops - window_rect = RectCalculator.set_final_window_rect(self.settings, self.window) + window_rect = RectCalculator.set_final_window_rect( + self.settings, self.window, self.settings_override + ) self.window.stick() # add tab must be called before window.show to avoid a @@ -714,8 +777,11 @@ def show(self): GLib.idle_add(lambda: self.window.move(window_rect.x, window_rect.y) and False) # this works around an issue in fluxbox + # Note: re-applies the rect directly (rather than going through + # triggerOnChangedValue) so this synthetic re-trigger doesn't clear + # settings_override the way a real "window-height" change should. if not self.fullscreen_manager.is_fullscreen(): - self.settings.general.triggerOnChangedValue(self.settings.general, "window-height") + RectCalculator.set_final_window_rect(self.settings, self.window, self.settings_override) time = get_server_time(self.window) diff --git a/guake/utils.py b/guake/utils.py index dc07c80e6..e69cac025 100644 --- a/guake/utils.py +++ b/guake/utils.py @@ -27,6 +27,8 @@ import time import yaml +from typing import TypedDict + import cairo import gi @@ -50,6 +52,19 @@ log = logging.getLogger(__name__) +class SettingsOverride(TypedDict, total=False): + """Shape used to remember a window size the user picked by resizing. + + When the user drags the window to a new height/width, that choice is + stored in memory with this shape and takes priority over the persisted + "window-height"/"window-width" gsettings values for the rest of the + current session, without overwriting those settings. + """ + + height_percentage: int + width_percentage: int + + def gdk_is_x11_display(instance): if GdkX11: return isinstance(instance, GdkX11.X11Display) @@ -225,7 +240,8 @@ def unfullscreen(self): # FIX to unfullscreen after show, fullscreen, hide, unfullscreen # (unfullscreen breaks/does not shrink window size) - RectCalculator.set_final_window_rect(self.settings, self.window) + settings_override = self.guake.settings_override if self.guake else {} + RectCalculator.set_final_window_rect(self.settings, self.window, settings_override) def toggle(self): if self.is_fullscreen(): @@ -249,14 +265,40 @@ def toggle_fullscreen_hide_tabbar(self): class RectCalculator: @classmethod - def set_final_window_rect(cls, settings, window): - """Sets the final size and location of the main window of guake. The height - is the window_height property, width is window_width and the + # pylint: disable-next=dangerous-default-value + def set_final_window_rect( + cls, + settings, + window, + settings_override: SettingsOverride = {}, + ): + """Sets the final size and location of the main window of guake. + + The height is the window_height property, width is window_width and the horizontal alignment is given by window_alignment. + + Args: + settings: The Guake Settings object, used to read the + window-height, window-width, window-halignment, + window-valignment and displacement values from gsettings. + window: The Gtk.Window (main Guake window) to resize and move. + settings_override: Optional height/width percentages that take + precedence over the persisted "window-height"/"window-width" + gsettings values for this call only, without writing them + back to gsettings. Falls back entirely to the persisted + settings when empty. + + Returns: + Gdk.Rectangle: the computed window rectangle (position and size, + in screen coordinates) that was applied to the window. """ # fetch settings - height_percents = settings.general.get_int("window-height") - width_percents = settings.general.get_int("window-width") + height_percents = settings_override.get("height_percentage") or settings.general.get_int( + "window-height" + ) + width_percents = settings_override.get("width_percentage") or settings.general.get_int( + "window-width" + ) halignment = settings.general.get_int("window-halignment") valignment = settings.general.get_int("window-valignment") vdisplacement = settings.general.get_int("window-vertical-displacement") diff --git a/releasenotes/notes/drag_resize-86950fd8fe5422c3.yaml b/releasenotes/notes/drag_resize-86950fd8fe5422c3.yaml new file mode 100644 index 000000000..43cd7d48b --- /dev/null +++ b/releasenotes/notes/drag_resize-86950fd8fe5422c3.yaml @@ -0,0 +1,24 @@ +release_summary: > + You can now resize the drop-down terminal by dragging its bottom edge, + similar to Windows Terminal - handy for temporarily expanding the + terminal to read long command output without leaving the drop-down view. + +features: + - | + - Allow resizing the drop-down terminal by dragging its bottom edge (#2337). + +known_issues: + +upgrade: + +deprecations: + +security: + +fixes: + +translations: + +notes_for_package_maintainers: + +other: