Skip to content

Commit

Permalink
Merge pull request #47 from slytomcat/dev
Browse files Browse the repository at this point in the history
pass notify icon as pixbuf via D-bus
  • Loading branch information
slytomcat authored Dec 31, 2024
2 parents e75fe54 + 6e5ffb8 commit ace0077
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 74 deletions.
24 changes: 5 additions & 19 deletions icons/icons.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package icons
import (
"context"
"fmt"
"os"
"sync"
"time"
)
Expand All @@ -12,7 +11,7 @@ var interval = time.Millisecond * 333

// Icon is the icon helper
type Icon struct {
NotifyIcon string // path to notification icon stored as file on disk
NotifyIcon []byte
lock sync.Mutex // data protection lock
currentStatus string
currentIcon int
Expand All @@ -28,29 +27,20 @@ type Icon struct {
// NewIcon initializes the icon helper and returns it.
// Use icon.CleanUp() for properly utilization of icon helper.
func NewIcon(theme string, set func([]byte)) (*Icon, error) {
file, err := os.CreateTemp("", "yd_notify_icon*.png")
if err != nil {
return nil, fmt.Errorf("icon store error: %v", err)
}
defer file.Close()
ctx, cancel := context.WithCancel(context.Background())
i := &Icon{
currentStatus: "",
currentIcon: 0,
NotifyIcon: file.Name(),
NotifyIcon: yd128,
setFunc: set,
ticker: time.NewTicker(interval),
stopper: cancel,
}
i.ticker.Stop()
if err = i.SetTheme(theme); err != nil {
if err := i.SetTheme(theme); err != nil {
return nil, err
}
i.setFunc(i.pauseIcon)
_, err = file.Write(yd128)
if err != nil {
return nil, fmt.Errorf("icon store error: %v", err)
}
go i.loop(ctx)
return i, nil
}
Expand Down Expand Up @@ -121,12 +111,8 @@ func (i *Icon) loop(ctx context.Context) {
}
}

// CleanUp removes temporary file for notification icon and stops internal loop
func (i *Icon) CleanUp() error {
// Close stops internal loop
func (i *Icon) Close() {
i.ticker.Stop()
i.stopper()
if err := os.Remove(i.NotifyIcon); err != nil {
return fmt.Errorf("icon remove error: %v", err)
}
return nil
}
16 changes: 4 additions & 12 deletions icons/icons_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestNewIcon(t *testing.T) {
i, err := NewIcon("dark", mi.set)
require.NoError(t, err)
require.NotNil(t, i)
defer i.CleanUp()
defer i.Close()
assert.Equal(t, darkPause, mi.get())
assert.Equal(t, darkError, i.errorIcon)
assert.Equal(t, darkIdle, i.idleIcon)
Expand All @@ -44,7 +44,7 @@ func TestNewIcon(t *testing.T) {
func TestSetTheme(t *testing.T) {
i, err := NewIcon("dark", mi.set)
require.NoError(t, err)
defer i.CleanUp()
defer i.Close()
assert.Equal(t, darkPause, mi.get())
i.SetTheme("light")
assert.Equal(t, darkPause, mi.get()) // current icon should not be changed if Set() was not called after NewIcon() as status is still unknown
Expand All @@ -62,7 +62,7 @@ func TestSet(t *testing.T) {
i, err := NewIcon("dark", mi.set)
require.NoError(t, err)
require.NotNil(t, i)
defer i.CleanUp()
defer i.Close()
i.Set("error")
assert.Equal(t, darkError, mi.get())
i.Set("idle")
Expand All @@ -84,7 +84,7 @@ func TestAnimation(t *testing.T) {
i, err := NewIcon("dark", mi.set)
require.NoError(t, err)
require.NotNil(t, i)
defer i.CleanUp()
defer i.Close()
i.Set("index")
assert.Equal(t, darkBusy1, mi.get())
assert.Eventually(t, event(darkBusy2), waitFor, tick)
Expand All @@ -99,11 +99,3 @@ func TestWrongTheme(t *testing.T) {
require.Error(t, err)
require.Nil(t, i)
}

func TestDoubleCleanUp(t *testing.T) {
i, err := NewIcon("dark", mi.set)
require.NoError(t, err)
require.NotNil(t, i)
require.NoError(t, i.CleanUp())
require.Error(t, i.CleanUp())
}
75 changes: 51 additions & 24 deletions notify/notify.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
package notify

import (
"bytes"
"image"
_ "image/png"
"sync/atomic"

"github.com/godbus/dbus/v5"
)

// Notify holds D-Bus connection and defaults for the notifications.
type Notify struct {
app string
icon string
replace bool
time int
conn *dbus.Conn
connObj dbus.BusObject
lastID uint32
app string
iconHints map[string]dbus.Variant
replace bool
time int
conn *dbus.Conn
connObj dbus.BusObject
lastID uint32
}

const (
Expand All @@ -24,21 +27,21 @@ const (

// New creates new Notify component.
// The application is the name of application.
// The defaultIcon is icon name/path to be used for notification when no icon specified during the Send call.
// The icon is png/ico image data to use in Send.
// True value of replace means that a new notification will replace the previous one if it is still displayed.
// The time sets the time in milliseconds after which the notification will disappear. Set it to -1 to use Desktop default settings.
func New(application, defaultIcon string, replace bool, time int) (*Notify, error) {
func New(application string, icon []byte, replace bool, time int) (*Notify, error) {
conn, err := dbus.ConnectSessionBus()
if err != nil {
return nil, err
}
notify := &Notify{
app: application,
icon: defaultIcon,
replace: replace,
time: time,
conn: conn,
connObj: conn.Object(dBusDest, dBusPath),
app: application,
iconHints: map[string]dbus.Variant{"image-data": dbus.MakeVariant(convertToPixels(icon))},
replace: replace,
time: time,
conn: conn,
connObj: conn.Object(dBusDest, dBusPath),
}
if _, err = notify.Cap(); err != nil {
return nil, err
Expand All @@ -51,22 +54,46 @@ func (n *Notify) Close() {
n.conn.Close()
}

// Send sends the desktop notification. If icon is not provided ("") then defaultIcon passed to New is used.
func (n *Notify) Send(icon, title, message string) {

if icon == "" {
icon = n.icon
}
// Send sends the desktop notification.
func (n *Notify) Send(title, message string) {
var last uint32
if n.replace {
last = atomic.LoadUint32(&n.lastID)
}
call := n.connObj.Call(dBusDest+".Notify", dbus.Flags(0),
n.app, last, icon, title, message, []string{}, map[string]interface{}{}, n.time)
call := n.connObj.Call(dBusDest+".Notify", dbus.Flags(0), n.app, last, "", title, message, []string{}, n.iconHints, n.time)
if call.Err == nil {
atomic.StoreUint32(&n.lastID, call.Body[0].(uint32))
}
// ignore any error as it can be called even New return error.
// ignore rest possible errors
}

type ImageData struct {
Width, Height, RowStride int32
HasAlpha bool
BitsPerSample, Channels int32
ImageData []byte
}

func convertToPixels(data []byte) ImageData {
src, _, err := image.Decode(bytes.NewReader(data))
if err != nil {
panic(err)
}
img := image.NewRGBA(src.Bounds())
for x := range src.Bounds().Dx() {
for y := range src.Bounds().Dy() {
img.Set(x, y, src.At(x, y))
}
}
return ImageData{
Width: int32(img.Bounds().Dx()),
Height: int32(img.Bounds().Dy()),
RowStride: int32(img.Stride),
HasAlpha: true,
BitsPerSample: 8,
Channels: 4,
ImageData: img.Pix,
}
}

// Cap returns the notification server capabilities
Expand Down
27 changes: 15 additions & 12 deletions notify/notify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,15 @@ func TestDBusNotify(t *testing.T) {
if os.Getenv("CI") != "" {
t.Skip("Skipping testing in CI environment")
}
icon := "dialog-information"
n, err := New("appName", "", true, -1)
// read icon
p, err := os.Getwd()
require.NoError(t, err)
p, _ = path.Split(p)
p += "/icons/img/yd128.png"
icon, err := os.ReadFile(p)
require.NoError(t, err)

n, err := New("appName", icon, true, -1)
require.NoError(t, err)
require.NotNil(t, n)
defer n.Close()
Expand All @@ -23,19 +30,15 @@ func TestDBusNotify(t *testing.T) {
require.NoError(t, err)
require.NotEmpty(t, cap)

n.Send(icon, "title", "message")
n.Send("title", "message")
time.Sleep(time.Second)
n.Send("dialog-error", "title1", "message1")
n.Send("title1", "message1")
time.Sleep(time.Second)
n.Send("dialog-warning", "title2", "message2")
n.Send("title2", "message2")
time.Sleep(time.Second)
n.Send(icon, "title3", "message3")
n.replace = false
n.Send("title3", "message3")
time.Sleep(time.Second)
n.Send("", "title4", "message4")
n.Send("title4", "message4")
time.Sleep(time.Second)
p, err := os.Getwd()
require.NoError(t, err)
p, _ = path.Split(p)
p += "/icons/img/yd128.png"
n.Send(p, "cool title", "cool message")
}
12 changes: 5 additions & 7 deletions yd.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func onReady() {
notifyAvailable = true
notifySend = func(title, body string) {
log.Debug("sending_message", "title", title, "message", body)
notifyHandler.Send("", title, body)
notifyHandler.Send(title, body)
}
}

Expand Down Expand Up @@ -197,13 +197,13 @@ func eventHandler(m *menu, cfg *tools.Config, YD *ydisk.YDisk, notifyHandler *no
go YD.Start()
}
defer func() {
if notifyHandler != nil {
notifyHandler.Close()
}
if cfg.StopDaemon {
YD.Stop()
}
YD.Close()
if notifyHandler != nil {
notifyHandler.Close()
}
systray.Quit()
}()
// register interrupt signals chan
Expand Down Expand Up @@ -363,8 +363,6 @@ func handleNotifications(yds *ydisk.YDvals) {

func onExit() {
appConfig.Save()
if err := icon.CleanUp(); err != nil {
log.Error("icon_cleanup", "error", err)
}
icon.Close()
log.Debug("exit", "status", "done")
}

0 comments on commit ace0077

Please sign in to comment.