go-toast

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

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 )