go-toast

Send toast notifications in Windows
Log | Files | Refs | README | LICENSE

registry.go (3143B)


      1 //go:build windows
      2 
      3 // This file contains registry manipulation code.
      4 // This logic is orthogonal to, but works in tandem with the COM code; since the
      5 // Windows Runtime uses the registry as it's primary source of state.
      6 package wintoast
      7 
      8 import (
      9 	"fmt"
     10 	"path/filepath"
     11 	"sync"
     12 
     13 	"golang.org/x/sys/windows/registry"
     14 )
     15 
     16 var (
     17 	// allows diffing the new call from the previous so that we can early-out,
     18 	// and avoid touching the registry more than necessary.
     19 	// It also allows empty app data to be supplied to the Notifcation type,
     20 	// without erasing the data that has been set via the global function.
     21 	appData   AppData
     22 	appDataMu sync.Mutex
     23 )
     24 
     25 // Overridden in testing.
     26 var (
     27 	writeStringValue = writeStringValueImpl
     28 	setAppDataFunc   = setAppDataImpl
     29 )
     30 
     31 var (
     32 	// appKeyRoot is the root path for app metadata.
     33 	appKeyRoot = filepath.Join("SOFTWARE", "Classes", "AppUserModelId")
     34 	// activationKey is the root path to the activation executable.
     35 	activationKey = filepath.Join("SOFTWARE", "Classes", "CLSID", GUID_ImplNotificationActivationCallback.String(), "LocalServer32")
     36 )
     37 
     38 // The Windows registry package uses empty string for the "(Default)" key.
     39 const registryDefaultKey string = ""
     40 
     41 func setAppDataImpl(data AppData) error {
     42 	if data.AppID == "" {
     43 		return fmt.Errorf("empty app ID")
     44 	}
     45 
     46 	appKey := filepath.Join(appKeyRoot, data.AppID)
     47 
     48 	if err := writeStringValue(appKey, "DisplayName", data.AppID); err != nil {
     49 		return err
     50 	}
     51 
     52 	// CustomActivator teaches Window what COM class to use as the callback when
     53 	// a toast notification is activated.
     54 	if err := writeStringValue(appKey, "CustomActivator", GUID_ImplNotificationActivationCallback.String()); err != nil {
     55 		return err
     56 	}
     57 
     58 	if data.IconPath != "" {
     59 		if err := writeStringValue(appKey, "IconUri", data.IconPath); err != nil {
     60 			return err
     61 		}
     62 	}
     63 
     64 	if data.IconBackgroundColor != "" {
     65 		if err := writeStringValue(appKey, "IconBackgroundColor", data.IconBackgroundColor); err != nil {
     66 			return err
     67 		}
     68 	}
     69 
     70 	if data.ActivationExe != "" {
     71 		if err := writeStringValue(activationKey, registryDefaultKey, data.ActivationExe); err != nil {
     72 			return fmt.Errorf("setting activation executable: %w", err)
     73 		}
     74 	}
     75 
     76 	return nil
     77 }
     78 
     79 // writeStringValue writes a string value to the path, where name is the subkey and
     80 // value is the literal value.
     81 func writeStringValueImpl(path, name, value string) error {
     82 	if keyExists(path, name) {
     83 		return nil
     84 	}
     85 	key, _, err := registry.CreateKey(registry.CURRENT_USER, path, registry.SET_VALUE)
     86 	if err != nil {
     87 		return fmt.Errorf("opening registry key: %s: %w", path, err)
     88 	}
     89 	if err := key.SetStringValue(name, value); err != nil {
     90 		return fmt.Errorf("setting string value: (%s) %s=%s: %w", path, name, value, err)
     91 	}
     92 	if err := key.Close(); err != nil {
     93 		return fmt.Errorf("closing key: %s: %w", path, err)
     94 	}
     95 	return nil
     96 }
     97 
     98 // keyExists returns true if the key exists.
     99 func keyExists(path, name string) bool {
    100 	key, err := registry.OpenKey(registry.CURRENT_USER, path, registry.READ)
    101 	if err != nil {
    102 		return false
    103 	}
    104 	defer key.Close()
    105 	v, _, err := key.GetStringValue(name)
    106 	if err != nil {
    107 		return false
    108 	}
    109 	return v != ""
    110 }