MaikuB / flutter_local_notifications

A Flutter plugin for displaying local notifications on Android, iOS, macOS and Linux
2.44k stars 1.39k forks source link

[flutter_local_notifications_linux] Add support for org.freedesktop.portal.Notification #1757

Open emersion opened 1 year ago

emersion commented 1 year ago

Inside a Flatpak sandbox, org.freedesktop.Notifications is not available. Sandboxed apps are supposed to talk to org.freedesktop.portal.Notification instead.

https://docs.flatpak.org/en/latest/portal-api-reference.html#gdbus-org.freedesktop.portal.Notification

MaikuB commented 1 year ago

Support for this would require a PR as Linux support came from a PR from @proninyaroslav so you would need to submit a PR for it. In saying that, I'm not even sure if a PR would be appropriate. Perhaps this is due to my limited knowledge of Linux (note: I don't work with the platform/OS) but even Canonical's own package for notifications uses org.freedesktop.Notifications. See https://github.com/canonical/desktop_notifications.dart/blob/main/lib/src/notifications_client.dart. This may mean you'll need to have your own solution for this as if I understood correctly, what you've linked to is a different specification altogether

proninyaroslav commented 1 year ago

Is org.freedesktop.portal.Notification backward compatible with org.freedesktop.Notifications? What are the minimum supported DE and distro versions it covers compared to org.freedesktop.Notifications?

Merrit commented 1 year ago

While the desktop portals started as an interface specifically for Flatpak, they appear to have been adopted and supported much more widely now.

It looks as though the Notification portal has been available since the initial 0.1 release of xdg-desktop-portal.

xdg-desktop-portal is supported at least as far back as:

So I think it is safe to say it has wide and time-tested adoption.

I tried running the specification through the dbus generator:

// This file was generated using the following command and may be overwritten.
// dart-dbus generate-remote-object org.freedesktop.portal.Notification.xml

import 'package:dbus/dbus.dart';

/// Signal data for org.freedesktop.portal.Notification.ActionInvoked.
class OrgFreedesktopPortalNotificationActionInvoked extends DBusSignal {
  String get id => values[0].asString();
  String get action => values[1].asString();
  List<DBusValue> get parameter => values[2].asVariantArray().toList();

  OrgFreedesktopPortalNotificationActionInvoked(DBusSignal signal) : super(sender: signal.sender, path: signal.path, interface: signal.interface, name: signal.name, values: signal.values);
}

class OrgFreedesktopPortalNotification extends DBusRemoteObject {
  /// Stream of org.freedesktop.portal.Notification.ActionInvoked signals.
  late final Stream<OrgFreedesktopPortalNotificationActionInvoked> actionInvoked;

  OrgFreedesktopPortalNotification(DBusClient client, String destination, {DBusObjectPath path = const DBusObjectPath.unchecked('/')}) : super(client, name: destination, path: path) {
    actionInvoked = DBusRemoteObjectSignalStream(object: this, interface: 'org.freedesktop.portal.Notification', name: 'ActionInvoked', signature: DBusSignature('ssav')).asBroadcastStream().map((signal) => OrgFreedesktopPortalNotificationActionInvoked(signal));
  }

  /// Gets org.freedesktop.portal.Notification.version
  Future<int> getversion() async {
    var value = await getProperty('org.freedesktop.portal.Notification', 'version', signature: DBusSignature('u'));
    return value.asUint32();
  }

  /// Invokes org.freedesktop.portal.Notification.AddNotification()
  Future<void> callAddNotification(String id, Map<String, DBusValue> notification, {bool noAutoStart = false, bool allowInteractiveAuthorization = false}) async {
    await callMethod('org.freedesktop.portal.Notification', 'AddNotification', [DBusString(id), DBusDict.stringVariant(notification)], replySignature: DBusSignature(''), noAutoStart: noAutoStart, allowInteractiveAuthorization: allowInteractiveAuthorization);
  }

  /// Invokes org.freedesktop.portal.Notification.RemoveNotification()
  Future<void> callRemoveNotification(String id, {bool noAutoStart = false, bool allowInteractiveAuthorization = false}) async {
    await callMethod('org.freedesktop.portal.Notification', 'RemoveNotification', [DBusString(id)], replySignature: DBusSignature(''), noAutoStart: noAutoStart, allowInteractiveAuthorization: allowInteractiveAuthorization);
  }
}

I tested it only briefly, but it seems to work well even outside of Flatpak confines:


late OrgFreedesktopPortalNotification notificationPortal;

Future<void> main() async {
  final client = DBusClient.session();
  notificationPortal = OrgFreedesktopPortalNotification(
    client,
    'org.freedesktop.portal.Desktop',
    path: DBusObjectPath('/org/freedesktop/portal/desktop'),
  );

  // Add a notification
  await addNotification(
    'app_name',
    {
      'title': DBusString('Test Title'),
      'body': DBusString('Test Body'),
    },
  );

  // Close the client
  await client.close();
}

// AddNotification
Future<void> addNotification(
  String id,
  Map<String, DBusValue> notification,
) async {
  await notificationPortal.callAddNotification(
    id,
    notification,
  );
}

Seems like since it works basically everywhere and is a modern notifications API on Linux it would be good to transition to. Thoughts? :)

MaikuB commented 1 year ago

My knowledge of Linux is limited so with what I know and if it doesn't cause breaking changes or limit supported Linux versions that those who were using the plugin before end up not being able to do so then that sounds reasonable to me