1

I'm having a problem with DBus usage.
I'm trying to get a notification bubble with a few action buttons up and running.

So far, I've been able to get the bubble to show with

retValue = await proxy.NotifyAsync(

and get the button that has been pressed in the callback, with

await proxy.WatchActionInvokedAsync

See code below.

So now, because the notification returns before the response is received, I need to wait for the user's response (ManualResetEventSlim), before closing the connection.
That was creating a deadlock, which I solved by switching to AsyncManualResetEvent.

Now it works, kindof - i get the bubble, i get the response if a bubble-action is chosen (if clicked before the timeout - timeout not handled at present), but I also get an Exception passed in the onError-callback in WatchActionInvokedAsync.

Error callback: Cannot access a disposed object. Object name:
'Tmds.DBus.Connection'

Why do I get this error ?
Shouldn't await notifyResponseReceived.WaitAsync(); return before the connection is disposed ?
What could cause this error ?
In addition to that, why does the exception have an empty stacktrace ?

namespace NotificationTest
{


    // https://devblogs.microsoft.com/pfxteam/building-async-coordination-primitives-part-1-asyncmanualresetevent/
    public class AsyncManualResetEvent
    {
        private volatile System.Threading.Tasks.TaskCompletionSource<bool> m_tcs; 


        public AsyncManualResetEvent()
        {
            this.m_tcs = new System.Threading.Tasks.TaskCompletionSource<bool>();
        }


        public System.Threading.Tasks.Task WaitAsync() 
        { 
            return this.m_tcs.Task; 
        }


        //public void Set() 
        //{ 
        //    this.m_tcs.TrySetResult(true); 
        //}

        public void Set()
        {
            System.Threading.Tasks.TaskCompletionSource<bool> tcs = this.m_tcs;

            System.Threading.Tasks.Task.Factory.StartNew(s => ((System.Threading.Tasks.TaskCompletionSource<bool>)s).TrySetResult(true),
                tcs, System.Threading.CancellationToken.None, System.Threading.Tasks.TaskCreationOptions.PreferFairness, System.Threading.Tasks.TaskScheduler.Default);

            tcs.Task.Wait();
        }


        public void Reset()
        {
            while (true)
            {
                System.Threading.Tasks.TaskCompletionSource<bool> tcs = this.m_tcs;
                if (!tcs.Task.IsCompleted ||
                    System.Threading.Interlocked.CompareExchange(ref this.m_tcs, new System.Threading.Tasks.TaskCompletionSource<bool>(), tcs) == tcs)
                    return;
            } // Whend 

        } // End Sub Reset 


    } // End Class AsyncManualResetEvent



    class Program
    {


        // https://wiki.debianforum.de/Desktop-Notification_von_Systemservice_mittels_dbus
        // https://cheesehead-techblog.blogspot.com/2009/02/five-ways-to-make-notification-pop-up.html
        // https://wiki.debianforum.de/Desktop-Notification_von_Systemservice_mittels_dbus
        // https://gist.github.com/ducin/6152106
        // https://cweiske.de/tagebuch/DBus%20notify-send%20over%20network.htm
        // dotnet dbus list services --bus system | grep NetworkManager org.freedesktop.NetworkManager
        // dotnet dbus list objects --bus system --service org.freedesktop.NetworkManager
        // dotnet dbus codegen --bus system --service org.freedesktop.NetworkManager

        // cd ~/gitlab/Projects/NotificationTest/Tmds.DBus.Tool
        // dotnet run codegen --bus system --service org.freedesktop.NetworkManager
        // dotnet run codegen --bus session --service org.freedesktop.Notifications
        private static async System.Threading.Tasks.Task<uint> SendNotification()
        {
            uint retValue = 666;
            // System.Threading.ManualResetEventSlim notifyResponseReceived = new System.Threading.ManualResetEventSlim();
            AsyncManualResetEvent notifyResponseReceived = new AsyncManualResetEvent();

            Tmds.DBus.ObjectPath objectPath = new Tmds.DBus.ObjectPath("/org/freedesktop/Notifications");
            string service = "org.freedesktop.Notifications";

            using (Tmds.DBus.Connection connection = new Tmds.DBus.Connection(Tmds.DBus.Address.Session))
            {
                await connection.ConnectAsync();

                Notifications.DBus.INotifications proxy = connection.CreateProxy<Notifications.DBus.INotifications>(service, objectPath);

                // Task<IDisposable> WatchActionInvokedAsync(Action<(uint id, string actionKey)> handler, Action<Exception> onError = null);
                await proxy.WatchActionInvokedAsync(
                    delegate ((uint id, string actionKey) id)
                    {
                        if (id.id != retValue)
                        {
                            System.Console.WriteLine("abort");
                            return;
                        }

                        System.Console.WriteLine("Dialog Id: {0}", id.id);
                        System.Console.WriteLine($"ActionKey: {id.actionKey}");
                        notifyResponseReceived.Set();
                    }, delegate (System.Exception ex)
                    {
                        System.Console.Write("Error callback: ");
                        System.Console.WriteLine(ex.Message);
                        System.Console.WriteLine(ex.StackTrace);
                    }
                );


                // string[] actions = new string[0];
                string[] actions = new string[] { "0", "Cancel", "1", "No", "266789", "default", "3", "test" };

                System.Collections.Generic.Dictionary<string, object> hints =
                    new System.Collections.Generic.Dictionary<string, object>();

                string icon = "insert-image"; // # Siehe https://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html#names
                icon = "call-start";
                icon = "/root/Downloads/5d9ad698-8270-4141-b64e-736d3dbb9ecc.jpeg";
                icon = "dialog-information";
                icon = "dialog-error";
                icon = "dialog-warning";
                icon = "flag-ch";

                // https://developer.gnome.org/notification-spec/

                //await proxy.NotifyAsync("Notification", 0, icon, "summary", "body", actions, hints, 0);
                retValue = await proxy.NotifyAsync("Notifica1tion", 0, icon, "This is the summary", "This is the body", actions, hints, 5000);

                // notifyResponseReceived.Wait(); // blocks itselfs
                await notifyResponseReceived.WaitAsync();
            } // End Using connection 

            return retValue;
        } // End Task SendNotification 


        static void Main(string[] args)
        {
            System.Console.Write("Notification ID: ");
            System.Console.WriteLine(SendNotification().Result);
            System.Console.ReadKey();

            System.Console.WriteLine(" --- Press any key to continue --- ");
            System.Console.ReadKey();
        } // End Sub Main 


    } // End Class AsyncManualResetEvent 


} // End Namespace NotificationTest

The generated proxy-object (Notifications.DBus.INotifications) is here: https://pastebin.com/v40pyaFN just in case anybody wants it.

This code uses the Tmds.DBus library.

Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
  • Can you provide the complete stack trace of the exception? Can you provide a [complete, minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example)? – Paulo Morgado Jan 16 '20 at 08:35
  • @Paulo Morgado: I can't because the output of stacktrace is empty. ==> System.Console.WriteLine(ex.StackTrace); Amended the question with this information. – Stefan Steiger Jan 16 '20 at 09:58
  • If you debug it, doesn't Visual Studio show a stack trace in the stack trace window? – Paulo Morgado Jan 16 '20 at 17:35

0 Answers0