github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/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 }