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>
180 lines
5.7 KiB
Go
180 lines
5.7 KiB
Go
//go:build windows
|
|
|
|
// This file contains our pure-Go implementations of two COM objects that we need
|
|
// to render toast notifications: IClassFactory and INotificationActivationCallback.
|
|
//
|
|
// More specifically we allocate the C callable functions that can be used to populate
|
|
// the vtable at runtime.
|
|
//
|
|
// Unfortunately these functions have to be declared as var not const because the callbacks
|
|
// are built at runtime. They are declared globally because `syscall.NewCallback` never
|
|
// releases the memory it allocates for the functions thus causing an unsolvable memory
|
|
// leak if we were to allocate these per-notification.
|
|
//
|
|
// The other COM interfaces we are interacting with are auto-generated from metadata.
|
|
// However the INotificationActivationCallback is undocumented, so we have to define
|
|
// it entirely ourselves.
|
|
//
|
|
// The definitions are derived from:
|
|
// - <combase.h>
|
|
// - <NotificationActivationCallback.h>
|
|
package wintoast
|
|
|
|
import (
|
|
"runtime"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"github.com/go-ole/go-ole"
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// Interface GUIDS. These GUIDS are predefined by the Windows Runtime, identifying the various
|
|
// interfaces we want to make use of.
|
|
var (
|
|
IID_IClassFactory = ole.NewGUID("{00000001-0000-0000-C000-000000000046}")
|
|
IID_INotificationActivationCallback = ole.NewGUID("{53E31837-6600-4A81-9395-75CFFE746F94}")
|
|
)
|
|
|
|
// This default GUID is for our implementation.
|
|
// This was generated and should not collide with any other GUID.
|
|
// It's preferable for the application to override this value with its own generated GUID.
|
|
var GUID_ImplNotificationActivationCallback = ole.NewGUID("{0F82E845-CB89-4039-BDBF-67CA33254C76}")
|
|
|
|
type (
|
|
// IClassFactory defines the factory that builds our INotificationActivationCallback instance.
|
|
// Windows Runtime loves factories.
|
|
IClassFactory struct {
|
|
VTable *IClassFactoryVtbl
|
|
}
|
|
|
|
IClassFactoryVtbl struct {
|
|
ole.IUnknownVtbl
|
|
CreateInstance uintptr
|
|
LockServer uintptr
|
|
}
|
|
)
|
|
|
|
type (
|
|
// INotificationActivationCallback receives activations from toast notifications.
|
|
INotificationActivationCallback struct {
|
|
VTable *INotificationActivationCallbackVtbl
|
|
}
|
|
|
|
INotificationActivationCallbackVtbl struct {
|
|
ole.IUnknownVtbl
|
|
Activate uintptr
|
|
}
|
|
)
|
|
|
|
/*
|
|
Strictly speaking we shouldn't need to pin the static objects. They
|
|
are package-globals and wont be garabge collected. No harm in being
|
|
extra careful, though.
|
|
*/
|
|
|
|
var pinner runtime.Pinner
|
|
|
|
func init() {
|
|
pinner.Pin(ClassFactory)
|
|
pinner.Pin(ClassFactory.VTable)
|
|
pinner.Pin(NotificationActivationCallback)
|
|
pinner.Pin(NotificationActivationCallback.VTable)
|
|
}
|
|
|
|
// Static implementations for the IClassFactory.
|
|
var (
|
|
ClassFactory = &IClassFactory{
|
|
VTable: &IClassFactoryVtbl{
|
|
IUnknownVtbl: ole.IUnknownVtbl{
|
|
QueryInterface: IClassFactory_QueryInterface,
|
|
AddRef: IClassFactory_AddRef,
|
|
Release: IClassFactory_Release,
|
|
},
|
|
LockServer: IClassFactory_LockServer,
|
|
CreateInstance: IClassFactory_CreateInstance,
|
|
},
|
|
}
|
|
|
|
IClassFactory_AddRef = syscall.NewCallback(func(this *IClassFactory) (re uintptr) {
|
|
return uintptr(1)
|
|
})
|
|
|
|
IClassFactory_Release = syscall.NewCallback(func(this *IClassFactory) (re uintptr) {
|
|
return uintptr(1)
|
|
})
|
|
|
|
IClassFactory_QueryInterface = syscall.NewCallback(func(this *IClassFactory, riid *ole.GUID, out unsafe.Pointer) (re uintptr) {
|
|
if !ole.IsEqualGUID(riid, IID_IClassFactory) &&
|
|
!ole.IsEqualGUID(riid, ole.IID_IUnknown) {
|
|
return ole.E_NOINTERFACE
|
|
}
|
|
*(**IClassFactory)(out) = this
|
|
return ole.S_OK
|
|
})
|
|
|
|
IClassFactory_LockServer = syscall.NewCallback(func(this *IClassFactory, flock uintptr) (ret uintptr) {
|
|
return ole.S_OK
|
|
})
|
|
|
|
IClassFactory_CreateInstance = syscall.NewCallback(func(this *IClassFactory, punkOuter *ole.IUnknown, riid *ole.GUID, out unsafe.Pointer) (re uintptr) {
|
|
if punkOuter != nil {
|
|
// Should be CLASS_E_NOAGGREGATION but ole doesn't define this.
|
|
return ole.E_NOINTERFACE
|
|
}
|
|
if !ole.IsEqualGUID(riid, IID_INotificationActivationCallback) &&
|
|
!ole.IsEqualGUID(riid, ole.IID_IUnknown) {
|
|
return ole.E_NOINTERFACE
|
|
}
|
|
*(**INotificationActivationCallback)(out) = NotificationActivationCallback
|
|
return ole.S_OK
|
|
})
|
|
)
|
|
|
|
// Static implementations for the INotificationActivationCallback.
|
|
var (
|
|
NotificationActivationCallback = &INotificationActivationCallback{
|
|
VTable: &INotificationActivationCallbackVtbl{
|
|
IUnknownVtbl: ole.IUnknownVtbl{
|
|
QueryInterface: INotificationActivationCallback_QueryInterface,
|
|
AddRef: INotificationActivationCallback_AddRef,
|
|
Release: INotificationActivationCallback_Release,
|
|
},
|
|
Activate: INotificationActivationCallback_Activate,
|
|
},
|
|
}
|
|
|
|
INotificationActivationCallback_AddRef = syscall.NewCallback(func(this *INotificationActivationCallback) (re uintptr) {
|
|
return uintptr(1)
|
|
})
|
|
|
|
INotificationActivationCallback_Release = syscall.NewCallback(func(this *INotificationActivationCallback) (re uintptr) {
|
|
return uintptr(1)
|
|
})
|
|
|
|
INotificationActivationCallback_QueryInterface = syscall.NewCallback(func(this *INotificationActivationCallback, riid *ole.GUID, out unsafe.Pointer) (re uintptr) {
|
|
if !ole.IsEqualGUID(riid, IID_INotificationActivationCallback) &&
|
|
!ole.IsEqualGUID(riid, ole.IID_IUnknown) {
|
|
return ole.E_NOINTERFACE
|
|
}
|
|
*(**INotificationActivationCallback)(out) = this
|
|
return ole.S_OK
|
|
})
|
|
|
|
// Activate is our re-entrance into Go from Windows. This is the magic.
|
|
INotificationActivationCallback_Activate = syscall.NewCallback(func(
|
|
this unsafe.Pointer,
|
|
appUserModelId unsafe.Pointer,
|
|
invokedArgs unsafe.Pointer,
|
|
data unsafe.Pointer,
|
|
count uint32,
|
|
) (ret uintptr) {
|
|
callback(
|
|
windows.UTF16PtrToString((*uint16)(appUserModelId)),
|
|
windows.UTF16PtrToString((*uint16)(invokedArgs)),
|
|
sliceUserDataFromUnsafe(data, int(count)),
|
|
)
|
|
return
|
|
})
|
|
)
|