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,