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 }