diff --git a/ddterm/app/accellabel.js b/ddterm/app/accellabel.js index 8696eaeb7..6794da813 100644 --- a/ddterm/app/accellabel.js +++ b/ddterm/app/accellabel.js @@ -29,7 +29,7 @@ class DDTermAccelLabel extends Gtk.Label { #realize() { this.#hierarchy_handler = - this.connect('hierarchy-changed', this.#update_hierarchy.bind(this)); + this.connect('notify::root', this.#update_hierarchy.bind(this)); this.#update_hierarchy(); } @@ -97,7 +97,7 @@ class DDTermAccelLabel extends Gtk.Label { this.#keys_handler = null; } - this.#toplevel = this.get_toplevel(); + this.#toplevel = this.root; if (this.#toplevel instanceof Gtk.Window) { this.#keys_handler = @@ -119,7 +119,10 @@ class DDTermAccelLabel extends Gtk.Label { for (const shortcut of toplevel.application?.get_accels_for_action(action) || []) { try { - return Gtk.accelerator_get_label(...Gtk.accelerator_parse(shortcut)); + const [ok, key, mods] = Gtk.accelerator_parse(shortcut); + + if (ok) + return Gtk.accelerator_get_label(key, mods); } catch (ex) { logError(ex); } diff --git a/ddterm/app/application.js b/ddterm/app/application.js index b7adb32e8..cbf49167f 100644 --- a/ddterm/app/application.js +++ b/ddterm/app/application.js @@ -9,7 +9,7 @@ import GObject from 'gi://GObject'; import Gio from 'gi://Gio'; import Gdk from 'gi://Gdk'; import Gtk from 'gi://Gtk'; -import Handy from 'gi://Handy'; +import Adw from 'gi://Adw'; import Gettext from 'gettext'; import Gi from 'gi'; @@ -78,7 +78,7 @@ export const Application = GObject.registerClass({ ), }, }, -class Application extends Gtk.Application { +class Application extends Adw.Application { _init(params) { super._init(params); @@ -266,9 +266,6 @@ class Application extends Gtk.Application { this.add_action(this.settings.create_action(key)); }); - Handy.init(); - this.style_manager = Handy.StyleManager.get_default(); - this.settings.connect( 'changed::theme-variant', this.update_color_scheme.bind(this) @@ -279,8 +276,8 @@ class Application extends Gtk.Application { const css_provider = Gtk.CssProvider.new(); css_provider.load_from_file(get_resource_file('style.css')); - Gtk.StyleContext.add_provider_for_screen( - Gdk.Screen.get_default(), + Gtk.StyleContext.add_provider_for_display( + Gdk.Display.get_default(), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ); @@ -358,7 +355,8 @@ class Application extends Gtk.Application { this.bind_shortcut(action, key); }); - Gtk.IconTheme.get_default().append_search_path(get_resource_file('icons').get_path()); + const icon_theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()); + icon_theme.add_search_path(get_resource_file('icons').get_path()); this.session_file_path = GLib.build_filenamev([ GLib.get_user_cache_dir(), @@ -579,6 +577,7 @@ class Application extends Gtk.Application { terminal_settings: this.terminal_settings, extension_dbus: this.extension_dbus, display_config: this.display_config, + hide_on_close: true, }); this.window.connect('destroy', source => { @@ -616,7 +615,7 @@ class Application extends Gtk.Application { application: this, }); - this.prefs_dialog.connect('destroy', source => { + this.prefs_dialog.connect('close-request', source => { if (source === this.prefs_dialog) this.prefs_dialog = null; }); @@ -661,9 +660,9 @@ class Application extends Gtk.Application { update_color_scheme() { const mapping = { - 'system': Handy.ColorScheme.PREFER_LIGHT, - 'dark': Handy.ColorScheme.FORCE_DARK, - 'light': Handy.ColorScheme.FORCE_LIGHT, + 'system': Adw.ColorScheme.DEFAULT, + 'dark': Adw.ColorScheme.FORCE_DARK, + 'light': Adw.ColorScheme.FORCE_LIGHT, }; const variant = this.settings.get_string('theme-variant'); diff --git a/ddterm/app/appwindow.js b/ddterm/app/appwindow.js index 69414be12..37f622352 100644 --- a/ddterm/app/appwindow.js +++ b/ddterm/app/appwindow.js @@ -16,33 +16,28 @@ import { get_resource_file } from './resources.js'; import { DisplayConfig, LayoutMode } from '../util/displayconfig.js'; function make_resizer(orientation) { - const box = new Gtk.EventBox({ visible: true }); - - new Gtk.Separator({ + const box = new Gtk.Separator({ visible: true, orientation, - parent: box, margin_top: orientation === Gtk.Orientation.HORIZONTAL ? 2 : 0, margin_bottom: orientation === Gtk.Orientation.HORIZONTAL ? 2 : 0, margin_start: orientation === Gtk.Orientation.VERTICAL ? 2 : 0, margin_end: orientation === Gtk.Orientation.VERTICAL ? 2 : 0, }); - box.connect('realize', () => { - box.window.cursor = Gdk.Cursor.new_from_name( - box.get_display(), - orientation === Gtk.Orientation.VERTICAL ? 'ew-resize' : 'ns-resize' - ); - }); + box.cursor = Gdk.Cursor.new_from_name( + orientation === Gtk.Orientation.VERTICAL ? 'ew-resize' : 'ns-resize', + null + ); return box; } const WINDOW_POS_TO_RESIZE_EDGE = { - top: Gdk.WindowEdge.SOUTH, - bottom: Gdk.WindowEdge.NORTH, - left: Gdk.WindowEdge.EAST, - right: Gdk.WindowEdge.WEST, + top: Gdk.SurfaceEdge.SOUTH, + bottom: Gdk.SurfaceEdge.NORTH, + left: Gdk.SurfaceEdge.EAST, + right: Gdk.SurfaceEdge.WEST, }; export const AppWindow = GObject.registerClass({ @@ -87,8 +82,8 @@ export const AppWindow = GObject.registerClass({ '', '', GObject.ParamFlags.READWRITE | GObject.ParamFlags.EXPLICIT_NOTIFY, - Gdk.WindowEdge, - Gdk.WindowEdge.SOUTH + Gdk.SurfaceEdge, + Gdk.SurfaceEdge.SOUTH ), 'tab-label-width': GObject.ParamSpec.double( 'tab-label-width', @@ -135,36 +130,43 @@ export const AppWindow = GObject.registerClass({ 'no-split' ), }, + Signals: { + 'size-allocate': { + param_types: [GObject.TYPE_INT, GObject.TYPE_INT], + }, + }, }, class DDTermAppWindow extends Gtk.ApplicationWindow { _init(params) { super._init({ title: Gettext.gettext('ddterm'), icon_name: 'utilities-terminal', - window_position: Gtk.WindowPosition.CENTER, ...params, }); this.menus = Gtk.Builder.new_from_file(get_resource_file('./ui/menus.ui').get_path()); - const grid = new Gtk.Grid({ - parent: this, - visible: true, - }); + const grid = new Gtk.Grid({ visible: true }); + + this.set_child(grid); this.paned = new Gtk.Paned({ visible: true, - border_width: 0, hexpand: true, vexpand: true, + shrink_start_child: false, + shrink_end_child: false, }); grid.attach(this.paned, 1, 1, 1, 1); let window_title_binding = null; - this.paned.connect('set-focus-child', (paned, child) => { + this.connect('notify::focus-widget', () => { + if (window_title_binding?.dup_source() === this.active_notebook) + return; + window_title_binding?.unbind(); - window_title_binding = child?.bind_property( + window_title_binding = this.active_notebook?.bind_property( 'current-title', this, 'title', @@ -175,11 +177,11 @@ class DDTermAppWindow extends Gtk.ApplicationWindow { }); const notebook1 = this.create_notebook(); - this.paned.pack1(notebook1, true, false); + this.paned.set_start_child(notebook1); this.paned.set_focus_child(notebook1); const notebook2 = this.create_notebook(); - this.paned.pack2(notebook2, true, false); + this.paned.set_end_child(notebook2); this.paned.connect('notify::orientation', () => this.notify('split-layout')); this.connect('notify::is-split', () => this.notify('split-layout')); @@ -189,7 +191,7 @@ class DDTermAppWindow extends Gtk.ApplicationWindow { this.freeze_notify(); try { - src.remove(child); + src.remove_page(src.page_num(child)); dst.insert_page(child, label, -1); } finally { this.thaw_notify(); @@ -200,7 +202,9 @@ class DDTermAppWindow extends Gtk.ApplicationWindow { notebook2.connect('move-to-other-pane', (_, page) => move_page(page, notebook2, notebook1)); this.connect('notify::tab-label-width', this.update_tab_label_width.bind(this)); - this.connect('configure-event', this.update_tab_label_width.bind(this)); + this.connect('realize', () => { + this.get_surface().connect('notify::width', this.update_tab_label_width.bind(this)); + }); this.update_tab_label_width(); this.settings.bind( @@ -212,7 +216,11 @@ class DDTermAppWindow extends Gtk.ApplicationWindow { const add_resize_box = (edge, x, y, orientation) => { const box = make_resizer(orientation); - box.connect('button-press-event', this.start_resizing.bind(this, edge)); + const gesture = Gtk.GestureClick.new(); + + gesture.set_button(Gdk.BUTTON_PRIMARY); + gesture.connect('pressed', this.start_resizing.bind(this, edge)); + box.add_controller(gesture); grid.attach(box, x, y, 1, 1); const update_visible = () => { @@ -224,10 +232,10 @@ class DDTermAppWindow extends Gtk.ApplicationWindow { update_visible(); }; - add_resize_box(Gdk.WindowEdge.SOUTH, 1, 2, Gtk.Orientation.HORIZONTAL); - add_resize_box(Gdk.WindowEdge.NORTH, 1, 0, Gtk.Orientation.HORIZONTAL); - add_resize_box(Gdk.WindowEdge.EAST, 2, 1, Gtk.Orientation.VERTICAL); - add_resize_box(Gdk.WindowEdge.WEST, 0, 1, Gtk.Orientation.VERTICAL); + add_resize_box(Gdk.SurfaceEdge.SOUTH, 1, 2, Gtk.Orientation.HORIZONTAL); + add_resize_box(Gdk.SurfaceEdge.NORTH, 1, 0, Gtk.Orientation.HORIZONTAL); + add_resize_box(Gdk.SurfaceEdge.EAST, 2, 1, Gtk.Orientation.VERTICAL); + add_resize_box(Gdk.SurfaceEdge.WEST, 0, 1, Gtk.Orientation.VERTICAL); this.settings.bind( 'window-resizable', @@ -242,20 +250,6 @@ class DDTermAppWindow extends Gtk.ApplicationWindow { this.connect('destroy', () => this.settings.disconnect(edge_handler)); this.update_window_pos(); - this.connect('notify::screen', () => this.update_visual()); - this.update_visual(); - - this.draw_handler = null; - this.connect('notify::app-paintable', this.setup_draw_handler.bind(this)); - this.setup_draw_handler(); - - this.settings.bind( - 'transparent-background', - this, - 'app-paintable', - Gio.SettingsBindFlags.GET - ); - const HEIGHT_MOD = 0.05; const OPACITY_MOD = 0.05; @@ -307,20 +301,6 @@ class DDTermAppWindow extends Gtk.ApplicationWindow { this.bind_property('is-split', action, 'enabled', GObject.BindingFlags.SYNC_CREATE); }); - this.settings.bind( - 'window-skip-taskbar', - this, - 'skip-taskbar-hint', - Gio.SettingsBindFlags.GET - ); - - this.settings.bind( - 'window-skip-taskbar', - this, - 'skip-pager-hint', - Gio.SettingsBindFlags.GET - ); - this.settings.bind( 'tab-show-shortcuts', this, @@ -337,18 +317,11 @@ class DDTermAppWindow extends Gtk.ApplicationWindow { this.close(); }); - this._hide_on_close(); - this._setup_size_sync(); - } + this.settings.connect('changed::window-skip-taskbar', this.update_skip_taskbar.bind(this)); + this.connect('realize', this.update_skip_taskbar.bind(this)); + this.update_skip_taskbar(); - _hide_on_close() { - this.connect('delete-event', () => { - if (this.is_empty) - return false; - - this.hide(); - return true; - }); + // this._setup_size_sync(); } _setup_size_sync() { @@ -374,19 +347,31 @@ class DDTermAppWindow extends Gtk.ApplicationWindow { this.connect('destroy', () => this.extension_dbus.disconnect(dbus_handler)); - this.connect('unmap-event', () => { + this.connect('unmap', () => { this.sync_size_with_extension(); }); this.sync_size_with_extension(); } + vfunc_size_allocate(width, height, baseline) { + super.vfunc_size_allocate(width, height, baseline); + this.emit('size-allocate', width, height); + } + + vfunc_map() { + this.maximized = this.settings.get_boolean('window-maximize'); + + super.vfunc_map(); + } + create_notebook() { const notebook = new Notebook({ terminal_settings: this.terminal_settings, scrollable: true, group_name: 'ddtermnotebook', menus: this.menus, + visible: false, }); const update_notebook_visibility = () => { @@ -506,18 +491,6 @@ class DDTermAppWindow extends Gtk.ApplicationWindow { return notebook; } - setup_draw_handler() { - if (this.app_paintable) { - if (!this.draw_handler) - this.draw_handler = this.connect('draw', this.draw.bind(this)); - } else if (this.draw_handler) { - this.disconnect(this.draw_handler); - this.draw_handler = null; - } - - this.queue_draw(); - } - adjust_double_setting(name, difference, min = 0.0, max = 1.0) { const current = this.settings.get_double(name); const new_setting = current + difference; @@ -531,16 +504,16 @@ class DDTermAppWindow extends Gtk.ApplicationWindow { this.present(); } - start_resizing(edge, source, event) { - const [button_ok, button] = event.get_button(); - if (!button_ok || button !== Gdk.BUTTON_PRIMARY) - return; - - const [coords_ok, x_root, y_root] = event.get_root_coords(); + start_resizing(edge, gesture) { + const event = gesture.get_current_event(); + const button = event.get_button?.() ?? 0; + const [coords_ok, x_root, y_root] = event.get_position(); if (!coords_ok) return; - this.window.begin_resize_drag_for_device( + gesture.set_state(Gtk.EventSequenceState.CLAIMED); + + this.get_surface().begin_resize( edge, event.get_device(), button, @@ -548,36 +521,8 @@ class DDTermAppWindow extends Gtk.ApplicationWindow { y_root, event.get_time() ); - } - - update_visual() { - const visual = this.screen.get_rgba_visual(); - - if (visual) - this.set_visual(visual); - } - - draw(_widget, cr) { - try { - if (!this.app_paintable) - return false; - - if (!Gtk.cairo_should_draw_window(cr, this.window)) - return false; - - const context = this.get_style_context(); - const allocation = this.get_child().get_allocation(); - Gtk.render_background( - context, cr, allocation.x, allocation.y, allocation.width, allocation.height - ); - Gtk.render_frame( - context, cr, allocation.x, allocation.y, allocation.width, allocation.height - ); - } finally { - cr.$dispose(); - } - return false; + gesture.reset(); } sync_size_with_extension() { @@ -601,17 +546,15 @@ class DDTermAppWindow extends Gtk.ApplicationWindow { target_h = Math.floor(target_h / scale); } - this.resize(target_w, target_h); - this.window?.resize(target_w, target_h); + this.set_default_size(target_w, target_h); } update_tab_label_width() { - const [width] = this.get_size(); - const tab_label_width = Math.floor(this.tab_label_width * width); + const tab_label_width = Math.floor(this.tab_label_width * this.get_width()); + const { start_child, end_child } = this.paned; - this.paned.foreach(child => { - child.tab_label_width = tab_label_width; - }); + start_child.tab_label_width = tab_label_width; + end_child.tab_label_width = tab_label_width; } get active_notebook() { @@ -619,11 +562,15 @@ class DDTermAppWindow extends Gtk.ApplicationWindow { } get is_empty() { - return this.paned.get_children().every(nb => !nb.get_visible()); + const { start_child, end_child } = this.paned; + + return !start_child?.get_visible() && !end_child?.get_visible(); } get is_split() { - return this.paned.get_children().every(nb => nb.get_visible()); + const { start_child, end_child } = this.paned; + + return start_child?.get_visible() && end_child?.get_visible(); } get split_layout() { @@ -640,17 +587,18 @@ class DDTermAppWindow extends Gtk.ApplicationWindow { if (!this.is_split) return; - const dst = this.paned.get_child1(); - const src = this.paned.get_child2(); + const dst = this.paned.start_child; + const src = this.paned.end_child; const current_page = this.active_notebook?.current_child; this.freeze_notify(); try { - for (const child of src.get_children()) { + while (src.get_n_pages()) { + const child = src.get_nth_page(0); const label = src.get_tab_label(child); - src.remove(child); + src.remove_page(0); dst.insert_page(child, label, -1); } @@ -668,9 +616,25 @@ class DDTermAppWindow extends Gtk.ApplicationWindow { } update_show_shortcuts() { - this.paned.foreach(child => { - child.tab_show_shortcuts = this.tab_show_shortcuts && child === this.active_notebook; - }); + const { start_child, end_child } = this.paned; + + start_child.tab_show_shortcuts = + this.tab_show_shortcuts && start_child === this.active_notebook; + + end_child.tab_show_shortcuts = + this.tab_show_shortcuts && end_child === this.active_notebook; + } + + update_skip_taskbar() { + const surface = this.get_surface(); + + if (surface?.constructor.$gtype.name !== 'GdkX11Toplevel') + return; + + const skip = this.settings.get_boolean('window-skip-taskbar'); + + surface.set_skip_taskbar_hint(skip); + surface.set_skip_pager_hint(skip); } vfunc_grab_focus() { @@ -679,12 +643,10 @@ class DDTermAppWindow extends Gtk.ApplicationWindow { return; } - for (const notebook of this.paned.get_children()) { - if (notebook.get_visible()) { - notebook.grab_focus(); - return; - } - } + if (this.paned.start_child?.get_visible()) + this.paned.start_child.grab_focus(); + else if (this.paned.end_child?.get_visible()) + this.paned.end_child.grab_focus(); } serialize_state() { @@ -708,8 +670,8 @@ class DDTermAppWindow extends Gtk.ApplicationWindow { ); } - properties.insert_value('notebook1', this.paned.get_child1().serialize_state()); - properties.insert_value('notebook2', this.paned.get_child2().serialize_state()); + properties.insert_value('notebook1', this.paned.start_child.serialize_state()); + properties.insert_value('notebook2', this.paned.end_child.serialize_state()); return properties.end(); } @@ -726,10 +688,10 @@ class DDTermAppWindow extends Gtk.ApplicationWindow { this.paned.orientation = orientation; if (notebook1_data) - this.paned.get_child1().deserialize_state(notebook1_data); + this.paned.start_child.deserialize_state(notebook1_data); if (notebook2_data) - this.paned.get_child2().deserialize_state(notebook2_data); + this.paned.end_child.deserialize_state(notebook2_data); if (position !== null) this.paned.position = (this.paned.max_position - this.paned.min_position) * position; diff --git a/ddterm/app/dependencies.json b/ddterm/app/dependencies.json index 4ef201544..4e0eaf897 100644 --- a/ddterm/app/dependencies.json +++ b/ddterm/app/dependencies.json @@ -1,32 +1,32 @@ { "Gtk": { - "3.0": { - "arch": "gtk3", - "debian": "gir1.2-gtk-3.0", - "fedora": "gtk3", - "filename": "Gtk-3.0.typelib", - "suse": "typelib-1_0-Gtk-3_0", - "alpine": "gtk+3.0" + "4.0": { + "filename": "Gtk-4.0.typelib", + "arch": "gtk4", + "alpine": "gtk4.0", + "debian": "gir1.2-gtk-4.0", + "fedora": "gtk4", + "suse": "typelib-1_0-Gtk-4_0" } }, "Gdk": { - "3.0": { - "arch": "gtk3", - "debian": "gir1.2-gtk-3.0", - "fedora": "gtk3", - "filename": "Gdk-3.0.typelib", - "suse": "typelib-1_0-Gtk-3_0", - "alpine": "gtk+3.0" + "4.0": { + "filename": "Gdk-4.0.typelib", + "arch": "gtk4", + "alpine": "gtk4.0", + "debian": "gir1.2-gtk-4.0", + "fedora": "gtk4", + "suse": "typelib-1_0-Gtk-4_0" } }, "Vte": { - "2.91": { - "arch": "vte3", - "debian": "gir1.2-vte-2.91", - "fedora": "vte291", - "filename": "Vte-2.91.typelib", - "suse": "typelib-1_0-Vte-2.91", - "alpine": "vte3" + "3.91": { + "filename": "Vte-3.91.typelib", + "arch": "vte4", + "alpine": "vte3-gtk4", + "debian": "gir1.2-vte-3.91", + "fedora": "vte291-gtk4", + "suse": "typelib-1_0-Vte-3_91" } }, "Pango": { @@ -39,14 +39,14 @@ "alpine": "pango" } }, - "Handy": { + "Adw": { "1": { - "filename": "Handy-1.typelib", - "arch": "libhandy", - "alpine": "libhandy1", - "suse": "typelib-1_0-Handy-1_0", - "fedora": "libhandy", - "debian": "gir1.2-handy-1" + "filename": "Adw-1.typelib", + "arch": "libadwaita", + "alpine": "libadwaita", + "debian": "gir1.2-adw-1", + "fedora": "libadwaita", + "suse": "typelib-1_0-Adw-1" } } -} \ No newline at end of file +} diff --git a/ddterm/app/dev-appwindow.js b/ddterm/app/dev-appwindow.js index a435a297e..98ce52362 100644 --- a/ddterm/app/dev-appwindow.js +++ b/ddterm/app/dev-appwindow.js @@ -8,9 +8,6 @@ import { AppWindow as BaseAppWindow } from './appwindow.js'; export const AppWindow = GObject.registerClass({ }, class DDTermDevAppWindow extends BaseAppWindow { - _hide_on_close() { - } - _setup_size_sync() { } }); diff --git a/ddterm/app/init.js b/ddterm/app/init.js index 61a0c9f32..bbad1bec1 100644 --- a/ddterm/app/init.js +++ b/ddterm/app/init.js @@ -4,6 +4,7 @@ import GObject from 'gi://GObject'; +import Gi from 'gi'; import Gettext from 'gettext'; import { setConsoleLogDomain } from 'console'; @@ -16,13 +17,19 @@ Gettext.bindtextdomain(metadata['gettext-domain'], dir.get_child('locale').get_p Gettext.textdomain(metadata['gettext-domain']); gi_require({ - 'Gtk': '3.0', - 'Gdk': '3.0', + 'Gtk': '4.0', + 'Gdk': '4.0', 'Pango': '1.0', - 'Vte': '2.91', - 'Handy': '1', + 'Vte': '3.91', + 'Adw': '1', }); +try { + Gi.require('GdkX11', '4.0'); +} catch (ex) { + logError(ex); +} + GObject.Object.prototype.disconnect = function (id) { if (GObject.signal_handler_is_connected(this, id)) GObject.signal_handler_disconnect(this, id); diff --git a/ddterm/app/notebook.js b/ddterm/app/notebook.js index cbfbc0b1a..e54f9ce2c 100644 --- a/ddterm/app/notebook.js +++ b/ddterm/app/notebook.js @@ -132,13 +132,13 @@ export const Notebook = GObject.registerClass({ const button_box = new Gtk.Box({ visible: true }); this.new_tab_button = new Gtk.Button({ - image: Gtk.Image.new_from_icon_name('list-add', Gtk.IconSize.MENU), + icon_name: 'list-add', tooltip_text: Gettext.gettext('New Tab (Last)'), action_name: 'notebook.new-tab', - relief: Gtk.ReliefStyle.NONE, + has_frame: false, visible: true, }); - button_box.add(this.new_tab_button); + button_box.append(this.new_tab_button); this.bind_property( 'show-new-tab-button', @@ -154,11 +154,10 @@ export const Notebook = GObject.registerClass({ this.tab_switch_button = new Gtk.MenuButton({ menu_model: menu, focus_on_click: false, - relief: Gtk.ReliefStyle.NONE, + has_frame: false, visible: true, - use_popover: false, }); - button_box.add(this.tab_switch_button); + button_box.append(this.tab_switch_button); this.bind_property( 'show-tab-switch-popup', @@ -170,10 +169,10 @@ export const Notebook = GObject.registerClass({ this.set_action_widget(button_box, Gtk.PackType.END); this.new_tab_front_button = new Gtk.Button({ - image: Gtk.Image.new_from_icon_name('list-add', Gtk.IconSize.MENU), + icon_name: 'list-add', tooltip_text: Gettext.gettext('New Tab (First)'), action_name: 'notebook.new-tab-front', - relief: Gtk.ReliefStyle.NONE, + has_frame: false, visible: true, }); this.set_action_widget(this.new_tab_front_button, Gtk.PackType.START); @@ -255,9 +254,6 @@ export const Notebook = GObject.registerClass({ this.connect('notify::tab-pos', this.update_tab_pos.bind(this)); this.update_tab_pos(); - this.connect('notify::tab-expand', this.update_tab_expand.bind(this)); - this.update_tab_expand(); - this._current_child = null; this.connect('switch-page', (notebook, page) => { @@ -293,7 +289,6 @@ export const Notebook = GObject.registerClass({ on_page_added(child, page_num) { this.set_tab_reorderable(child, true); this.set_tab_detachable(child, true); - this.child_set_property(child, 'tab-expand', this.tab_expand); const handlers = [ child.connect('new-tab-before-request', () => { @@ -320,9 +315,13 @@ export const Notebook = GObject.registerClass({ child.connect('move-to-other-pane-request', () => { this.emit('move-to-other-pane', child); }), + child.connect('close', () => { + this.remove_page(this.page_num(child)); + }), ]; - const label = this.get_tab_label(child); + const label = child.tab_label; + const page = this.get_page(child); const bindings = [ this.bind_property( @@ -355,6 +354,12 @@ export const Notebook = GObject.registerClass({ 'split-layout', GObject.BindingFlags.SYNC_CREATE ), + this.bind_property( + 'tab-expand', + page, + 'tab-expand', + GObject.BindingFlags.SYNC_CREATE + ), ]; this.page_disconnect.set(child, () => { @@ -398,7 +403,9 @@ export const Notebook = GObject.registerClass({ command: properties['command'] ?? this.get_command_from_settings(), }); - this.insert_page(page, page.tab_label, position); + this.insert_page(page, null, position); + this.set_tab_label(page, page.tab_label); + return page; } @@ -410,20 +417,14 @@ export const Notebook = GObject.registerClass({ } update_tab_switch_actions() { - let i = 0; + const n_pages = this.get_n_pages(); - this.foreach(child => { - const label = this.get_tab_label(child); + for (let i = 0; i < n_pages; i++) { + const label = this.get_nth_page(i).tab_label; - label.action_target = GLib.Variant.new_int32(i++); + label.action_target = GLib.Variant.new_int32(i); label.action_name = 'notebook.switch-to-tab'; - }); - } - - update_tab_expand() { - this.foreach(page => { - this.child_set_property(page, 'tab-expand', this.tab_expand); - }); + } } update_tabs_visible() { @@ -477,9 +478,12 @@ export const Notebook = GObject.registerClass({ const properties = GLib.VariantDict.new(null); const variant_dict_type = new GLib.VariantType('a{sv}'); const pages = []; + const n_pages = this.get_n_pages(); - for (const page of this.get_children()) { + for (let i = 0; i < n_pages; i++) { try { + const page = this.get_nth_page(i); + pages.push(page.serialize_state()); } catch (ex) { logError(ex, "Can't serialize terminal state"); @@ -568,11 +572,16 @@ const NotebookMenu = GObject.registerClass({ _update() { const prev_length = this.get_n_items(); + const update = []; + const n_pages = this.notebook.get_n_pages(); + + for (let i = 0; i < n_pages; i++) + update.push(this.notebook.get_nth_page(i).title); - this._label = this.notebook.get_children().map(page => page.title); - this._target.length = this._label.length; + this._label = update; + this._target.length = update.length; - this.items_changed(0, prev_length, this._label.length); + this.items_changed(0, prev_length, update.length); } _schedule_update() { diff --git a/ddterm/app/search.js b/ddterm/app/search.js index 5efaf08df..f66672bd5 100644 --- a/ddterm/app/search.js +++ b/ddterm/app/search.js @@ -199,19 +199,19 @@ class DDTermSearchBar extends Gtk.Revealer { const layout = new Gtk.Box({ visible: true, - border_width: 5, spacing: 5, - parent: this, }); + this.child = layout; + const case_sensitive_button = new Gtk.ToggleButton({ - image: new Gtk.Image({ icon_name: 'uppercase' }), + icon_name: 'uppercase', tooltip_text: Gettext.gettext('Case Sensitive'), visible: true, focus_on_click: false, }); - layout.pack_start(case_sensitive_button, false, false, 0); + layout.append(case_sensitive_button); this.pattern.bind_property( 'case-sensitive', @@ -221,13 +221,13 @@ class DDTermSearchBar extends Gtk.Revealer { ); const whole_word_button = new Gtk.ToggleButton({ - image: new Gtk.Image({ icon_name: 'quotation' }), + icon_name: 'quotation', tooltip_text: Gettext.gettext('Match Whole Word'), visible: true, focus_on_click: false, }); - layout.pack_start(whole_word_button, false, false, 0); + layout.append(whole_word_button); this.pattern.bind_property( 'whole-word', @@ -237,13 +237,13 @@ class DDTermSearchBar extends Gtk.Revealer { ); const regex_button = new Gtk.ToggleButton({ - image: new Gtk.Image({ icon_name: 'regex' }), + icon_name: 'regex', tooltip_text: Gettext.gettext('Regular Expression'), visible: true, focus_on_click: false, }); - layout.pack_start(regex_button, false, false, 0); + layout.append(regex_button); this.pattern.bind_property( 'use-regex', @@ -252,118 +252,143 @@ class DDTermSearchBar extends Gtk.Revealer { GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE ); - const entry = new Gtk.SearchEntry({ + this.entry = new Gtk.SearchEntry({ visible: true, }); - layout.pack_start(entry, true, true, 0); + layout.append(this.entry); this.pattern.bind_property( 'text', - entry, + this.entry, 'text', GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE ); - const error_popover = new Gtk.Popover({ - relative_to: entry, - modal: false, - border_width: 5, - }); - - const error_label = new Gtk.Label({ - parent: error_popover, + this.error_label = new Gtk.Label({ visible: true, }); - error_label.get_style_context().add_class('error'); + this.error_label.get_style_context().add_class('error'); - this.pattern.connect('notify::error', () => { - if (this.pattern.error) { - entry.get_style_context().add_class('error'); - error_label.label = this.pattern.error.message; - error_popover.popup(); - } else { - entry.get_style_context().remove_class('error'); - error_popover.popdown(); - } + this.error_popover = new Gtk.Popover({ + autohide: false, + visible: false, + child: this.error_label, }); - entry.connect('activate', () => this.find_next()); - entry.connect('next-match', () => this.find_next()); - entry.connect('previous-match', () => this.find_prev()); - entry.connect('stop-search', () => this.close()); - entry.connect('search-changed', () => this.pattern.update()); + this.connect('realize', () => this.error_popover.set_parent(this.entry)); + this.connect('unrealize', () => this.error_popover.unparent()); - this.connect('key-press-event', (_, event) => entry.handle_event(event)); - this.connect('key-release-event', (_, event) => entry.handle_event(event)); - this.connect('notify::reveal-child', () => { - if (this.reveal_child) - entry.grab_focus(); + this.find_next_button = new Gtk.Button({ + icon_name: 'go-down', + tooltip_text: Gettext.gettext('Find Next'), + visible: true, + focus_on_click: false, }); - const close_button = new Gtk.Button({ - image: new Gtk.Image({ icon_name: 'window-close' }), - tooltip_text: Gettext.gettext('Close Search Bar'), + layout.append(this.find_next_button); + + this._pattern.bind_property( + 'regex-set', + this.find_next_button, + 'sensitive', + GObject.BindingFlags.SYNC_CREATE + ); + + this.find_prev_button = new Gtk.Button({ + icon_name: 'go-up', + tooltip_text: Gettext.gettext('Find Previous'), visible: true, focus_on_click: false, }); - layout.pack_end(close_button, false, false, 0); + layout.append(this.find_prev_button); - close_button.connect('clicked', this.close.bind(this)); + this._pattern.bind_property( + 'regex-set', + this.find_prev_button, + 'sensitive', + GObject.BindingFlags.SYNC_CREATE + ); - const wrap_button = new Gtk.ToggleButton({ - image: new Gtk.Image({ icon_name: 'view-wrapped' }), + this.wrap_button = new Gtk.ToggleButton({ + icon_name: 'view-wrapped', tooltip_text: Gettext.gettext('Wrap Around'), visible: true, focus_on_click: false, }); - layout.pack_end(wrap_button, false, false, 0); + layout.append(this.wrap_button); this.bind_property( 'wrap', - wrap_button, + this.wrap_button, 'active', GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE ); - const find_next_button = new Gtk.Button({ - image: new Gtk.Image({ icon_name: 'go-down' }), - tooltip_text: Gettext.gettext('Find Next'), + this.close_button = new Gtk.Button({ + icon_name: 'window-close', + tooltip_text: Gettext.gettext('Close Search Bar'), visible: true, focus_on_click: false, }); - layout.pack_end(find_next_button, false, false, 0); + layout.append(this.close_button); - this._pattern.bind_property( - 'regex-set', - find_next_button, - 'sensitive', - GObject.BindingFlags.SYNC_CREATE - ); + this.connect('notify::reveal-child', () => { + if (this.reveal_child) + this.entry.grab_focus(); + }); - find_next_button.connect('clicked', this.find_next.bind(this)); + this._setup_capture(); + this.connect('realize', this._connect_handlers.bind(this)); + } - const find_prev_button = new Gtk.Button({ - image: new Gtk.Image({ icon_name: 'go-up' }), - tooltip_text: Gettext.gettext('Find Previous'), - visible: true, - focus_on_click: false, - }); + _setup_capture() { + const { entry } = this; + const capture_controller = Gtk.EventControllerKey.new(); - layout.pack_end(find_prev_button, false, false, 0); + capture_controller.set_propagation_phase(Gtk.PropagationPhase.BUBBLE); + capture_controller.connect('key-pressed', () => capture_controller.forward(entry)); + capture_controller.connect('key-released', () => capture_controller.forward(entry)); - this._pattern.bind_property( - 'regex-set', - find_prev_button, - 'sensitive', - GObject.BindingFlags.SYNC_CREATE - ); + this.add_controller(capture_controller); + } + + _connect_handlers() { + const error_handler = + this.pattern.connect('notify::error', this.show_error.bind(this)); + + const entry_handlers = [ + this.entry.connect('activate', this.find_next.bind(this)), + this.entry.connect('next-match', this.find_next.bind(this)), + this.entry.connect('previous-match', this.find_prev.bind(this)), + this.entry.connect('stop-search', this.close.bind(this)), + this.entry.connect('search-changed', () => this.pattern.update()), + ]; + + const find_next_handler = + this.find_next_button.connect('clicked', this.find_next.bind(this)); + + const find_prev_handler = + this.find_prev_button.connect('clicked', this.find_prev.bind(this)); + + const close_handler = + this.close_button.connect('clicked', this.close.bind(this)); - find_prev_button.connect('clicked', this.find_prev.bind(this)); + const unrealize_handler = this.connect('unrealize', () => { + this.disconnect(unrealize_handler); + this.pattern.disconnect(error_handler); + + for (const handler of entry_handlers) + this.entry.disconnect(handler); + + this.find_next_button.disconnect(find_next_handler); + this.find_prev_button.disconnect(find_prev_handler); + this.close_button.disconnect(close_handler); + }); } get pattern() { @@ -383,4 +408,15 @@ class DDTermSearchBar extends Gtk.Revealer { this.pattern.update(); this.emit('find-prev'); } + + show_error() { + if (this.pattern.error) { + this.entry.get_style_context().add_class('error'); + this.error_label.label = this.pattern.error.message; + this.error_popover.popup(); + } else { + this.entry.get_style_context().remove_class('error'); + this.error_popover.popdown(); + } + } }); diff --git a/ddterm/app/tablabel.js b/ddterm/app/tablabel.js index ad451ae2c..ba35ed6cc 100644 --- a/ddterm/app/tablabel.js +++ b/ddterm/app/tablabel.js @@ -58,24 +58,17 @@ export const TabLabel = GObject.registerClass({ 'close': {}, 'reset-label': {}, }, -}, class DDTermTabLabel extends Gtk.EventBox { +}, class DDTermTabLabel extends Gtk.Box { _init(params) { - super._init(params); + super._init({ spacing: 10, ...params }); - this.connect_after('button-press-event', this._button_press_event.bind(this)); - this.connect('popup-menu', this._popup_menu.bind(this)); - - const layout = new Gtk.Box({ - visible: true, - spacing: 10, - parent: this, - }); + this._setup_popup_menu(); this.shortcut_label = new AccelLabel({ visible: true, }); - layout.pack_start(this.shortcut_label, false, false, 0); + this.append(this.shortcut_label); this.bind_property( 'show-shortcut', @@ -90,7 +83,7 @@ export const TabLabel = GObject.registerClass({ visible: true, }); - layout.pack_start(label, true, true, 0); + this.append(label); this.bind_property( 'label', @@ -106,19 +99,19 @@ export const TabLabel = GObject.registerClass({ GObject.BindingFlags.SYNC_CREATE ); + this.append(this._create_close_button()); + this.edit_popover = this._create_edit_popover(); + } + + _create_close_button() { const close_button = new Gtk.Button({ tooltip_text: Gettext.gettext('Close'), - image: new Gtk.Image({ - icon_name: 'window-close', - visible: true, - }), + icon_name: 'window-close', visible: true, focus_on_click: false, - relief: Gtk.ReliefStyle.NONE, + has_frame: false, }); - layout.pack_end(close_button, false, false, 0); - this.bind_property( 'close-button', close_button, @@ -126,27 +119,24 @@ export const TabLabel = GObject.registerClass({ GObject.BindingFlags.SYNC_CREATE ); - close_button.connect('clicked', () => this.emit('close')); - - this.edit_popover = new Gtk.Popover({ - relative_to: this, + this.connect('realize', () => { + const click_handler = close_button.connect('clicked', () => this.emit('close')); + const unrealize_handler = this.connect('unrealize', () => { + this.disconnect(unrealize_handler); + close_button.disconnect(click_handler); + }); }); - this.connect('destroy', () => this.edit_popover.destroy()); + return close_button; + } + _create_edit_popover() { const edit_entry = new Gtk.Entry({ visible: true, - parent: this.edit_popover, secondary_icon_name: 'edit-clear', secondary_icon_activatable: true, secondary_icon_sensitive: true, - }); - - edit_entry.connect('activate', () => this.edit_popover.popdown()); - - edit_entry.connect('icon-press', () => { - this.edit_popover.popdown(); - this.emit('reset-label'); + width_chars: 50, }); this.bind_property( @@ -156,8 +146,69 @@ export const TabLabel = GObject.registerClass({ GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL ); - this.connect('size-allocate', (_, allocation) => { - edit_entry.width_request = allocation.width; + const edit_popover = new Gtk.Popover({ + visible: false, + child: edit_entry, + autohide: true, + }); + + this.connect('realize', () => { + const activate_handler = edit_entry.connect('activate', () => { + edit_popover.popdown(); + }); + + const icon_handler = edit_entry.connect('icon-press', () => { + edit_popover.popdown(); + this.emit('reset-label'); + }); + + edit_popover.set_parent(this); + + const unrealize_handler = this.connect('unrealize', () => { + this.disconnect(unrealize_handler); + edit_entry.disconnect(activate_handler); + edit_entry.disconnect(icon_handler); + edit_popover.unparent(); + }); + }); + + return edit_popover; + } + + _setup_popup_menu() { + const gesture = Gtk.GestureClick.new(); + this.add_controller(gesture); + gesture.button = 0; + gesture.exclusive = true; + + this.connect('realize', () => { + // eslint-disable-next-line no-shadow + const handler = gesture.connect('pressed', (gesture, n_press, x, y) => { + const event = gesture.get_current_event(); + + if (event.triggers_context_menu()) { + gesture.set_state(Gtk.EventSequenceState.CLAIMED); + this.show_popup_menu(x, y, event.get_pointer_emulated()); + } + }); + + const create_handler = this.connect( + 'notify::context-menu-model', + this._create_context_menu.bind(this) + ); + + const unrealize_handler = this.connect('unrealize', () => { + this.disconnect(unrealize_handler); + this.disconnect(create_handler); + gesture.disconnect(handler); + }); + + this._create_context_menu(); + }); + + this.connect('unrealize', () => { + this._context_menu?.unparent(); + this._context_menu = null; }); } @@ -197,28 +248,33 @@ export const TabLabel = GObject.registerClass({ this.shortcut_label.set_action_target_value(value); } - _button_press_event(terminal, event) { - if (!event.triggers_context_menu()) - return false; + _create_context_menu() { + this._context_menu?.unparent(); - const menu = Gtk.Menu.new_from_model(this.context_menu_model); + if (!this.context_menu_model) { + this._context_menu = null; + return; + } - menu.__heapgraph_name = 'DDTermTabLabelContextMenu'; - menu.attach_to_widget(this, (widget, m) => m.destroy()); - menu.connect('selection-done', m => m.detach()); - menu.popup_at_pointer(event); - - return true; + this._context_menu = Gtk.PopoverMenu.new_from_model(this.context_menu_model); + this._context_menu.__heapgraph_name = 'DDTermTabLabelContextMenu'; + this._context_menu.set_parent(this); } - _popup_menu() { - const menu = Gtk.Menu.new_from_model(this.context_menu_model); - - menu.__heapgraph_name = 'DDTermTabLabelContextMenu'; - menu.attach_to_widget(this, (widget, m) => m.destroy()); - menu.connect('selection-done', m => m.detach()); - menu.popup_at_widget(this, Gdk.Gravity.SOUTH, Gdk.Gravity.SOUTH, null); - - return true; + show_popup_menu(x, y, is_touch = false) { + if (is_touch) + this._context_menu.halign = Gtk.Align.FILL; + else if (this.get_direction() === Gtk.TextDirection.RTL) + this._context_menu.halign = Gtk.Align.END; + else + this._context_menu.halign = Gtk.Align.START; + + this._context_menu.autohide = true; + this._context_menu.cascade_popdown = true; + this._context_menu.has_arrow = is_touch; + this._context_menu.position = is_touch ? Gtk.PositionType.TOP : Gtk.PositionType.BOTTOM; + this._context_menu.pointing_to = new Gdk.Rectangle({ x, y, width: 0, height: 0 }); + + this._context_menu.popup(); } }); diff --git a/ddterm/app/terminal.js b/ddterm/app/terminal.js index c7d73231a..f6cc8dfbd 100644 --- a/ddterm/app/terminal.js +++ b/ddterm/app/terminal.js @@ -339,31 +339,100 @@ const TerminalBase = GObject.registerClass({ GObject.BindingFlags.DEFAULT ); - this.connect( - 'button-press-event', - this._update_clicked_hyperlink.bind(this) - ); + const click_controller = Gtk.GestureClick.new(); + click_controller.propagation_phase = Gtk.PropagationPhase.CAPTURE; + click_controller.button = 0; + this.add_controller(click_controller); + + this.connect('realize', () => { + const pressed_handler = + click_controller.connect('pressed', this._update_clicked_hyperlink.bind(this)); + + const released_handler = + click_controller.connect('released', this._update_clicked_hyperlink.bind(this)); + + const parent_handler = this.connect('notify::parent', () => { + this.update_style(); + }); + + const root_handler = this.connect('notify::root', () => { + this.update_style(); + }); + + const unrealize_handler = this.connect('unrealize', () => { + this.disconnect(unrealize_handler); + this.disconnect(parent_handler); + this.disconnect(root_handler); + click_controller.disconnect(pressed_handler); + click_controller.disconnect(released_handler); + }); + + this.update_style(); + }); this.connect('notify::background-opacity', () => { if (this._background_from_style) this.set_color_background(null); }); - if (this._background_from_style) - this.set_color_background(null); - this.connect('notify::font-scale', () => { this.notify('can-increase-font-scale'); this.notify('can-decrease-font-scale'); }); + } + + vfunc_css_changed(change) { + super.vfunc_css_changed(change); + + this.update_style(); + + // VTE bug? https://github.com/ddterm/gnome-shell-extension-ddterm/issues/674 + this.set_font(this.get_font()); + } + + vfunc_root() { + super.vfunc_root(); - this._gdk_atom_primary = Gdk.Atom.intern('PRIMARY', true); + this.update_style(); } get child_pid() { return this._child_pid; } + get_style_foreground() { + return this.get_style_context()?.get_color() ?? null; + } + + get_style_background() { + let node = null; + let parent = this; + + while (parent) { + const style = parent.get_style_context(); + + if (style) { + const snapshot = Gtk.Snapshot.new(); + snapshot.render_background(style, 0, 0, 16, 16); + node = snapshot.to_node(); + + if (node && node.get_color) + break; + } + + parent = parent.parent; + } + + let background = node?.get_color?.()?.copy(); + + if (!background) + return null; + + background.alpha = this.background_opacity; + + return background; + } + set colors(value) { this.set_colors(value.foreground, value.background, value.palette); } @@ -372,14 +441,11 @@ const TerminalBase = GObject.registerClass({ this._foreground_from_style = foreground === null; this._background_from_style = background === null; - if (this._foreground_from_style || this._background_from_style) { - const style = this.get_style_context(); - const state = style.get_state(); + if (this._foreground_from_style) + foreground = this.get_style_foreground(); - foreground = style.get_property('color', state); - background = style.get_property('background-color', state).copy(); - background.alpha = this.background_opacity; - } + if (this._background_from_style) + background = this.get_style_background(); super.set_colors(foreground, background, palette); } @@ -391,14 +457,11 @@ const TerminalBase = GObject.registerClass({ set_color_foreground(value) { this._foreground_from_style = value === null; - if (this._foreground_from_style) { - const style = this.get_style_context(); - const state = style.get_state(); - - value = style.get_property('color', state); - } + if (this._foreground_from_style) + value = this.get_style_foreground(); - super.set_color_foreground(value); + if (value) + super.set_color_foreground(value); } set color_background(value) { @@ -408,34 +471,26 @@ const TerminalBase = GObject.registerClass({ set_color_background(value) { this._background_from_style = value === null; - if (this._background_from_style) { - const style = this.get_style_context(); - const state = style.get_state(); - - value = style.get_property('background-color', state).copy(); - value.alpha = this.background_opacity; - } + if (this._background_from_style) + value = this.get_style_background(); - super.set_color_background(value); + if (value) + super.set_color_background(value); } - on_style_updated() { - // VTE bug? https://github.com/ddterm/gnome-shell-extension-ddterm/issues/674 - this.set_font(this.get_font()); - - if (!this._foreground_from_style && !this._background_from_style) - return; - - const style = this.get_style_context(); - const state = style.get_state(); + update_style() { + if (this._foreground_from_style) { + const value = this.get_style_foreground(); - if (this._foreground_from_style) - super.set_color_foreground(style.get_property('color', state)); + if (value) + super.set_color_foreground(value); + } if (this._background_from_style) { - const value = style.get_property('background-color', state); - value.alpha = this.background_opacity; - super.set_color_background(value); + const value = this.get_style_background(); + + if (value) + super.set_color_background(value); } } @@ -582,11 +637,11 @@ const TerminalBase = GObject.registerClass({ return this._clicked_filename; } - _update_clicked_hyperlink(terminal_, event) { - let clicked_hyperlink = this.hyperlink_check_event(event); + _update_clicked_hyperlink(gesture, n_press, x, y) { + let clicked_hyperlink = this.check_hyperlink_at(x, y); if (!clicked_hyperlink && this._url_detect) - clicked_hyperlink = this._url_detect.check_event(event); + clicked_hyperlink = this._url_detect.check_match_at(x, y); let clicked_filename = null; @@ -614,23 +669,6 @@ const TerminalBase = GObject.registerClass({ } } - get_text_selected_async() { - if (this.get_text_selected) - return Promise.resolve(this.get_text_selected(Vte.Format.TEXT)); - - if (!this.get_has_selection()) - return Promise.resolve(''); - - const primary_selection = this.get_clipboard(this._gdk_atom_primary); - this.copy_primary(); - - return new Promise(resolve => { - primary_selection.request_text((_, text) => { - resolve(text); - }); - }); - } - get_text() { if (this.get_text_format) return this.get_text_format(Vte.Format.TEXT); @@ -667,55 +705,99 @@ const TerminalContextMenu = HAS_CONTEXT_MENU ? null : GObject.registerClass({ _init(params) { super._init(params); - this.connect('button-press-event', this._button_press_early.bind(this)); - this.connect_after('button-press-event', this._button_press_late.bind(this)); - this.connect('popup-menu', this._popup_menu.bind(this)); - } + const menu_click_early = Gtk.GestureClick.new(); + this.add_controller(menu_click_early); + menu_click_early.button = 0; + menu_click_early.exclusive = true; + menu_click_early.propagation_phase = Gtk.PropagationPhase.CAPTURE; - _create_context_menu() { - let menu = Gtk.Menu.new_from_model(this.context_menu_model); + const menu_click_late = Gtk.GestureClick.new(); + this.add_controller(menu_click_late); + menu_click_late.button = 0; + menu_click_late.exclusive = true; + menu_click_late.propagation_phase = Gtk.PropagationPhase.TARGET; - menu.__heapgraph_name = 'DDTermTerminalContextMenuContextMenu'; - // https://github.com/ddterm/gnome-shell-extension-ddterm/issues/116 - menu.get_style_context().add_class(Gtk.STYLE_CLASS_CONTEXT_MENU); - menu.attach_to_widget(this, (widget, m) => m.destroy()); - menu.connect('selection-done', m => m.detach()); + this.connect('realize', () => { + const early_handler = + menu_click_early.connect('pressed', (gesture, n_press, x, y) => { + const event = gesture.get_current_event(); - return menu; - } + if (!event.triggers_context_menu()) + return; - _button_press_early(terminal, event) { - if (!event.triggers_context_menu()) - return false; + const state = event.get_modifier_state(); - const state = event.get_state()[1]; + if (!(state & Gdk.ModifierType.SHIFT_MASK)) + return; - if (!(state & Gdk.ModifierType.SHIFT_MASK)) - return false; + if (state & (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.MOD1_MASK)) + return; - if (state & (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.MOD1_MASK)) - return false; + gesture.set_state(Gtk.EventSequenceState.CLAIMED); + this.show_popup_menu(x, y, event.get_pointer_emulated()); + }); + + const late_handler = + menu_click_late.connect('pressed', (gesture, n_press, x, y) => { + const event = gesture.get_current_event(); - this._create_context_menu().popup_at_pointer(event); + if (!event.triggers_context_menu()) + return; - return true; + gesture.set_state(Gtk.EventSequenceState.CLAIMED); + this.show_popup_menu(x, y, event.get_pointer_emulated()); + }); + + const create_handler = this.connect( + 'notify::context-menu-model', + this._create_context_menu.bind(this) + ); + + const unrealize_handler = this.connect('unrealize', () => { + this.disconnect(unrealize_handler); + this.disconnect(create_handler); + menu_click_early.disconnect(early_handler); + menu_click_late.disconnect(late_handler); + }); + + this._create_context_menu(); + }); + + this.connect('unrealize', () => { + this._context_menu?.unparent(); + this._context_menu = null; + }); } - _button_press_late(terminal, event) { - if (!event.triggers_context_menu()) - return false; + _create_context_menu() { + this._context_menu?.unparent(); - this._create_context_menu().popup_at_pointer(event); + if (!this.context_menu_model) { + this._context_menu = null; + return; + } - return true; + this._context_menu = Gtk.PopoverMenu.new_from_model(this.context_menu_model); + this._context_menu.__heapgraph_name = 'DDTermTerminalContextMenuContextMenu'; + this._context_menu.get_style_context().add_class('context-menu'); + this._context_menu.set_parent(this); } - _popup_menu() { - const menu = this._create_context_menu(); + show_popup_menu(x, y, is_touch = false) { + if (is_touch) + this._context_menu.halign = Gtk.Align.FILL; + else if (this.get_direction() === Gtk.TextDirection.RTL) + this._context_menu.halign = Gtk.Align.END; + else + this._context_menu.halign = Gtk.Align.START; - menu.popup_at_widget(this, Gdk.Gravity.SOUTH, Gdk.Gravity.SOUTH, null); + this._context_menu.autohide = true; + this._context_menu.cascade_popdown = true; + this._context_menu.has_arrow = is_touch; + this._context_menu.position = is_touch ? Gtk.PositionType.TOP : Gtk.PositionType.BOTTOM; + this._context_menu.pointing_to = new Gdk.Rectangle({ x, y, width: 0, height: 0 }); - return true; + this._context_menu.popup(); } }); diff --git a/ddterm/app/terminalpage.js b/ddterm/app/terminalpage.js index 4fa3f7ddb..79110ea1e 100644 --- a/ddterm/app/terminalpage.js +++ b/ddterm/app/terminalpage.js @@ -77,6 +77,7 @@ export const TerminalPage = GObject.registerClass({ ), }, Signals: { + 'close': {}, 'new-tab-before-request': {}, 'new-tab-after-request': {}, 'move-prev-request': {}, @@ -90,87 +91,142 @@ export const TerminalPage = GObject.registerClass({ _init(params) { super._init(params); - const terminal_with_scrollbar = new Gtk.Box({ - visible: true, - orientation: Gtk.Orientation.HORIZONTAL, - }); + this.orientation = Gtk.Orientation.VERTICAL; this.terminal = new Terminal({ visible: true, context_menu_model: this.terminal_menu, + vexpand: true, }); - terminal_with_scrollbar.pack_start(this.terminal, true, true, 0); - this.terminal_settings.bind_terminal(this.terminal); - this.scrollbar = new Gtk.Scrollbar({ - orientation: Gtk.Orientation.VERTICAL, - adjustment: this.terminal.vadjustment, - visible: true, + this.scrolled_window = new Gtk.ScrolledWindow({ + child: this.terminal, + hscrollbar_policy: Gtk.PolicyType.NEVER, + vscrollbar_policy: Gtk.PolicyType.NEVER, }); - terminal_with_scrollbar.pack_end(this.scrollbar, false, false, 0); + this.append(this.scrolled_window); - this.orientation = Gtk.Orientation.VERTICAL; + this.tab_label = this._setup_tab_label(); + this.search_bar = this._setup_search_bar(); + this.append(this.search_bar); - this.search_bar = new SearchBar({ + this.terminal_settings.bind_property_full( + 'show-scrollbar', + this.scrolled_window, + 'vscrollbar-policy', + GObject.BindingFlags.SYNC_CREATE, + (binding, value) => [true, value ? Gtk.PolicyType.ALWAYS : Gtk.PolicyType.NEVER], + null + ); + + this._setup_click_controller(); + + this.connect('realize', this._setup_page_actions.bind(this)); + this.connect('realize', this._setup_terminal_actions.bind(this)); + this.connect('realize', this._setup_child_handler.bind(this)); + } + + _setup_search_bar() { + const search_bar = new SearchBar({ visible: true, }); - this.pack_end(this.search_bar, false, false, 0); - this.pack_end(terminal_with_scrollbar, true, true, 0); + this.connect('realize', () => { + const { terminal } = this; - this.search_bar.connect('find-next', this.find_next.bind(this)); - this.search_bar.connect('find-prev', this.find_prev.bind(this)); + const handlers = [ + search_bar.connect('find-next', this.find_next.bind(this)), + search_bar.connect('find-prev', this.find_prev.bind(this)), + // eslint-disable-next-line no-shadow + search_bar.connect('notify::wrap', search_bar => { + terminal.search_set_wrap_around(search_bar.wrap); + }), + // eslint-disable-next-line no-shadow + search_bar.connect('notify::reveal-child', search_bar => { + if (!search_bar.reveal_child) + terminal.grab_focus(); + }), + ]; - this.search_bar.connect('notify::wrap', () => { - this.terminal.search_set_wrap_around(this.search_bar.wrap); - }); + terminal.search_set_wrap_around(search_bar.wrap); - this.terminal.search_set_wrap_around(this.search_bar.wrap); + const unrealize = () => { + this.disconnect(unrealize_handler); - this.search_bar.connect('notify::reveal-child', () => { - if (!this.search_bar.reveal_child) - this.terminal.grab_focus(); - }); + for (const handler of handlers) + search_bar.disconnect(handler); + }; - this.tab_label = new TabLabel({ - visible_window: false, - context_menu_model: this.tab_menu, + const unrealize_handler = this.connect('unrealize', unrealize); + + handlers.push(search_bar.connect('unrealize', unrealize)); }); - const tab_label_destroy_handler = - this.connect('destroy', () => this.tab_label.destroy()); + return search_bar; + } - this.tab_label.connect('destroy', () => { - this.disconnect(tab_label_destroy_handler); - }); - this.tab_label.connect('close', () => this.close()); - this.tab_label.connect('reset-label', () => { - this.use_custom_title = false; + _setup_tab_label() { + const tab_label = new TabLabel({ + context_menu_model: this.tab_menu, }); this.bind_property( 'title', - this.tab_label, + tab_label, 'label', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL ); - this.terminal_settings.bind_property( - 'show-scrollbar', - this.scrollbar, - 'visible', - GObject.BindingFlags.SYNC_CREATE - ); + this.connect('realize', () => { + const close_handler = tab_label.connect('close', () => this.close()); + const reset_label_handler = tab_label.connect('reset-label', () => { + this.use_custom_title = false; + }); - this.terminal.connect( - 'button-press-event', - this.terminal_button_press_early.bind(this) - ); + const unrealize = () => { + this.disconnect(unrealize_handler); + tab_label.disconnect(tab_label_unrealize_handler); + tab_label.disconnect(close_handler); + tab_label.disconnect(reset_label_handler); + }; + + const unrealize_handler = this.connect('unrealize', unrealize); + const tab_label_unrealize_handler = tab_label.connect('unrealize', unrealize); + }); + + return tab_label; + } + + _setup_click_controller() { + const early_click = Gtk.GestureClick.new(); + + early_click.propagation_phase = Gtk.PropagationPhase.CAPTURE; + early_click.button = 0; + early_click.exclusive = true; + + this.terminal.add_controller(early_click); + + this.connect('realize', () => { + const click_handler = + early_click.connect('pressed', this.terminal_button_press_early.bind(this)); + + const unrealize = () => { + this.disconnect(unrealize_handler); + early_click.disconnect(click_handler); + this.terminal.disconnect(terminal_unrealize_handler); + }; + const unrealize_handler = this.connect('unrealize', unrealize); + const terminal_unrealize_handler = this.terminal.connect('unrealize', unrealize); + }); + } + + _setup_page_actions() { const page_actions = new Gio.SimpleActionGroup(); + const handlers = []; const close_action = new Gio.SimpleAction({ name: 'close' }); close_action.connect('activate', () => this.close()); @@ -204,9 +260,9 @@ export const TerminalPage = GObject.registerClass({ parameter_type: new GLib.VariantType('s'), state: GLib.Variant.new_string(this.split_layout), }); - this.connect('notify::split-layout', () => { + handlers.push(this.connect('notify::split-layout', () => { split_layout_action.state = GLib.Variant.new_string(this.split_layout); - }); + })); split_layout_action.connect('change-state', (_, value) => { this.emit('split-layout-request', value.unpack()); }); @@ -224,9 +280,9 @@ export const TerminalPage = GObject.registerClass({ page_actions.add_action(move_to_other_pane_action); this._title_binding = null; - this.connect('notify::use-custom-title', () => { + handlers.push(this.connect('notify::use-custom-title', () => { this.update_title_binding(); - }); + })); this.update_title_binding(); const use_custom_title_action = new Gio.SimpleAction({ @@ -237,11 +293,11 @@ export const TerminalPage = GObject.registerClass({ use_custom_title_action.connect('change-state', (_, value) => { this.use_custom_title = value.get_boolean(); }); - this.connect('notify::use-custom-title', () => { + handlers.push(this.connect('notify::use-custom-title', () => { use_custom_title_action.set_state( GLib.Variant.new_boolean(this.use_custom_title) ); - }); + })); use_custom_title_action.connect('activate', (_, param) => { use_custom_title_action.change_state(param); @@ -251,9 +307,32 @@ export const TerminalPage = GObject.registerClass({ page_actions.add_action(use_custom_title_action); this.insert_action_group('page', page_actions); + + handlers.push(this.connect('unrealize', () => { + for (const handler of handlers) + this.disconnect(handler); + + this.insert_action_group('page', null); + })); + this.tab_label.insert_action_group('page', page_actions); + const tab_label_unrealize = () => { + this.disconnect(unrealize_handler); + this.tab_label.disconnect(tab_label_unrealize_handler); + this.tab_label.insert_action_group('page', null); + }; + + const tab_label_unrealize_handler = + this.tab_label.connect('unrealize', tab_label_unrealize); + + const unrealize_handler = + this.connect('unrealize', tab_label_unrealize); + } + + _setup_terminal_actions() { const terminal_actions = new Gio.SimpleActionGroup(); + const terminal_handlers = []; const copy_action = new Gio.SimpleAction({ name: 'copy', @@ -273,10 +352,10 @@ export const TerminalPage = GObject.registerClass({ }); terminal_actions.add_action(copy_html_action); - this.terminal.connect('selection-changed', () => { - copy_action.enabled = this.terminal.get_has_selection(); - copy_html_action.enabled = this.terminal.get_has_selection(); - }); + terminal_handlers.push(this.terminal.connect('selection-changed', terminal => { + copy_action.enabled = terminal.get_has_selection(); + copy_html_action.enabled = terminal.get_has_selection(); + })); const open_hyperlink_action = new Gio.SimpleAction({ name: 'open-hyperlink', @@ -292,11 +371,11 @@ export const TerminalPage = GObject.registerClass({ copy_hyperlink_action.connect('activate', this.copy_hyperlink.bind(this)); terminal_actions.add_action(copy_hyperlink_action); - this.terminal.connect('notify::last-clicked-hyperlink', () => { - const enable = this.terminal.last_clicked_hyperlink !== null; + terminal_handlers.push(this.terminal.connect('notify::last-clicked-hyperlink', terminal => { + const enable = terminal.last_clicked_hyperlink !== null; open_hyperlink_action.enabled = enable; copy_hyperlink_action.enabled = enable; - }); + })); const copy_filename_action = new Gio.SimpleAction({ name: 'copy-filename', @@ -305,10 +384,10 @@ export const TerminalPage = GObject.registerClass({ copy_filename_action.connect('activate', this.copy_filename.bind(this)); terminal_actions.add_action(copy_filename_action); - this.terminal.connect('notify::last-clicked-filename', () => { - const enable = this.terminal.last_clicked_filename !== null; + terminal_handlers.push(this.terminal.connect('notify::last-clicked-filename', terminal => { + const enable = terminal.last_clicked_filename !== null; copy_filename_action.enabled = enable; - }); + })); const paste_action = new Gio.SimpleAction({ name: 'paste' }); paste_action.connect('activate', () => { @@ -393,9 +472,9 @@ export const TerminalPage = GObject.registerClass({ font_scale_reset_action.connect('activate', () => { this.terminal.font_scale = 1; }); - this.terminal.connect('notify::font-scale', () => { - font_scale_reset_action.enabled = this.terminal.font_scale !== 1; - }); + terminal_handlers.push(this.terminal.connect('notify::font-scale', terminal => { + font_scale_reset_action.enabled = terminal.font_scale !== 1; + })); terminal_actions.add_action(font_scale_reset_action); const show_in_file_manager_action = new Gio.SimpleAction({ @@ -408,12 +487,37 @@ export const TerminalPage = GObject.registerClass({ this.insert_action_group('terminal', terminal_actions); - this.terminal.connect_after('child-exited', (terminal_, status) => { - if (this.keep_open_after_exit) - this.add_exit_status_banner(status); - else - this.destroy(); - }); + const unrealize = () => { + this.disconnect(unrealize_handler); + + for (const handler of terminal_handlers) + this.terminal.disconnect(handler); + + this.insert_action_group('terminal', null); + }; + + const unrealize_handler = this.connect('unrealize', unrealize); + + terminal_handlers.push(this.terminal.connect('unrealize', unrealize)); + } + + _setup_child_handler() { + const child_handler = + this.terminal.connect_after('child-exited', (terminal_, status) => { + if (this.keep_open_after_exit) + this.add_exit_status_banner(status); + else + this.emit('close'); + }); + + const unrealize = () => { + this.disconnect(unrealize_handler); + this.terminal.disconnect(terminal_unrealize_handler); + this.terminal.disconnect(child_handler); + }; + + const unrealize_handler = this.connect('unrealize', unrealize); + const terminal_unrealize_handler = this.terminal.connect('unrealize', unrealize); } get_cwd() { @@ -432,7 +536,7 @@ export const TerminalPage = GObject.registerClass({ revealed: true, }); - banner.get_content_area().pack_start(label, false, false, 0); + banner.add_child(label); banner.add_button(Gettext.gettext('Restart'), 0); banner.add_button(Gettext.gettext('Close Terminal'), 1); @@ -443,12 +547,12 @@ export const TerminalPage = GObject.registerClass({ banner.destroy(); break; case 1: - this.destroy(); + this.emit('close'); break; } }); - this.pack_start(banner, false, false, 0); + this.prepend(banner); } add_exit_status_banner(status) { @@ -494,7 +598,7 @@ export const TerminalPage = GObject.registerClass({ } open_hyperlink() { - Gtk.show_uri_on_window( + Gtk.show_uri( this.get_ancestor(Gtk.Window), this.terminal.last_clicked_hyperlink, Gdk.CURRENT_TIME @@ -511,13 +615,9 @@ export const TerminalPage = GObject.registerClass({ clipboard.set_text(this.terminal.last_clicked_filename, -1); } - terminal_button_press_early(_terminal, event) { - const state = event.get_state()[1]; - - if (state & Gdk.ModifierType.CONTROL_MASK) { - const button = event.get_button()[1]; - - if ([Gdk.BUTTON_PRIMARY, Gdk.BUTTON_MIDDLE].includes(button)) { + terminal_button_press_early(gesture) { + if (gesture.get_current_event_state() & Gdk.ModifierType.CONTROL_MASK) { + if ([Gdk.BUTTON_PRIMARY, Gdk.BUTTON_MIDDLE].includes(gesture.get_current_button())) { this.open_hyperlink(); return true; } @@ -537,12 +637,12 @@ export const TerminalPage = GObject.registerClass({ } find() { - this.terminal.get_text_selected_async().then(text => { - if (text) - this.search_bar.pattern.text = text; + const text = this.terminal.get_text_selected?.(Vte.Format.TEXT); - this.search_bar.reveal_child = true; - }); + if (text) + this.search_bar.pattern.text = text; + + this.search_bar.reveal_child = true; } show_in_file_manager() { @@ -569,12 +669,12 @@ export const TerminalPage = GObject.registerClass({ close() { if (!this.terminal.has_foreground_process()) { - this.destroy(); + this.emit('close'); return; } const message = new Gtk.MessageDialog({ - transient_for: this.get_toplevel(), + transient_for: this.root, modal: true, buttons: Gtk.ButtonsType.CANCEL, message_type: Gtk.MessageType.WARNING, @@ -594,7 +694,7 @@ export const TerminalPage = GObject.registerClass({ message.connect('response', (_, response_id) => { if (response_id === Gtk.ResponseType.ACCEPT) - this.destroy(); + this.emit('close'); message.destroy(); }); diff --git a/ddterm/app/urldetect.js b/ddterm/app/urldetect.js index bb3915f8b..8935f1cd4 100644 --- a/ddterm/app/urldetect.js +++ b/ddterm/app/urldetect.js @@ -122,8 +122,8 @@ export const UrlDetect = GObject.registerClass({ }); } - check_event(event) { - const [url, tag] = this.terminal.match_check_event(event); + check_match_at(x, y) { + const [url, tag] = this.terminal.check_match_at(x, y); if (url && tag !== null && this._url_prefix.has(tag)) { const prefix = this._url_prefix.get(tag); diff --git a/tests/apphook.js b/tests/apphook.js index d546ebe7e..91c39f092 100644 --- a/tests/apphook.js +++ b/tests/apphook.js @@ -171,34 +171,45 @@ class DebugInterface { return () => obj.disconnect(handler_id); }; + const unrealize_callbacks = []; + + const call_unrealize_callbacks = () => { + while (unrealize_callbacks.length) + unrealize_callbacks.pop()(); + }; + this.disconnect_callbacks = [ + call_unrealize_callbacks, + connect(win, 'unrealize', call_unrealize_callbacks), connect(win, 'destroy', () => { if (win === this.window) this.connect_window(null); }), - connect(win, 'event', (_, event) => { - this.emit_event(event); - - return false; - }), - connect(win, 'configure-event', () => { - this.emit_configure_event(win.get_size()); - - return false; - }), - connect(win, 'window-state-event', () => { - this.emit_window_state_event(win.window.get_state()); - - return false; + connect(win, 'realize', w => { + const surface = w.get_surface(); + + unrealize_callbacks.push( + connect(surface, 'layout', (_, width, height) => { + this.emit_window_layout(width, height); + }), + connect(surface, 'notify::state', s => { + this.emit_toplevel_state(s.state); + }), + connect(surface, 'event', (_, event) => { + this.emit_event(event); + + return false; + }) + ); }), - connect(win, 'size-allocate', (_, rect) => { - this.emit_size_allocate(rect); + connect(win, 'size-allocate', (_, width, height) => { + this.emit_size_allocate(width, height); }), ]; const notify_num_tabs = this.notify_num_tabs.bind(this); - for (const notebook of [win.paned.get_child1(), win.paned.get_child2()]) { + for (const notebook of [win.paned.start_child, win.paned.end_child]) { this.disconnect_callbacks.push(connect(notebook, 'page-added', notify_num_tabs)); this.disconnect_callbacks.push(connect(notebook, 'page-removed', notify_num_tabs)); } @@ -216,25 +227,23 @@ class DebugInterface { ); } - emit_configure_event([width, height]) { + emit_window_layout(width, height) { this.dbus.emit_signal( - 'ConfigureEvent', + 'WindowLayout', GLib.Variant.new_tuple([GLib.Variant.new_int32(width), GLib.Variant.new_int32(height)]) ); } - emit_window_state_event(state) { - state = GObject.flags_to_string(Gdk.WindowState, state).split(' | '); + emit_toplevel_state(state) { + state = GObject.flags_to_string(Gdk.ToplevelState, state).split(' | '); this.dbus.emit_signal( - 'WindowStateEvent', + 'ToplevelState', GLib.Variant.new_tuple([GLib.Variant.new_strv(state)]) ); } - emit_size_allocate(rect) { - const { width, height } = rect; - + emit_size_allocate(width, height) { this.dbus.emit_signal( 'SizeAllocate', GLib.Variant.new_tuple([GLib.Variant.new_int32(width), GLib.Variant.new_int32(height)]) @@ -259,7 +268,7 @@ class DebugInterface { WaitFrameAsync(params, invocation) { try { - const frame_clock = this.window.window.get_frame_clock(); + const frame_clock = this.window.get_frame_clock(); const handler = frame_clock.connect_after('after-paint', () => { frame_clock.disconnect(handler); @@ -320,10 +329,7 @@ class DebugInterface { deepest_scope = focus_widget; } - const [prefix, name] = action_name.split('.'); - const actions = deepest_scope.get_action_group(prefix); - - actions.activate_action(name, target_value); + deepest_scope.activate_action(action_name, target_value); } get Connected() { @@ -336,7 +342,7 @@ class DebugInterface { const { paned } = this.window; - return paned.get_child1().get_n_pages() + paned.get_child2().get_n_pages(); + return paned.start_child.get_n_pages() + paned.end_child.get_n_pages(); } notify_num_tabs() { diff --git a/tests/conftest.py b/tests/conftest.py index 5288b2676..42c8435ea 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -408,9 +408,11 @@ def global_environment(container, request): env['LIBGL_ALWAYS_SOFTWARE'] = 'true' env['GBM_ALWAYS_SOFTWARE'] = 'true' env['VK_LOADER_DRIVERS_SELECT'] = 'lvp_*' + env['GSK_RENDERER'] = 'cairo' env['NO_AT_BRIDGE'] = '1' env['GTK_A11Y'] = 'none' + env['ADW_DISABLE_PORTAL'] = '1' return env diff --git a/tests/dbus-interfaces/com.github.amezin.ddterm.Debug.xml b/tests/dbus-interfaces/com.github.amezin.ddterm.Debug.xml index cd828a037..cf8347210 100644 --- a/tests/dbus-interfaces/com.github.amezin.ddterm.Debug.xml +++ b/tests/dbus-interfaces/com.github.amezin.ddterm.Debug.xml @@ -25,11 +25,11 @@ - + - + diff --git a/tests/test_app.py b/tests/test_app.py index ec0b03258..ed76fb45d 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -19,7 +19,7 @@ LOGGER = logging.getLogger(__name__) -GC_CYCLES = 2 +GC_CYCLES = 20 def diff_heap(dump_old, dump_new, hide_node=[], hide_edge=[], gray_roots=True, weak_maps=True): diff --git a/tests/test_wm.py b/tests/test_wm.py index 034ab4df9..fde6816c8 100644 --- a/tests/test_wm.py +++ b/tests/test_wm.py @@ -185,7 +185,7 @@ def reset(signal, source, *args): ) ) - for signal in ('ConfigureEvent', 'WindowStateEvent', 'SizeAllocate'): + for signal in ('WindowLayout', 'ToplevelState', 'SizeAllocate'): stack.enter_context( glibutil.signal_handler( app_debug_dbus_interface,