diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f7257b07..db0178ca 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -23,7 +23,7 @@ jobs:
- name: Install Dependencies
run: |
apt update
- apt install -y libgranite-7-dev libgtk-4-dev libpantheon-wayland-1-dev meson valac
+ apt install -y libaccountsservice-dev libadwaita-1-dev libgranite-7-dev libgtk-4-dev libpantheon-wayland-1-dev meson valac
- name: Build
run: |
meson build
diff --git a/data/Application.css b/data/Application.css
index 0543b2c9..cbb92291 100644
--- a/data/Application.css
+++ b/data/Application.css
@@ -14,3 +14,8 @@
.dialog toolbox .bottom button {
min-width: 5em;
}
+
+.flat avatar {
+ border: none;
+ box-shadow: none;
+}
diff --git a/data/gresource.xml b/data/gresource.xml
index 3baf25c1..aa209733 100644
--- a/data/gresource.xml
+++ b/data/gresource.xml
@@ -4,6 +4,9 @@
Application.css
+ icons/account.svg
+ icons/account.svg
+
icons/screencast.svg
icons/screencast.svg
diff --git a/data/icons/account.svg b/data/icons/account.svg
new file mode 100644
index 00000000..2cbf827c
--- /dev/null
+++ b/data/icons/account.svg
@@ -0,0 +1,42 @@
+
+
diff --git a/data/pantheon.portal b/data/pantheon.portal
index b9de94e3..dab25b77 100644
--- a/data/pantheon.portal
+++ b/data/pantheon.portal
@@ -1,4 +1,4 @@
[portal]
DBusName=org.freedesktop.impl.portal.desktop.pantheon
-Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.AppChooser;org.freedesktop.impl.portal.Background;org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.Wallpaper;org.freedesktop.impl.portal.ScreenCast
+Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.Account;org.freedesktop.impl.portal.AppChooser;org.freedesktop.impl.portal.Background;org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.Wallpaper;org.freedesktop.impl.portal.ScreenCast
UseIn=pantheon
diff --git a/meson.build b/meson.build
index 1fe653ab..83d8b459 100644
--- a/meson.build
+++ b/meson.build
@@ -10,6 +10,8 @@ localedir = prefix / get_option('localedir')
systemd_dep = dependency('systemd')
+act_dep = dependency('accountsservice')
+adw_dep = dependency('libadwaita-1')
glib_dep = dependency('glib-2.0')
gobject_dep = dependency('gobject-2.0')
gio_dep = dependency('gio-2.0')
diff --git a/po/POTFILES b/po/POTFILES
index e43f71dc..3691af5a 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -2,6 +2,8 @@ src/XdgDesktopPortalPantheon.vala
src/ExternalWindow.vala
src/Access/Portal.vala
src/Access/Dialog.vala
+src/Account/Dialog.vala
+src/Account/Portal.vala
src/AppChooser/Portal.vala
src/AppChooser/Dialog.vala
src/AppChooser/AppButton.vala
diff --git a/src/Access/Portal.vala b/src/Access/Portal.vala
index e000bb93..7a0bd0bc 100644
--- a/src/Access/Portal.vala
+++ b/src/Access/Portal.vala
@@ -69,10 +69,10 @@ public class Access.Portal : Object {
}
var _results = new HashTable (str_hash, str_equal);
- var _response = 2;
+ uint32 _response = 2;
- dialog.response.connect ((id) => {
- switch (id) {
+ dialog.response.connect ((response) => {
+ switch (response) {
case ALLOW:
var choices_builder = new VariantBuilder (new VariantType ("a(ss)"));
@@ -81,18 +81,13 @@ public class Access.Portal : Object {
});
_results["choices"] = choices_builder.end ();
- _response = 0;
break;
-
- case CANCEL:
- _response = 1;
- break;
-
- case DELETE_EVENT:
- _response = 2;
+ default:
break;
}
+ _response = response.to_id ();
+
access_dialog.callback ();
});
diff --git a/src/Account/Dialog.vala b/src/Account/Dialog.vala
new file mode 100644
index 00000000..157f01cd
--- /dev/null
+++ b/src/Account/Dialog.vala
@@ -0,0 +1,108 @@
+/*
+ * SPDX-FileCopyrigthText: 2026 elementary, Inc. (https://elementary.io)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+[DBus (name = "org.freedesktop.impl.portal.Request")]
+public class Account.Dialog : PortalDialog {
+ // The ID used to register this dialog on the DBusConnection
+ public uint register_id { get; set; default = 0; }
+
+ // The ID of the app sending the request
+ public string app_id { get; construct; }
+
+ public string user_name;
+ public string image_filename;
+ public string real_name;
+
+ private Adw.Avatar avatar;
+
+ public string reason {
+ set {
+ secondary_text = _("It provided the following reason, ā%sā").printf (value);
+ }
+ }
+
+ public Dialog (string app_id) {
+ Object (app_id: app_id);
+ }
+
+ construct {
+ default_height = -1;
+ title = _("An application wants to access your name and photo");
+ secondary_icon = new ThemedIcon ("emblem-portal-account");
+ secondary_text = _("It did not provide a reason for this request.");
+
+ if (app_id != "") {
+ var app_info = new DesktopAppInfo (app_id + ".desktop");
+ if (app_info != null) {
+ primary_icon = app_info.get_icon ();
+ title = _("ā%sā wants to access your name and photo").printf (app_info.get_display_name ());
+ }
+ }
+
+ user_name = Environment.get_user_name ();
+ real_name = Environment.get_real_name ();
+
+ avatar = new Adw.Avatar (32, null, false);
+ avatar.add_css_class (Granite.STYLE_CLASS_FLAT);
+
+ var username_label = new Gtk.Label (user_name) {
+ halign = START
+ };
+ username_label.add_css_class (Granite.CssClass.DIM);
+ username_label.add_css_class (Granite.CssClass.SMALL);
+
+ var name_box = new Granite.Box (VERTICAL, NONE) {
+ valign = CENTER
+ };
+ name_box.append (new Gtk.Label (real_name));
+ name_box.append (username_label);
+
+ var box = new Granite.Box (HORIZONTAL, SINGLE);
+ box.append (avatar);
+ box.append (name_box);
+
+ var listitem = new Granite.ListItem () {
+ child = box,
+ margin_top = 12,
+ margin_end = 12,
+ margin_bottom = 12,
+ margin_start = 12
+ };
+ listitem.add_css_class (Granite.CssClass.CARD);
+
+ content = listitem;
+
+ var usermanager = Act.UserManager.get_default ();
+ if (usermanager.is_loaded) {
+ on_usermanager_loaded (usermanager);
+ } else {
+ usermanager.notify["is-loaded"].connect (() => on_usermanager_loaded (usermanager));
+ }
+ }
+
+ private void on_usermanager_loaded (Act.UserManager usermanager) {
+ var user = usermanager.get_user (user_name);
+ if (user.is_loaded) {
+ on_user_loaded (user);
+ } else {
+ user.notify["is-loaded"].connect (() => on_user_loaded (user));
+ }
+
+ }
+
+ private void on_user_loaded (Act.User user) {
+ image_filename = user.get_icon_file ();
+ try {
+ avatar.custom_image = Gdk.Texture.from_filename (image_filename);
+ } catch {
+ debug ("unable to set avatar");
+ }
+ }
+
+ [DBus (name = "Close")]
+ public void on_close () throws DBusError, IOError {
+ response (DELETE_EVENT);
+ }
+}
diff --git a/src/Account/Portal.vala b/src/Account/Portal.vala
new file mode 100644
index 00000000..45bd2aff
--- /dev/null
+++ b/src/Account/Portal.vala
@@ -0,0 +1,65 @@
+/*
+ * SPDX-FileCopyrigthText: 2026 elementary, Inc. (https://elementary.io)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+[DBus (name = "org.freedesktop.impl.portal.Account")]
+public class Account.Portal : Object {
+ private DBusConnection connection;
+
+ public Portal (DBusConnection connection) {
+ this.connection = connection;
+ }
+
+ [DBus (name = "GetUserInformation")]
+ public async void get_user_information (
+ ObjectPath handle,
+ string app_id,
+ string parent_window,
+ HashTable options,
+ out uint response,
+ out HashTable results
+ ) throws DBusError, IOError {
+ var dialog = new Dialog (app_id) {
+ parent_handle = parent_window
+ };
+
+ if ("reason" in options) {
+ dialog.reason = options["reason"].get_string ();
+ }
+
+ try {
+ dialog.register_id = connection.register_object