diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 203cd097..2ce26955 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 container: image: elementary/docker:next-unstable @@ -19,11 +19,10 @@ jobs: - name: Install Dependencies run: | apt update - apt install -y libgranite-dev libgtk-3-dev meson valac + apt install -y libgranite-7-dev libgtk-4-dev meson valac - name: Build run: | meson build - ninja -C build ninja -C build install lint: diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index be67f744..35373d09 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -8,14 +8,14 @@ on: jobs: gettext: name: Gettext - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v1 - name: Gettext - uses: elementary/actions/gettext-template@master + uses: elementary/actions/gettext-template@horus env: GIT_USER_TOKEN: "${{ secrets.GIT_USER_TOKEN }}" GIT_USER_NAME: "elementaryBot" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1f899f53..3cd8e824 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ on: jobs: release_deb: name: Release (Deb) - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 if: github.event.pull_request.merged == true && true == contains(join(github.event.pull_request.labels.*.name), 'Release') @@ -25,4 +25,4 @@ jobs: GIT_USER_NAME: "elementaryBot" GIT_USER_EMAIL: "builds@elementary.io" with: - release_branch: 'odin' + release_branch: 'horus' diff --git a/README.md b/README.md index 59ce44d8..570520c0 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,8 @@ ## Building, Testing, and Installation You'll need the following dependencies: -* libgranite-dev >= 6.0.0 -* libhandy-1 -* libgtk-3.0-dev +* libgranite-7-dev +* gtk4 * libvte-2.91-dev * meson * valac diff --git a/meson.build b/meson.build index 59b7291a..3396cd05 100644 --- a/meson.build +++ b/meson.build @@ -12,11 +12,11 @@ systemd_dep = dependency('systemd') glib_dep = dependency('glib-2.0') gobject_dep = dependency('gobject-2.0') gio_dep = dependency('gio-2.0') -gtk_dep = dependency('gtk+-3.0') -gdk_dep = [ dependency('gdk-x11-3.0'), dependency('gdk-wayland-3.0') ] +granite_dep = dependency('granite-7') +gtk_dep = dependency('gtk4') +gtk_x11_dep = dependency('gtk4-x11') +wayland_dep = dependency('gtk4-wayland') x11_dep = dependency('x11') -granite_dep = dependency('granite') -handy_dep = dependency('libhandy-1') add_project_arguments( '--vapidir', meson.current_source_dir() / 'vapi', diff --git a/src/Access/Choice.vala b/src/Access/Choice.vala index e4b9448d..35e7b7e6 100644 --- a/src/Access/Choice.vala +++ b/src/Access/Choice.vala @@ -26,7 +26,7 @@ public class Access.Choice : Gtk.Box { var label = new Gtk.Label (label); bind_property ("label", label, "label", BindingFlags.DEFAULT); - add (label); + append (label); if (options.n_children () == 0) { var check = new Gtk.CheckButton (); @@ -42,7 +42,7 @@ public class Access.Choice : Gtk.Box { } ); - add (check); + append (check); } else { var combo = new Gtk.ComboBoxText (); var iter = options.iterator (); @@ -53,7 +53,7 @@ public class Access.Choice : Gtk.Box { } bind_property ("selected", combo, "active-id", BindingFlags.BIDIRECTIONAL); - add (combo); + append (combo); } } } diff --git a/src/Access/Dialog.vala b/src/Access/Dialog.vala index 13385f3b..af3296c5 100644 --- a/src/Access/Dialog.vala +++ b/src/Access/Dialog.vala @@ -41,7 +41,6 @@ public class Access.Dialog : Granite.MessageDialog { private Gtk.Button grant_button; private Gtk.Button deny_button; private List choices; - private Gtk.Box box; public Dialog (ButtonAction action, string app_id, string parent_window, string icon) { Object ( @@ -54,13 +53,10 @@ public class Access.Dialog : Granite.MessageDialog { } construct { - skip_taskbar_hint = true; resizable = false; modal = true; choices = new List (); - set_role ("AccessDialog"); // used in Gala.CloseDialog - set_keep_above (true); if (app_id != "") { badge_icon = new DesktopAppInfo (app_id + ".desktop").get_icon (); @@ -68,24 +64,29 @@ public class Access.Dialog : Granite.MessageDialog { deny_button = add_button (_("Deny Access"), Gtk.ResponseType.CANCEL) as Gtk.Button; grant_button = add_button (_("Grant Access"), Gtk.ResponseType.OK) as Gtk.Button; - unowned var grant_context = grant_button.get_style_context (); if (action == ButtonAction.SUGGESTED) { - grant_context.add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION); - set_default (grant_button); + grant_button.add_css_class (Granite.STYLE_CLASS_SUGGESTED_ACTION); + default_widget = grant_button; } else { - grant_context.add_class (Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION); - set_default (deny_button); + grant_button.add_css_class (Granite.STYLE_CLASS_DESTRUCTIVE_ACTION); + default_widget = deny_button; } - box = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); - custom_bin.child = box; - box.show (); + custom_bin.orientation = Gtk.Orientation.VERTICAL; + custom_bin.spacing = 6; if (parent_window != "") { - realize.connect (() => { + ((Gtk.Widget) this).realize.connect (() => { + unowned var surface = get_surface (); + + if (surface is Gdk.X11.Surface) { + unowned var x11_surface = (Gdk.X11.Surface) surface; + x11_surface.set_skip_taskbar_hint (true); + } + try { - ExternalWindow.from_handle (parent_window).set_parent_of (get_window ()); + ExternalWindow.from_handle (parent_window).set_parent_of (surface); } catch (Error e) { warning ("Failed to associate portal window with parent %s: %s", parent_window, e.message); } @@ -93,21 +94,14 @@ public class Access.Dialog : Granite.MessageDialog { } show.connect (() => { - var window = get_window (); - if (window == null) { - return; - } - - window.focus (Gdk.CURRENT_TIME); + present_with_time (Gdk.CURRENT_TIME); }); - - response.connect_after (destroy); } [DBus (visible = false)] public void add_choice (Choice choice) { choices.append (choice); - box.add (choice); + custom_bin.append (choice); } [DBus (visible = false)] diff --git a/src/Access/Portal.vala b/src/Access/Portal.vala index 9f4117d4..0dd465b8 100644 --- a/src/Access/Portal.vala +++ b/src/Access/Portal.vala @@ -70,13 +70,6 @@ public class Access.Portal : Object { var _results = new HashTable (str_hash, str_equal); uint _response = 2; - dialog.destroy.connect (() => { - if (dialog.register_id != 0) { - connection.unregister_object (dialog.register_id); - dialog.register_id = 0; - } - }); - dialog.response.connect ((id) => { switch (id) { case Gtk.ResponseType.OK: @@ -100,9 +93,12 @@ public class Access.Portal : Object { access_dialog.callback (); }); - dialog.show_all (); + dialog.present (); yield; + connection.unregister_object (dialog.register_id); + dialog.destroy (); + results = _results; response = _response; } diff --git a/src/AppChooser/AppButton.vala b/src/AppChooser/AppButton.vala index ea31a721..cf9bcf48 100644 --- a/src/AppChooser/AppButton.vala +++ b/src/AppChooser/AppButton.vala @@ -15,22 +15,17 @@ public class AppChooser.AppButton : Gtk.ListBoxRow { var icon = new Gtk.Image () { gicon = app_info.get_icon () ?? new ThemedIcon ("application-default-icon"), - icon_size = Gtk.IconSize.DND + icon_size = Gtk.IconSize.LARGE }; var name = new Gtk.Label (app_info.get_display_name ()) { ellipsize = Pango.EllipsizeMode.END }; - var grid = new Gtk.Grid () { - column_spacing = 6, - margin = 3, - margin_start = 6, - margin_end = 6 - }; - grid.add (icon); - grid.add (name); + var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6); + box.append (icon); + box.append (name); - add (grid); + child = box; } } diff --git a/src/AppChooser/Dialog.vala b/src/AppChooser/Dialog.vala index fb1c6b60..4c417211 100644 --- a/src/AppChooser/Dialog.vala +++ b/src/AppChooser/Dialog.vala @@ -4,7 +4,7 @@ */ [DBus (name = "org.freedesktop.impl.portal.Request")] -public class AppChooser.Dialog : Hdy.Window { +public class AppChooser.Dialog : Gtk.Window { public signal void choiced (string app_id); // The ID used to register this dialog on the DBusConnection @@ -47,7 +47,6 @@ public class AppChooser.Dialog : Hdy.Window { construct { buttons = new HashTable (str_hash, str_equal); AppInfo? info = app_id == "" ? null : new DesktopAppInfo (app_id + ".desktop"); - Hdy.init (); var primary_text = _("Open file with…"); if (filename != "") { @@ -68,7 +67,7 @@ public class AppChooser.Dialog : Hdy.Window { wrap = true, xalign = 0 }; - primary_label.get_style_context ().add_class (Granite.STYLE_CLASS_PRIMARY_LABEL); + primary_label.add_css_class (Granite.STYLE_CLASS_TITLE_LABEL); var secondary_text = _("An application requested to open a %s.").printf (content_description); if (info != null) { @@ -83,89 +82,101 @@ public class AppChooser.Dialog : Hdy.Window { xalign = 0 }; - var mime_icon = new Gtk.Image () { - gicon = content_icon , - icon_size = Gtk.IconSize.DIALOG + var mime_icon = new Gtk.Image.from_gicon (content_icon) { + pixel_size = 48 }; var overlay = new Gtk.Overlay () { + child = mime_icon, valign = Gtk.Align.START }; - overlay.add (mime_icon); if (info != null) { - var badge = new Gtk.Image.from_gicon (info.get_icon (), Gtk.IconSize.LARGE_TOOLBAR) { + var badge = new Gtk.Image.from_gicon (info.get_icon ()) { halign = Gtk.Align.END, - valign = Gtk.Align.END + valign = Gtk.Align.END, + pixel_size = 24 }; overlay.add_overlay (badge); } - var placeholder = new Granite.Widgets.AlertView ( - _("No installed apps can open %s").printf (content_description), - _("New apps can be installed from AppCenter"), - "application-default-icon" - ); - placeholder.show_all (); + var placeholder = new Granite.Placeholder (_("No installed apps can open %s").printf (content_description)) { + description = _("New apps can be installed from AppCenter"), + icon = new ThemedIcon ("application-default-icon") + }; listbox = new Gtk.ListBox () { - expand = true + hexpand = true, + vexpand = true }; + listbox.add_css_class (Granite.STYLE_CLASS_RICH_LIST); listbox.set_placeholder (placeholder); - var scrolled_window = new Gtk.ScrolledWindow (null, null); - scrolled_window.add (listbox); + var scrolled_window = new Gtk.ScrolledWindow () { + child = listbox + }; - var frame = new Gtk.Frame (null); - frame.add (scrolled_window); + var frame = new Gtk.Frame (null) { + child = scrolled_window + }; - var cancel = new Gtk.Button.with_label (_("Cancel")); + var cancel_button = new Gtk.Button.with_label (_("Cancel")); open_button = new Gtk.Button.with_label (_("Open")) { - can_default = true + receives_default = true }; - open_button.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION); + open_button.add_css_class (Granite.STYLE_CLASS_SUGGESTED_ACTION); - var button_box = new Gtk.ButtonBox (Gtk.Orientation.HORIZONTAL) { - layout_style = Gtk.ButtonBoxStyle.END, - margin_top = 12, - spacing = 6 + var button_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) { + halign = Gtk.Align.END }; - - button_box.add (cancel); - button_box.add (open_button); + button_box.append (cancel_button); + button_box.append (open_button); + button_box.add_css_class ("dialog-action-area"); var grid = new Gtk.Grid () { orientation = Gtk.Orientation.VERTICAL, column_spacing = 12, - row_spacing = 6, - margin = 12 + row_spacing = 6 }; grid.attach (overlay, 0, 0, 1, 2); grid.attach (primary_label, 1, 0); grid.attach (secondary_label, 1, 1); grid.attach (frame, 0, 3, 2); - grid.attach (button_box, 1, 4); + grid.add_css_class (Granite.STYLE_CLASS_DIALOG_CONTENT_AREA); + + var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); + box.append (grid); + box.append (button_box); + box.add_css_class ("dialog-vbox"); - var window_handle = new Hdy.WindowHandle (); - window_handle.add (grid); + var window_handle = new Gtk.WindowHandle () { + child = box + }; - add (window_handle); - type_hint = Gdk.WindowTypeHint.DIALOG; + child = window_handle; + + // We need to hide the title area + titlebar = new Gtk.Grid () { + visible = false + }; + + modal = true; default_height = 400; default_width = 350; - modal = true; + default_widget = open_button; - open_button.grab_default (); + add_css_class ("dialog"); + add_css_class (Granite.STYLE_CLASS_MESSAGE_DIALOG); if (parent_window != "") { - realize.connect (() => { + ((Gtk.Widget) this).realize.connect (() => { try { - ExternalWindow.from_handle (parent_window).set_parent_of (get_window ()); + ExternalWindow.from_handle (parent_window).set_parent_of (get_surface ()); } catch (Error e) { - warning ("Failed to associate portal window with parent window %s: %s", parent_window, e.message); + warning ("Failed to associate portal window with parent %s: %s", parent_window, e.message); } }); } @@ -175,15 +186,12 @@ public class AppChooser.Dialog : Hdy.Window { }); open_button.clicked.connect (() => choiced (((AppChooser.AppButton) listbox.get_selected_row ()).app_id)); - cancel.clicked.connect (() => choiced ("")); - - // close the dialog after a selection; - choiced.connect_after (() => destroy ()); + cancel_button.clicked.connect (() => choiced ("")); } private void add_choice (string choice) { buttons[choice] = new AppButton (choice); - listbox.add (buttons[choice]); + listbox.append (buttons[choice]); } @@ -194,18 +202,17 @@ public class AppChooser.Dialog : Hdy.Window { add_choice (choice); } } - listbox.show_all (); if (last_choice != "" && !(last_choice in buttons) && last_choice != app_id) { add_choice (last_choice); buttons[last_choice].grab_focus (); } - open_button.sensitive = listbox.get_children ().length () > 0; + open_button.sensitive = listbox.get_row_at_index (0) != null; } [DBus (name = "Close")] public void on_close () throws DBusError, IOError { - destroy (); + choiced (""); } } diff --git a/src/AppChooser/Portal.vala b/src/AppChooser/Portal.vala index 72538609..6988201a 100644 --- a/src/AppChooser/Portal.vala +++ b/src/AppChooser/Portal.vala @@ -58,24 +58,10 @@ public class AppChooser.Portal : Object { critical (e.message); } - var _results = new HashTable (str_hash, str_equal); uint _response = 2; - dialog.destroy.connect (() => { - if (dialog.register_id != 0) { - connection.unregister_object (dialog.register_id); - } - }); - - var destroy_id = dialog.destroy.connect_after (() => { - _results["choice"] = ""; - choose_application.callback (); - }); - dialog.choiced.connect ((app_id) => { - dialog.disconnect (destroy_id); - _results["choice"] = app_id.replace (".desktop", ""); _response = app_id == "" ? 1 : 0; @@ -83,9 +69,12 @@ public class AppChooser.Portal : Object { }); handles[handle] = dialog; - dialog.show_all (); + dialog.present (); yield; + connection.unregister_object (dialog.register_id); + dialog.destroy (); + results = _results; response = _response; } diff --git a/src/ExternalWindow.vala b/src/ExternalWindow.vala index ab28219b..9341f7bc 100644 --- a/src/ExternalWindow.vala +++ b/src/ExternalWindow.vala @@ -20,7 +20,7 @@ */ public interface ExternalWindow : GLib.Object { - public abstract void set_parent_of (Gdk.Window child_window); + public abstract void set_parent_of (Gdk.Surface child_surface); public static ExternalWindow? from_handle (string handle) throws GLib.IOError { const string X11_PREFIX = "x11:"; @@ -64,8 +64,8 @@ public class ExternalWindowX11 : ExternalWindow, GLib.Object { } Gdk.set_allowed_backends ("x11"); - x11_display = Gdk.Display.open (null); - Gdk.set_allowed_backends (null); + x11_display = Gdk.Display.open (""); + Gdk.set_allowed_backends ("*"); if (x11_display == null) { warning ("Failed to open X11 display"); @@ -74,21 +74,21 @@ public class ExternalWindowX11 : ExternalWindow, GLib.Object { return x11_display; } - public void set_parent_of (Gdk.Window child_window) { + public void set_parent_of (Gdk.Surface child_surface) { unowned var display = (Gdk.X11.Display) get_x11_display (); unowned var x_display = display.get_xdisplay (); - var child_xid = ((Gdk.X11.Window) child_window).get_xid (); + var child_xid = ((Gdk.X11.Surface) child_surface).get_xid (); x_display.set_transient_for_hint (child_xid, foreign_window); - var atom = Gdk.X11.get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE_DIALOG"); + var dialog_atom = display.get_xatom_by_name ("_NET_WM_WINDOW_TYPE_DIALOG"); x_display.change_property ( child_xid, - Gdk.X11.get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE"), + display.get_xatom_by_name ("_NET_WM_WINDOW_TYPE"), X.XA_ATOM, 32, X.PropMode.Replace, - (uchar[]) atom, + (uchar[]) dialog_atom, 1 ); } @@ -114,8 +114,8 @@ public class ExternalWindowWayland : ExternalWindow, GLib.Object { } Gdk.set_allowed_backends ("wayland"); - wayland_display = Gdk.Display.open (null); - Gdk.set_allowed_backends (null); + wayland_display = Gdk.Display.open (""); + Gdk.set_allowed_backends ("*"); if (wayland_display == null) { warning ("Failed to open Wayland display"); @@ -124,8 +124,8 @@ public class ExternalWindowWayland : ExternalWindow, GLib.Object { return wayland_display; } - public void set_parent_of (Gdk.Window child_window) { - if (!((Gdk.Wayland.Window) child_window).set_transient_for_exported (handle)) { + public void set_parent_of (Gdk.Surface child_surface) { + if (!((Gdk.Wayland.Toplevel) child_surface).set_transient_for_exported (handle)) { warning ("Failed to set portal window transient for external parent"); } } diff --git a/src/XdgDesktopPortalPantheon.vala b/src/XdgDesktopPortalPantheon.vala index f1c282ad..e0af6142 100644 --- a/src/XdgDesktopPortalPantheon.vala +++ b/src/XdgDesktopPortalPantheon.vala @@ -33,17 +33,17 @@ private const GLib.OptionEntry[] OPTIONS_ENTRIES = { private void on_bus_acquired (DBusConnection connection, string name) { try { connection.register_object ("/org/freedesktop/portal/desktop", new Access.Portal (connection)); - debug ("Access Portal registed!"); + debug ("Access Portal registered!"); connection.register_object ("/org/freedesktop/portal/desktop", new AppChooser.Portal (connection)); - debug ("AppChooser Portal registred!"); + debug ("AppChooser Portal registered!"); } catch (Error e) { critical ("Unable to register the object: %s", e.message); } } private void on_name_acquired () { - debug ("org.freedesktop.impl.portal.desktop.panthon acquired"); + debug ("org.freedesktop.impl.portal.desktop.pantheon acquired"); // We're probably being started by xdg-desktop-portal, which won't be fully initialised until all the backends have loaded. // Granite depends on the settings portal to get the style preference, but can't DBus activate it because it's already starting. @@ -75,7 +75,7 @@ int main (string[] args) { /* Avoid pointless and confusing recursion */ GLib.Environment.unset_variable ("GTK_USE_PORTAL"); - Gtk.init (ref args); + Gtk.init (); try { var opt_context = new OptionContext ("- portal backends"); diff --git a/src/meson.build b/src/meson.build index 47be37af..1155dfd6 100644 --- a/src/meson.build +++ b/src/meson.build @@ -13,11 +13,11 @@ executable( glib_dep, gobject_dep, gio_dep, - gtk_dep, - gdk_dep, - x11_dep, granite_dep, - handy_dep + gtk_dep, + gtk_x11_dep, + wayland_dep, + x11_dep ], install: true, install_dir: libexecdir