Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Background portal implementation #73

Merged
merged 39 commits into from
May 22, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
fe1fb09
Prototype background portal
leolost2605 May 12, 2023
913c895
Minor fixes
leolost2605 May 12, 2023
dccd199
Cleanup
leolost2605 May 12, 2023
dbb9c93
Add fixmes
leolost2605 May 12, 2023
293c4e4
Fix lint
leolost2605 May 12, 2023
884429c
Fix criticals, implement NotifyBackground
leolost2605 May 13, 2023
d01480b
Update ci
leolost2605 May 13, 2023
70aba7b
Fix quoting of args that don't need quoting
leolost2605 May 14, 2023
90d7d8a
Use first commandline arg as app_id if it is empty
leolost2605 May 14, 2023
030d076
Cleanup, use enums
leolost2605 May 14, 2023
87a5bf5
Cleanup
leolost2605 May 14, 2023
bf2613c
Remove .desktop
leolost2605 May 14, 2023
891ea0a
Init Notify with xdg-desktop-portal-pantheon
leolost2605 May 14, 2023
4701a54
Apply suggestions from code review
leolost2605 May 17, 2023
a3c2033
Fix stuff from code review
leolost2605 May 17, 2023
fc627b4
Always use first command_line arg as app_id if it is empty
leolost2605 May 17, 2023
f95c7ee
Handle notifications without libnotify
leolost2605 May 17, 2023
1f5116a
Rename variable
leolost2605 May 17, 2023
88db5f8
Don't keep references on expired notifications
leolost2605 May 17, 2023
6cb7ea5
Merge branch 'main' into background-portal
zeebok May 18, 2023
17bc48a
Apply suggestions from code review
leolost2605 May 18, 2023
13cdb0e
Merge NotificationHandler and NotificationRequest
May 18, 2023
732c8c0
Add license header
leolost2605 May 18, 2023
8d0e6e1
Change notification content
leolost2605 May 18, 2023
61fce13
Add failed result, set response
leolost2605 May 18, 2023
5b4cc60
Formatting
leolost2605 May 18, 2023
c566a4b
Cleanup
leolost2605 May 19, 2023
3dabd28
Merge branch 'main' into background-portal
zeebok May 21, 2023
1f7dd77
Apply suggestions from code review
leolost2605 May 22, 2023
6b6ec9e
Fix issues from code review
leolost2605 May 22, 2023
8810517
Apply suggestions from code review
leolost2605 May 22, 2023
ed1d592
Use AppInfo to get name + icon, use all commandline args as filename
leolost2605 May 22, 2023
d8eb4ac
Fix lint
leolost2605 May 22, 2023
f06b991
Add some dbug messages and fix issue with the appstate variant not be…
leolost2605 May 22, 2023
67004b1
Add another debug message
leolost2605 May 22, 2023
c851f0d
Fix autostart
leolost2605 May 22, 2023
72eea6e
Cleanup
leolost2605 May 22, 2023
f29b0ae
Cleanup
leolost2605 May 22, 2023
14d29bb
Use Flags
leolost2605 May 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion data/pantheon.portal
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[portal]
DBusName=org.freedesktop.impl.portal.desktop.pantheon
Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.AppChooser;
Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.AppChooser;org.freedesktop.impl.portal.Background;
UseIn=pantheon
109 changes: 109 additions & 0 deletions src/Background/NotificationHandler.vala
leolost2605 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
public class NotificationHandler : Object {
public const string ACTION_ALLOW_BACKGROUND = "background.allow";
public const string ACTION_FORBID_BACKGROUND = "background.forbid";

private DBusConnection connection;
private HashTable<uint32, NotificationRequest> notification_by_id;
private Notifications notifications;

private enum NotifyBackgroundResult {
FORBID,
ALLOW,
ALLOW_ONCE
}
leolost2605 marked this conversation as resolved.
Show resolved Hide resolved

[DBus (name = "org.freedesktop.Notifications")]
public interface Notifications : Object {
public signal void action_invoked (uint32 id, string action_key);
public signal void notification_closed (uint32 id, uint32 reason);

public abstract void close_notification (uint32 id) throws DBusError, IOError;
public abstract uint32 notify (
string app_name,
uint32 replaces_id,
string app_icon,
string summary,
string body,
string[] actions,
HashTable<string, Variant> hints,
int32 expire_timeout
) throws DBusError, IOError;
}

public NotificationHandler (DBusConnection connection) {
this.connection = connection;
notification_by_id = new HashTable<uint32, NotificationRequest> (null, null);

try {
notifications = connection.get_proxy_sync ("org.freedesktop.Notifications", "/org/freedesktop/Notifications");
notifications.action_invoked.connect (on_action_invoked);
notifications.notification_closed.connect (on_notification_closed);
} catch {
warning ("Cannot connect to notifications dbus, background portal working with reduced functionality.");
}
}

public NotificationRequest? send_notification (string app_id, string app_name) {
string[] actions = {
ACTION_ALLOW_BACKGROUND,
_("Allow"),
ACTION_FORBID_BACKGROUND,
_("Forbid")
};

try {
var id = notifications.notify (
Environment.get_prgname (),
0,
"dialog-information",
_("Background activity"),
_(""""%s" is running in the background""").printf (app_name),
actions,
new HashTable<string, Variant> (str_hash, str_equal),
0
);
leolost2605 marked this conversation as resolved.
Show resolved Hide resolved

var notification = new NotificationRequest (this, id);
notification_by_id.set (id, notification);

return notification;
} catch (Error e) {
warning ("Failed to send notification for app id '%s': %s", app_id, e.message);
return null;
}
}

private void on_action_invoked (uint32 id, string action_key) {
if (id in notification_by_id) {
var notification = notification_by_id.get (id);
if (action_key == NotificationHandler.ACTION_ALLOW_BACKGROUND) {
notification.response (NotifyBackgroundResult.ALLOW);
} else if (action_key == NotificationHandler.ACTION_FORBID_BACKGROUND) {
notification.response (NotifyBackgroundResult.FORBID);
}

notification_by_id.remove (id);
}
leolost2605 marked this conversation as resolved.
Show resolved Hide resolved
}

private void on_notification_closed (uint32 id, uint32 reason) {
if (id in notification_by_id) {
var notification = notification_by_id.get (id);
if (reason == 2 || reason == 3) {
notification.response (NotifyBackgroundResult.ALLOW_ONCE);

notification_by_id.remove (id);
}
}
leolost2605 marked this conversation as resolved.
Show resolved Hide resolved
}

public void close_notification (uint32 id) {
try {
notifications.close_notification (id);
} catch (Error e) {
warning ("Failed to close notification with id '%u': %s", id, e.message);
}

notification_by_id.remove (id);
leolost2605 marked this conversation as resolved.
Show resolved Hide resolved
}
}
19 changes: 19 additions & 0 deletions src/Background/NotificationRequest.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[DBus (name = "org.freedesktop.impl.portal.Request")]
public class NotificationRequest : Object {
[DBus (visible = false)]
public signal void response (uint32 result);

public uint register_id { get; set; default = 0; }

private NotificationHandler handler;
private uint32 id;

public NotificationRequest (NotificationHandler handler, uint32 id) {
this.handler = handler;
this.id = id;
}

public void close () throws DBusError, IOError {
handler.close_notification (id);
}
}
162 changes: 162 additions & 0 deletions src/Background/Portal.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
* SPDX-FileCopyrightText: 2023 elementary, Inc. (https://elementary.io)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/

[DBus (name = "org.freedesktop.impl.portal.Background")]
public class Background.Portal : Object {
public signal void running_applications_changed ();

private DBusConnection connection;
private DesktopIntegration desktop_integration;
private NotificationHandler notification_handler;

public Portal (DBusConnection connection) {
this.connection = connection;
leolost2605 marked this conversation as resolved.
Show resolved Hide resolved
notification_handler = new NotificationHandler (connection);
try {
desktop_integration = connection.get_proxy_sync ("org.pantheon.gala", "/org/pantheon/gala/DesktopInterface");
desktop_integration.running_applications_changed.connect (() => running_applications_changed ());
} catch {
warning ("Cannot connect to compositor, background portal working with reduced functionality.");
}
}

[DBus (name = "org.pantheon.gala.DesktopIntegration")]
public interface DesktopIntegration : Object {
public struct RunningApplications {
string app_id;
HashTable<string,Variant> details;
}

public signal void running_applications_changed ();
public abstract RunningApplications[] get_running_applications () throws DBusError, IOError;
}

private enum ApplicationState {
BACKGROUND,
RUNNING,
ACTIVE
}

public HashTable<string, Variant> get_app_state () throws DBusError, IOError {
if (desktop_integration == null) {
throw new DBusError.FAILED ("No connection to compositor.");
}

var apps = desktop_integration.get_running_applications ();
var results = new HashTable<string, Variant> (null, null);
foreach (var app in apps) {
var app_id = app.app_id;
if (app_id.has_suffix (".desktop")) {
app_id = app_id.slice (0, app_id.last_index_of_char ('.'));
}

results[app_id] = ApplicationState.RUNNING; //FIXME: Don't hardcode: needs implementation on the gala side
}

return results;
}

public async void notify_background (
ObjectPath handle,
string app_id,
string name,
out uint32 response,
out HashTable<string, Variant> results
) throws DBusError, IOError {
response = 0; //Won't be used
var _results = new HashTable<string, Variant> (str_hash, str_equal);

var notification_request = notification_handler.send_notification (app_id, name);

if (notification_request == null) {
_results.set ("result", 2);
results = _results;
return;
}

notification_request.response.connect ((result) => {
_results.set ("result", result);
notify_background.callback ();
});

try {
notification_request.register_id = connection.register_object<NotificationRequest> (handle, notification_request);
} catch (Error e) {
critical ("Failed to export request object: %s", e.message);
}

yield;

connection.unregister_object (notification_request.register_id);
results = _results;
leolost2605 marked this conversation as resolved.
Show resolved Hide resolved
}

private enum AutostartFlags {
AUTOSTART_FLAGS_NONE,
AUTOSTART_FLAGS_DBUS_ACTIVATABLE
}
leolost2605 marked this conversation as resolved.
Show resolved Hide resolved

public bool enable_autostart (
string app_id,
bool enable,
string[] commandline,
uint32 flags
leolost2605 marked this conversation as resolved.
Show resolved Hide resolved
) throws DBusError, IOError {
/* If the portal request is made by a non-flatpak application app_id will most of the time be empty */
if (app_id.strip () == "") {
/* Usually we can then asume that the first commandline arg is the app_id */
if (commandline[0].strip () != "") {
app_id = commandline[0];
} else {
return false;
}
}

var path = Path.build_filename (Environment.get_user_config_dir (), "autostart", app_id + ".desktop");
leolost2605 marked this conversation as resolved.
Show resolved Hide resolved

if (!enable) {
FileUtils.unlink (path);
return false;
}

var autostart_flags = (AutostartFlags) flags;

var key_file = new KeyFile ();
key_file.set_string (KeyFileDesktop.GROUP, KeyFileDesktop.KEY_TYPE, "Application");
key_file.set_string (KeyFileDesktop.GROUP, KeyFileDesktop.KEY_NAME, app_id);
key_file.set_string (KeyFileDesktop.GROUP, KeyFileDesktop.KEY_EXEC, flatpak_quote_argv (commandline));
leolost2605 marked this conversation as resolved.
Show resolved Hide resolved
if (autostart_flags == AUTOSTART_FLAGS_DBUS_ACTIVATABLE) {
key_file.set_boolean (KeyFileDesktop.GROUP, KeyFileDesktop.KEY_DBUS_ACTIVATABLE, true);
}
key_file.set_string (KeyFileDesktop.GROUP, "X-Flatpak", app_id);
leolost2605 marked this conversation as resolved.
Show resolved Hide resolved

try {
key_file.save_to_file (path);
} catch (Error e) {
warning ("Failed to write autostart file: %s", e.message);
return false;
}

return true;
}

private string flatpak_quote_argv (string[] argv) {
var builder = new StringBuilder ();

foreach (var arg in argv) {
foreach (var c in (char[]) arg.data) {
if (!c.isalnum () && !(c.to_string () in "-/~:._=@")) {
arg = Shell.quote (arg);
break;
}
}

builder.append (arg);
builder.append (" ");
}

return builder.str.strip ();
}
}
3 changes: 3 additions & 0 deletions src/XdgDesktopPortalPantheon.vala
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ private void on_bus_acquired (DBusConnection connection, string name) {

connection.register_object ("/org/freedesktop/portal/desktop", new AppChooser.Portal (connection));
debug ("AppChooser Portal registered!");

connection.register_object ("/org/freedesktop/portal/desktop", new Background.Portal (connection));
debug ("Background Portal registered!");
} catch (Error e) {
critical ("Unable to register the object: %s", e.message);
}
Expand Down
3 changes: 3 additions & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ executable(
'AppChooser/AppButton.vala',
'AppChooser/Dialog.vala',
'AppChooser/Portal.vala',
'Background/NotificationHandler.vala',
'Background/NotificationRequest.vala',
'Background/Portal.vala',
configure_file(input: 'Config.vala.in', output: '@BASENAME@', configuration: conf_data),
'ExternalWindow.vala',
'XdgDesktopPortalPantheon.vala',
Expand Down