Files
GitHub Actions c1a0fe2949 Switch remote deploy to vendored source builds
Move remote deployment to a vendored source bundle built on the target host via Docker so redeploys no longer require local cross-compilation or host Go installation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 12:19:18 +08:00

111 lines
3.1 KiB
Go

//go:build windows
// This file contains registry manipulation code.
// This logic is orthogonal to, but works in tandem with the COM code; since the
// Windows Runtime uses the registry as it's primary source of state.
package wintoast
import (
"fmt"
"path/filepath"
"sync"
"golang.org/x/sys/windows/registry"
)
var (
// allows diffing the new call from the previous so that we can early-out,
// and avoid touching the registry more than necessary.
// It also allows empty app data to be supplied to the Notifcation type,
// without erasing the data that has been set via the global function.
appData AppData
appDataMu sync.Mutex
)
// Overridden in testing.
var (
writeStringValue = writeStringValueImpl
setAppDataFunc = setAppDataImpl
)
var (
// appKeyRoot is the root path for app metadata.
appKeyRoot = filepath.Join("SOFTWARE", "Classes", "AppUserModelId")
// activationKey is the root path to the activation executable.
activationKey = filepath.Join("SOFTWARE", "Classes", "CLSID", GUID_ImplNotificationActivationCallback.String(), "LocalServer32")
)
// The Windows registry package uses empty string for the "(Default)" key.
const registryDefaultKey string = ""
func setAppDataImpl(data AppData) error {
if data.AppID == "" {
return fmt.Errorf("empty app ID")
}
appKey := filepath.Join(appKeyRoot, data.AppID)
if err := writeStringValue(appKey, "DisplayName", data.AppID); err != nil {
return err
}
// CustomActivator teaches Window what COM class to use as the callback when
// a toast notification is activated.
if err := writeStringValue(appKey, "CustomActivator", GUID_ImplNotificationActivationCallback.String()); err != nil {
return err
}
if data.IconPath != "" {
if err := writeStringValue(appKey, "IconUri", data.IconPath); err != nil {
return err
}
}
if data.IconBackgroundColor != "" {
if err := writeStringValue(appKey, "IconBackgroundColor", data.IconBackgroundColor); err != nil {
return err
}
}
if data.ActivationExe != "" {
if err := writeStringValue(activationKey, registryDefaultKey, data.ActivationExe); err != nil {
return fmt.Errorf("setting activation executable: %w", err)
}
}
return nil
}
// writeStringValue writes a string value to the path, where name is the subkey and
// value is the literal value.
func writeStringValueImpl(path, name, value string) error {
if keyExists(path, name) {
return nil
}
key, _, err := registry.CreateKey(registry.CURRENT_USER, path, registry.SET_VALUE)
if err != nil {
return fmt.Errorf("opening registry key: %s: %w", path, err)
}
if err := key.SetStringValue(name, value); err != nil {
return fmt.Errorf("setting string value: (%s) %s=%s: %w", path, name, value, err)
}
if err := key.Close(); err != nil {
return fmt.Errorf("closing key: %s: %w", path, err)
}
return nil
}
// keyExists returns true if the key exists.
func keyExists(path, name string) bool {
key, err := registry.OpenKey(registry.CURRENT_USER, path, registry.READ)
if err != nil {
return false
}
defer key.Close()
v, _, err := key.GetStringValue(name)
if err != nil {
return false
}
return v != ""
}