1//go:build linux
2
3package ui
4
5import (
6 "fmt"
7 "net/url"
8 "sync"
9
10 "github.com/godbus/dbus/v5"
11)
12
13var dbusConn *dbus.Conn
14var dbusLock sync.Mutex
15
16var notifications = make(map[int]*NotifyEvent)
17
18func notifyDBus(title, content string) int {
19 conn, err := dbus.SessionBus()
20 if err != nil {
21 return -1
22 }
23 var r uint32
24 obj := conn.Object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
25 err = obj.Call("org.freedesktop.Notifications.Notify", 0, "senpai", uint32(0), "senpai", title, content, []string{
26 "default", "Open",
27 }, map[string]dbus.Variant{
28 "category": dbus.MakeVariant("im.received"),
29 "desktop-entry": dbus.MakeVariant("senpai"),
30 "image-path": dbus.MakeVariant("senpai"),
31 "urgency": dbus.MakeVariant(uint8(1)), // Normal
32 }, int32(-1)).Store(&r)
33 if err != nil {
34 return -1
35 }
36 return int(r)
37}
38
39func (ui *UI) notify(target NotifyEvent, title, content string) int {
40 if ui.config.LocalIntegrations {
41 id := notifyDBus(title, content)
42 if id > 0 {
43 dbusLock.Lock()
44 notifications[id] = &target
45 dbusLock.Unlock()
46 return id
47 }
48 }
49
50 ui.vx.Notify(title, content)
51 return -1
52}
53
54func notifyClose(id int) {
55 conn, err := dbus.SessionBus()
56 if err != nil {
57 return
58 }
59 obj := conn.Object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
60 obj.Call("org.freedesktop.Notifications.CloseNotification", 0, uint32(id))
61}
62
63func Screenshot() error {
64 conn, err := dbus.SessionBus()
65 if err != nil {
66 return fmt.Errorf("failed to connect to D-Bus: %v", err)
67 }
68 obj := conn.Object("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop")
69 c := obj.Call("org.freedesktop.portal.Screenshot.Screenshot", 0, "", map[string]dbus.Variant{
70 "modal": dbus.MakeVariant(true),
71 "interactive": dbus.MakeVariant(true),
72 })
73 if c.Err != nil {
74 return fmt.Errorf("failed to take screenshot: %v", c.Err)
75 }
76 return nil
77}
78
79func DBusStart(callback func(any)) {
80 conn, err := dbus.SessionBus()
81 if err != nil {
82 return
83 }
84 if err := conn.AddMatchSignal(
85 dbus.WithMatchObjectPath("/org/freedesktop/Notifications"),
86 dbus.WithMatchInterface("org.freedesktop.Notifications"),
87 dbus.WithMatchSender("org.freedesktop.Notifications.ActionInvoked"),
88 ); err != nil {
89 return
90 }
91 if err := conn.AddMatchSignal(
92 dbus.WithMatchObjectPath("/org/freedesktop/portal/desktop"),
93 dbus.WithMatchInterface("org.freedesktop.portal.Request"),
94 dbus.WithMatchMember("Response"),
95 ); err != nil {
96 return
97 }
98 c := make(chan *dbus.Signal, 64)
99 conn.Signal(c)
100 dbusLock.Lock()
101 dbusConn = conn
102 dbusLock.Unlock()
103 go func() {
104 for v := range c {
105 switch v.Name {
106 case "org.freedesktop.Notifications.NotificationClosed":
107 id := int(v.Body[0].(uint32))
108 dbusLock.Lock()
109 delete(notifications, id)
110 dbusLock.Unlock()
111 case "org.freedesktop.Notifications.ActionInvoked":
112 id := int(v.Body[0].(uint32))
113 dbusLock.Lock()
114 target, ok := notifications[id]
115 dbusLock.Unlock()
116 if ok {
117 callback(target)
118 }
119 case "org.freedesktop.portal.Request.Response":
120 status := v.Body[0].(uint32)
121 results := v.Body[1].(map[string]dbus.Variant)
122 if status == 0 /* success */ {
123 uri := results["uri"].Value().(string)
124 if u, err := url.Parse(uri); err == nil && u.Scheme == "file" {
125 callback(&ScreenshotEvent{
126 Path: u.Path,
127 })
128 }
129 }
130 }
131 }
132 }()
133}
134
135func DBusStop() {
136 dbusLock.Lock()
137 c := dbusConn
138 dbusConn = nil
139 dbusLock.Unlock()
140 if c != nil {
141 c.Close()
142 }
143}