github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/desktop/notification/notificationtest/fdo.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2021 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package notificationtest
    21  
    22  import (
    23  	"fmt"
    24  	"sort"
    25  	"sync"
    26  
    27  	"github.com/godbus/dbus"
    28  
    29  	"github.com/snapcore/snapd/dbusutil"
    30  )
    31  
    32  const (
    33  	fdoBusName    = "org.freedesktop.Notifications"
    34  	fdoObjectPath = "/org/freedesktop/Notifications"
    35  	fdoInterface  = "org.freedesktop.Notifications"
    36  )
    37  
    38  type FdoNotification struct {
    39  	ID      uint32
    40  	AppName string
    41  	Icon    string
    42  	Summary string
    43  	Body    string
    44  	Actions []string
    45  	Hints   map[string]dbus.Variant
    46  	Expires int32
    47  }
    48  
    49  type FdoServer struct {
    50  	conn *dbus.Conn
    51  	err  *dbus.Error
    52  
    53  	mu            sync.Mutex
    54  	lastID        uint32
    55  	notifications map[uint32]*FdoNotification
    56  }
    57  
    58  func NewFdoServer() (*FdoServer, error) {
    59  	conn, err := dbusutil.SessionBusPrivate()
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	server := &FdoServer{
    65  		conn:          conn,
    66  		notifications: make(map[uint32]*FdoNotification),
    67  	}
    68  	conn.Export(fdoApi{server}, fdoObjectPath, fdoInterface)
    69  
    70  	reply, err := conn.RequestName(fdoBusName, dbus.NameFlagDoNotQueue)
    71  	if err != nil {
    72  		conn.Close()
    73  		return nil, err
    74  	}
    75  
    76  	if reply != dbus.RequestNameReplyPrimaryOwner {
    77  		conn.Close()
    78  		return nil, fmt.Errorf("cannot obtain bus name %q", fdoBusName)
    79  	}
    80  	return server, nil
    81  }
    82  
    83  func (server *FdoServer) Stop() error {
    84  	if _, err := server.conn.ReleaseName(fdoBusName); err != nil {
    85  		return err
    86  	}
    87  	return server.conn.Close()
    88  }
    89  
    90  // SetError sets an error to be returned by the D-Bus interface.
    91  //
    92  // If not nil, all the fdoApi methods will return the provided error
    93  // in place of performing their usual task.
    94  func (server *FdoServer) SetError(err *dbus.Error) {
    95  	server.err = err
    96  }
    97  
    98  func (server *FdoServer) Get(id uint32) *FdoNotification {
    99  	server.mu.Lock()
   100  	defer server.mu.Unlock()
   101  
   102  	return server.notifications[id]
   103  }
   104  
   105  func (server *FdoServer) GetAll() []*FdoNotification {
   106  	server.mu.Lock()
   107  	defer server.mu.Unlock()
   108  
   109  	notifications := make([]*FdoNotification, 0, len(server.notifications))
   110  	for _, n := range server.notifications {
   111  		notifications = append(notifications, n)
   112  	}
   113  	sort.Slice(notifications, func(i, j int) bool {
   114  		return notifications[i].ID < notifications[j].ID
   115  	})
   116  	return notifications
   117  }
   118  
   119  func (server *FdoServer) Close(id, reason uint32) error {
   120  	server.mu.Lock()
   121  	defer server.mu.Unlock()
   122  
   123  	if _, ok := server.notifications[id]; !ok {
   124  		return fmt.Errorf("No such notification: %d", id)
   125  	}
   126  	delete(server.notifications, id)
   127  	return server.conn.Emit(fdoObjectPath, fdoInterface+".NotificationClosed", id, reason)
   128  }
   129  
   130  func (server *FdoServer) InvokeAction(id uint32, actionKey string) error {
   131  	return server.conn.Emit(fdoObjectPath, fdoInterface+".ActionInvoked", id, actionKey)
   132  }
   133  
   134  type fdoApi struct {
   135  	server *FdoServer
   136  }
   137  
   138  func (a fdoApi) GetCapabilities() ([]string, *dbus.Error) {
   139  	if a.server.err != nil {
   140  		return nil, a.server.err
   141  	}
   142  
   143  	return []string{"cap-foo", "cap-bar"}, nil
   144  }
   145  
   146  func (a fdoApi) Notify(appName string, replacesID uint32, icon, summary, body string, actions []string, hints map[string]dbus.Variant, expires int32) (uint32, *dbus.Error) {
   147  	if a.server.err != nil {
   148  		return 0, a.server.err
   149  	}
   150  
   151  	a.server.mu.Lock()
   152  	defer a.server.mu.Unlock()
   153  
   154  	a.server.lastID += 1
   155  	notification := &FdoNotification{
   156  		ID:      a.server.lastID,
   157  		AppName: appName,
   158  		Icon:    icon,
   159  		Summary: summary,
   160  		Body:    body,
   161  		Actions: actions,
   162  		Hints:   hints,
   163  		Expires: expires,
   164  	}
   165  	if replacesID != 0 {
   166  		delete(a.server.notifications, replacesID)
   167  	}
   168  	a.server.notifications[notification.ID] = notification
   169  
   170  	return notification.ID, nil
   171  }
   172  
   173  func (a fdoApi) CloseNotification(id uint32) *dbus.Error {
   174  	if a.server.err != nil {
   175  		return a.server.err
   176  	}
   177  
   178  	// close reason 3 is "closed by a call to CloseNotification"
   179  	// https://specifications.freedesktop.org/notification-spec/latest/ar01s09.html#signal-notification-closed
   180  	if err := a.server.Close(id, 3); err != nil {
   181  		return &dbus.Error{
   182  			Name: "org.freedesktop.DBus.Error.Failed",
   183  			Body: []interface{}{err.Error()},
   184  		}
   185  	}
   186  	return nil
   187  }
   188  
   189  func (a fdoApi) GetServerInformation() (name, vendor, version, specVersion string, err *dbus.Error) {
   190  	if a.server.err != nil {
   191  		return "", "", "", "", a.server.err
   192  	}
   193  
   194  	return "name", "vendor", "version", "specVersion", nil
   195  }