impl.go (5838B)
1 //go:build windows 2 3 // This file contains our pure-Go implementations of two COM objects that we need 4 // to render toast notifications: IClassFactory and INotificationActivationCallback. 5 // 6 // More specifically we allocate the C callable functions that can be used to populate 7 // the vtable at runtime. 8 // 9 // Unfortunately these functions have to be declared as var not const because the callbacks 10 // are built at runtime. They are declared globally because `syscall.NewCallback` never 11 // releases the memory it allocates for the functions thus causing an unsolvable memory 12 // leak if we were to allocate these per-notification. 13 // 14 // The other COM interfaces we are interacting with are auto-generated from metadata. 15 // However the INotificationActivationCallback is undocumented, so we have to define 16 // it entirely ourselves. 17 // 18 // The definitions are derived from: 19 // - <combase.h> 20 // - <NotificationActivationCallback.h> 21 package wintoast 22 23 import ( 24 "runtime" 25 "syscall" 26 "unsafe" 27 28 "github.com/go-ole/go-ole" 29 "golang.org/x/sys/windows" 30 ) 31 32 // Interface GUIDS. These GUIDS are predefined by the Windows Runtime, identifying the various 33 // interfaces we want to make use of. 34 var ( 35 IID_IClassFactory = ole.NewGUID("{00000001-0000-0000-C000-000000000046}") 36 IID_INotificationActivationCallback = ole.NewGUID("{53E31837-6600-4A81-9395-75CFFE746F94}") 37 ) 38 39 // This default GUID is for our implementation. 40 // This was generated and should not collide with any other GUID. 41 // It's preferable for the application to override this value with its own generated GUID. 42 var GUID_ImplNotificationActivationCallback = ole.NewGUID("{0F82E845-CB89-4039-BDBF-67CA33254C76}") 43 44 type ( 45 // IClassFactory defines the factory that builds our INotificationActivationCallback instance. 46 // Windows Runtime loves factories. 47 IClassFactory struct { 48 VTable *IClassFactoryVtbl 49 } 50 51 IClassFactoryVtbl struct { 52 ole.IUnknownVtbl 53 CreateInstance uintptr 54 LockServer uintptr 55 } 56 ) 57 58 type ( 59 // INotificationActivationCallback receives activations from toast notifications. 60 INotificationActivationCallback struct { 61 VTable *INotificationActivationCallbackVtbl 62 } 63 64 INotificationActivationCallbackVtbl struct { 65 ole.IUnknownVtbl 66 Activate uintptr 67 } 68 ) 69 70 /* 71 Strictly speaking we shouldn't need to pin the static objects. They 72 are package-globals and wont be garabge collected. No harm in being 73 extra careful, though. 74 */ 75 76 var pinner runtime.Pinner 77 78 func init() { 79 pinner.Pin(ClassFactory) 80 pinner.Pin(ClassFactory.VTable) 81 pinner.Pin(NotificationActivationCallback) 82 pinner.Pin(NotificationActivationCallback.VTable) 83 } 84 85 // Static implementations for the IClassFactory. 86 var ( 87 ClassFactory = &IClassFactory{ 88 VTable: &IClassFactoryVtbl{ 89 IUnknownVtbl: ole.IUnknownVtbl{ 90 QueryInterface: IClassFactory_QueryInterface, 91 AddRef: IClassFactory_AddRef, 92 Release: IClassFactory_Release, 93 }, 94 LockServer: IClassFactory_LockServer, 95 CreateInstance: IClassFactory_CreateInstance, 96 }, 97 } 98 99 IClassFactory_AddRef = syscall.NewCallback(func(this *IClassFactory) (re uintptr) { 100 return uintptr(1) 101 }) 102 103 IClassFactory_Release = syscall.NewCallback(func(this *IClassFactory) (re uintptr) { 104 return uintptr(1) 105 }) 106 107 IClassFactory_QueryInterface = syscall.NewCallback(func(this *IClassFactory, riid *ole.GUID, out unsafe.Pointer) (re uintptr) { 108 if !ole.IsEqualGUID(riid, IID_IClassFactory) && 109 !ole.IsEqualGUID(riid, ole.IID_IUnknown) { 110 return ole.E_NOINTERFACE 111 } 112 *(**IClassFactory)(out) = this 113 return ole.S_OK 114 }) 115 116 IClassFactory_LockServer = syscall.NewCallback(func(this *IClassFactory, flock uintptr) (ret uintptr) { 117 return ole.S_OK 118 }) 119 120 IClassFactory_CreateInstance = syscall.NewCallback(func(this *IClassFactory, punkOuter *ole.IUnknown, riid *ole.GUID, out unsafe.Pointer) (re uintptr) { 121 if punkOuter != nil { 122 // Should be CLASS_E_NOAGGREGATION but ole doesn't define this. 123 return ole.E_NOINTERFACE 124 } 125 if !ole.IsEqualGUID(riid, IID_INotificationActivationCallback) && 126 !ole.IsEqualGUID(riid, ole.IID_IUnknown) { 127 return ole.E_NOINTERFACE 128 } 129 *(**INotificationActivationCallback)(out) = NotificationActivationCallback 130 return ole.S_OK 131 }) 132 ) 133 134 // Static implementations for the INotificationActivationCallback. 135 var ( 136 NotificationActivationCallback = &INotificationActivationCallback{ 137 VTable: &INotificationActivationCallbackVtbl{ 138 IUnknownVtbl: ole.IUnknownVtbl{ 139 QueryInterface: INotificationActivationCallback_QueryInterface, 140 AddRef: INotificationActivationCallback_AddRef, 141 Release: INotificationActivationCallback_Release, 142 }, 143 Activate: INotificationActivationCallback_Activate, 144 }, 145 } 146 147 INotificationActivationCallback_AddRef = syscall.NewCallback(func(this *INotificationActivationCallback) (re uintptr) { 148 return uintptr(1) 149 }) 150 151 INotificationActivationCallback_Release = syscall.NewCallback(func(this *INotificationActivationCallback) (re uintptr) { 152 return uintptr(1) 153 }) 154 155 INotificationActivationCallback_QueryInterface = syscall.NewCallback(func(this *INotificationActivationCallback, riid *ole.GUID, out unsafe.Pointer) (re uintptr) { 156 if !ole.IsEqualGUID(riid, IID_INotificationActivationCallback) && 157 !ole.IsEqualGUID(riid, ole.IID_IUnknown) { 158 return ole.E_NOINTERFACE 159 } 160 *(**INotificationActivationCallback)(out) = this 161 return ole.S_OK 162 }) 163 164 // Activate is our re-entrance into Go from Windows. This is the magic. 165 INotificationActivationCallback_Activate = syscall.NewCallback(func( 166 this unsafe.Pointer, 167 appUserModelId unsafe.Pointer, 168 invokedArgs unsafe.Pointer, 169 data unsafe.Pointer, 170 count uint32, 171 ) (ret uintptr) { 172 callback( 173 windows.UTF16PtrToString((*uint16)(appUserModelId)), 174 windows.UTF16PtrToString((*uint16)(invokedArgs)), 175 sliceUserDataFromUnsafe(data, int(count)), 176 ) 177 return 178 }) 179 )