feat: Windows notifications, benchmarks, GPG signing, docs site, one-liner installers
This commit is contained in:
@@ -70,3 +70,22 @@ func TestDIBRejectsUnsupportedCompression(t *testing.T) {
|
||||
t.Error("expected error for unsupported compression, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDIBRoundTrip(b *testing.B) {
|
||||
img := image.NewRGBA(image.Rect(0, 0, 256, 256))
|
||||
var buf bytes.Buffer
|
||||
_ = png.Encode(&buf, img)
|
||||
pngData := buf.Bytes()
|
||||
|
||||
dib, err := pngToDIB(pngData)
|
||||
if err != nil {
|
||||
b.Fatalf("pngToDIB: %v", err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := dibToPNG(dib); err != nil {
|
||||
b.Fatalf("dibToPNG: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package dedup
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func BenchmarkShouldIgnoreNewContent(b *testing.B) {
|
||||
f := NewFilter("machine-a")
|
||||
payload := Payload{
|
||||
Text: "benchmark content string",
|
||||
Ts: time.Now().UnixNano(),
|
||||
Origin: "machine-b",
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = f.ShouldIgnore(payload)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkShouldIgnoreImage(b *testing.B) {
|
||||
f := NewFilter("machine-a")
|
||||
payload := Payload{
|
||||
Mime: "image/png",
|
||||
Data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
||||
Ts: time.Now().UnixNano(),
|
||||
Origin: "machine-b",
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = f.ShouldIgnore(payload)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarkWritten(b *testing.B) {
|
||||
f := NewFilter("machine-a")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
f.MarkWritten("some clipboard text")
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,161 @@
|
||||
|
||||
package notify
|
||||
|
||||
// Notifier is a no-op on Windows. Native toast notifications require a
|
||||
// registered, packaged app (or a third-party module such as BurntToast), which
|
||||
// is out of scope for a single self-contained binary.
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// notifier shows balloon notifications via Shell_NotifyIconW. This is a
|
||||
// self-contained approach (no Start Menu shortcut or AppID registration
|
||||
// required), unlike WinRT toast notifications. The balloon appears in the
|
||||
// notification area and a small tray icon is kept for the daemon's lifetime.
|
||||
type notifier struct{}
|
||||
|
||||
// New returns a no-op Windows notifier.
|
||||
// New returns a Windows notifier using Shell_NotifyIcon balloon tips.
|
||||
func New() Notifier { return notifier{} }
|
||||
|
||||
func (notifier) Notify(string, string) error { return nil }
|
||||
const (
|
||||
nifInfo = 0x00000010
|
||||
nimAdd = 0x00000000
|
||||
nimModify = 0x00000001
|
||||
niiInfo = 0x00000001
|
||||
)
|
||||
|
||||
// hwndMessage is the HWND_MESSAGE (-3) pseudo-handle for a message-only window.
|
||||
const hwndMessage = ^uintptr(2)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
user32 = syscall.NewLazyDLL("user32.dll")
|
||||
shell32 = syscall.NewLazyDLL("shell32.dll")
|
||||
|
||||
procGetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
|
||||
procRegisterClassExW = user32.NewProc("RegisterClassExW")
|
||||
procCreateWindowExW = user32.NewProc("CreateWindowExW")
|
||||
procDefWindowProcW = user32.NewProc("DefWindowProcW")
|
||||
procShellNotifyIconW = shell32.NewProc("Shell_NotifyIconW")
|
||||
)
|
||||
|
||||
type wndClassExW struct {
|
||||
cbSize uint32
|
||||
style uint32
|
||||
lpfnWndProc uintptr
|
||||
cbClsExtra int32
|
||||
cbWndExtra int32
|
||||
hInstance uintptr
|
||||
hIcon uintptr
|
||||
hCursor uintptr
|
||||
hbrBackground uintptr
|
||||
lpszMenuName *uint16
|
||||
lpszClassName *uint16
|
||||
hIconSm uintptr
|
||||
}
|
||||
|
||||
type notifyIconDataW struct {
|
||||
cbSize uint32
|
||||
hWnd uintptr
|
||||
uID uint32
|
||||
uFlags uint32
|
||||
uCallbackMessage uint32
|
||||
hIcon uintptr
|
||||
szTip [128]uint16
|
||||
dwState uint32
|
||||
dwStateMask uint32
|
||||
szInfo [256]uint16
|
||||
uTimeout uint32
|
||||
szInfoTitle [64]uint16
|
||||
dwInfoFlags uint32
|
||||
guidItem [16]byte
|
||||
hBalloonIcon uintptr
|
||||
}
|
||||
|
||||
var (
|
||||
notifyOnce sync.Once
|
||||
notifyHwnd uintptr
|
||||
notifyErr error
|
||||
|
||||
iconMu sync.Mutex
|
||||
iconAdded bool
|
||||
)
|
||||
|
||||
func setup() {
|
||||
hInstance, _, _ := procGetModuleHandleW.Call(0)
|
||||
if hInstance == 0 {
|
||||
notifyErr = syscall.EINVAL
|
||||
return
|
||||
}
|
||||
|
||||
className, _ := syscall.UTF16PtrFromString("clip-sync-notify")
|
||||
wc := wndClassExW{
|
||||
cbSize: uint32(unsafe.Sizeof(wndClassExW{})),
|
||||
lpfnWndProc: procDefWindowProcW.Addr(),
|
||||
hInstance: hInstance,
|
||||
lpszClassName: className,
|
||||
}
|
||||
// Registration may fail if already registered (e.g. re-run) — that's fine.
|
||||
_, _, _ = procRegisterClassExW.Call(uintptr(unsafe.Pointer(&wc)))
|
||||
|
||||
hwnd, _, _ := procCreateWindowExW.Call(
|
||||
0,
|
||||
uintptr(unsafe.Pointer(className)),
|
||||
uintptr(unsafe.Pointer(className)),
|
||||
0,
|
||||
0, 0, 0, 0,
|
||||
hwndMessage,
|
||||
0, hInstance, 0,
|
||||
)
|
||||
notifyHwnd = hwnd
|
||||
if hwnd == 0 {
|
||||
notifyErr = fmt.Errorf("notify: CreateWindowExW failed")
|
||||
}
|
||||
}
|
||||
|
||||
func (notifier) Notify(title, body string) error {
|
||||
notifyOnce.Do(setup)
|
||||
if notifyErr != nil {
|
||||
return notifyErr
|
||||
}
|
||||
|
||||
var nid notifyIconDataW
|
||||
nid.cbSize = uint32(unsafe.Sizeof(nid))
|
||||
nid.hWnd = notifyHwnd
|
||||
nid.uID = 1
|
||||
nid.uFlags = nifInfo
|
||||
nid.uTimeout = 5000
|
||||
nid.dwInfoFlags = niiInfo
|
||||
|
||||
if info, err := syscall.UTF16FromString(body); err == nil {
|
||||
copy(nid.szInfo[:], info)
|
||||
}
|
||||
if title16, err := syscall.UTF16FromString(title); err == nil {
|
||||
copy(nid.szInfoTitle[:], title16)
|
||||
}
|
||||
|
||||
iconMu.Lock()
|
||||
defer iconMu.Unlock()
|
||||
|
||||
cmd := uintptr(nimAdd)
|
||||
if iconAdded {
|
||||
cmd = nimModify
|
||||
}
|
||||
r, _, _ := procShellNotifyIconW.Call(cmd, uintptr(unsafe.Pointer(&nid)))
|
||||
if r != 0 {
|
||||
iconAdded = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fall back to the other command on failure (e.g. icon already/not yet present).
|
||||
other := uintptr(nimAdd)
|
||||
if cmd == nimAdd {
|
||||
other = nimModify
|
||||
}
|
||||
r, _, _ = procShellNotifyIconW.Call(other, uintptr(unsafe.Pointer(&nid)))
|
||||
if r != 0 {
|
||||
iconAdded = true
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("notify: Shell_NotifyIconW failed")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"git.dracodev.net/Projets/clip-sync/internal/dedup"
|
||||
)
|
||||
|
||||
func BenchmarkHandleClipText(b *testing.B) {
|
||||
cb := &mockClipboard{}
|
||||
srv := newTestServer(cb, Options{MaxBodyBytes: 1 << 20})
|
||||
|
||||
body, _ := json.Marshal(dedup.Payload{
|
||||
Text: "benchmark clipboard text", Ts: 1690000000000000000, Origin: "machine-b",
|
||||
})
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
req := httptest.NewRequest(http.MethodPost, "/clip", bytes.NewReader(body))
|
||||
w := httptest.NewRecorder()
|
||||
srv.handleClip(w, req)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHandleClipImage(b *testing.B) {
|
||||
cb := &mockClipboard{}
|
||||
srv := newTestServer(cb, Options{MaxBodyBytes: 1 << 20})
|
||||
|
||||
body := []byte(`{"mime":"image/png","data":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==","ts":1,"origin":"machine-b"}`)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
req := httptest.NewRequest(http.MethodPost, "/clip", bytes.NewReader(body))
|
||||
w := httptest.NewRecorder()
|
||||
srv.handleClip(w, req)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user