Switch remote deploy to vendored source builds
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>
This commit is contained in:
1
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/.gitignore
vendored
Normal file
1
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.exe
|
||||
244
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/ARCHITECTURE.md
vendored
Normal file
244
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/ARCHITECTURE.md
vendored
Normal file
@@ -0,0 +1,244 @@
|
||||
# Architecture
|
||||
|
||||
This document will attempt to explain how this code works.
|
||||
|
||||
## Windows COM
|
||||
|
||||
Windows makes heavy use of it's [COM api](https://en.wikipedia.org/wiki/Component_Object_Model),
|
||||
(Component Object Model) which is a binary interface - allowing programs that agree on a memory
|
||||
layout in order to communicate.
|
||||
|
||||
COM apis are typically Object Oriented, and based on interfaces. This should be familiar to Go
|
||||
programmers, since Go includes interfaces as a core part of its language design and type system.
|
||||
|
||||
The difference being that in COM we don't get any runtime help, nice syntax or type safety. We
|
||||
get raw [VTables](https://en.wikipedia.org/wiki/Virtual_method_table) and deal with raw memory.
|
||||
|
||||
You can think of COM as like working with a Go api that uses `any` (empty interface) _everywhere_
|
||||
and typeswitching is required to access methods `file, ok := obj.(File)`.
|
||||
|
||||
Some languages like C++ have extensions that support COM and provide convenient wrappers for
|
||||
generating and using COM apis. Go does not. C also, does not.
|
||||
|
||||
However there is a package `go-ole` that allows us to _call_ COM apis with some level of
|
||||
convenience - which we will use where possible. What go-ole does not expose is a way to
|
||||
implement a COM object in Go.
|
||||
|
||||
## Interacting with COM objects in pure Go
|
||||
|
||||
In order to interact with COM objects we need to:
|
||||
|
||||
1. locate headers containing the VTable definitions
|
||||
2. define vtables in Go that are compatable with those definitions
|
||||
3. invoke the appropriate COM objects using our vtables and the syscall package
|
||||
|
||||
### 1. locate headers
|
||||
|
||||
Download Windows SDK via the [Visual Studio installer](https://visualstudio.microsoft.com/downloads).
|
||||
You will need to check "Desktop development with C++".
|
||||
|
||||
Once complete you can navigate to the SDK include directory.
|
||||
|
||||
In our case we needed `Windows.ui.notifications.h`, which contains the definitions of
|
||||
the types we want to call, and `NotificationActivationCallback.h` which contains the definition of
|
||||
`INotificationActivationCallback` which is the interface we need to _implement_.
|
||||
|
||||
### 2. define vtables in Go
|
||||
|
||||
The VTables are defined in C (mired in macros). We need to define compatible vtables in Go syntax
|
||||
so we can call the ones defined in the header.
|
||||
|
||||
COM objects are structured in a such a way that we want a parent struct who's first field is a pointer
|
||||
to the vtable struct. A full example is provided later, for now it we need something like this:
|
||||
|
||||
```go
|
||||
type Object struct {
|
||||
lpvtbl *ObjectVtbl
|
||||
}
|
||||
type ObjectVtbl struct {
|
||||
MethodOne uintptr
|
||||
MethdoTwo uintptr
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
### 3. invoke methods in Go
|
||||
|
||||
Using package `syscall` we can invoke these methods (provided the uintptr are valid) using
|
||||
`syscal.SyscallN`. Paramters and return values are defined in the C headers.
|
||||
|
||||
```go
|
||||
func (v *Object) One() error {
|
||||
hr, _, _ := syscall.SyscallN(uintptr(v))
|
||||
if hr != ole.S_OK {
|
||||
return ole.NewError(hr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
With that we can inoke methods on a COM object. This is how `go-ole` works.
|
||||
|
||||
## Implementing a COM object in pure Go (no cgo!)
|
||||
|
||||
To do this we will need to allocate raw memory for the VTables (so that Go garbage collector
|
||||
doesn't interfere) and write our function pointers to the VTables.
|
||||
|
||||
Since these are not safe Go capabilities we will need the help of package `syscall` (on Windows).
|
||||
|
||||
Package `syscall` provides two very important functions:
|
||||
|
||||
1. `NewProc` - which loads a function from a DLL
|
||||
2. `NewCallback` - which allocates a C-callable function pointer from a Go function
|
||||
|
||||
For the first part, we can load the Windows kernel api via `kernel32.dll` system dll, and
|
||||
pull out `GlobalAlloc` and `GlobalFree` using `syscall.NewProc`.
|
||||
|
||||
For the second part, we can use `syscall.NewCallback` to build a C-callable function pointer
|
||||
from a Go function and instantiate the VtTables with it. Caveat emptor: memory allocated by
|
||||
`NewCallback` is never released, and only 1024 callbacks are guaranteed to be allowed. This
|
||||
is why we only allocate the callbacks once on init.
|
||||
|
||||
Thus we can implement a COM object (invokable from C) like this:
|
||||
|
||||
```go
|
||||
|
||||
// Initialize our kernel functions.
|
||||
var (
|
||||
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
procMalloc = kernel32.NewProc("GlobalAlloc")
|
||||
procFree = kernel32.NewProc("GlobalFree")
|
||||
)
|
||||
|
||||
// malloc allocates raw memory using the Windows kernel.
|
||||
// In case of out of memory, the returned pointer will be nil.
|
||||
// The memory is zeroed out to make sure we don't get garbage that looks like
|
||||
// valid Go data types.
|
||||
func malloc(size uintptr) unsafe.Pointer {
|
||||
hr, _, _ := procMalloc.Call(uintptr(GMEM_FIXED|GMEM_ZEROINIT), uintptr(size))
|
||||
if hr == 0 {
|
||||
return nil
|
||||
}
|
||||
return unsafe.Pointer(hr)
|
||||
}
|
||||
|
||||
// free deallocates raw memory allocated by malloc.
|
||||
func free(object unsafe.Pointer) {
|
||||
procFree.Call(uintptr(object))
|
||||
}
|
||||
|
||||
// Object defines our object.
|
||||
// This is how COM objects are laid out in memory, where the first field is a pointer
|
||||
// to a vtable, and the vtable's fields are pointers to functions.
|
||||
type Object struct {
|
||||
lpvtbl *ObjectVtbl // lpvtbl is a COM conventional name for this field.
|
||||
}
|
||||
|
||||
// ObjectVtbl defines the Vtable of our object.
|
||||
type ObjectVtbl struct {
|
||||
MethodOne uintptr
|
||||
MethodTwo uintptr
|
||||
MethodThree uintptr
|
||||
}
|
||||
|
||||
// These methods are allocated once as package globals because Go will never reclaim the
|
||||
// memory allocated for such callbacks.
|
||||
//
|
||||
// All arguments must be uintptr sized, and the return must be a uintptr as well.
|
||||
//
|
||||
// By convention, the first parameter is a pointer to the parent object.
|
||||
var (
|
||||
methodOne = syscall.NewCallback(func(this *Object) uintptr {
|
||||
fmt.Printf("methodOne invoked\n")
|
||||
return uintptr(0)
|
||||
})
|
||||
|
||||
methodTwo = syscall.NewCallback(func(this *Object) uintptr {
|
||||
fmt.Printf("methodTwo invoked\n")
|
||||
return uintptr(0)
|
||||
})
|
||||
|
||||
methodThree = syscall.NewCallback(func(this *Object) uintptr {
|
||||
fmt.Printf("methodThree invoked\n")
|
||||
return uintptr(0)
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
func NewObject() *Object {
|
||||
// Allocate the parent object and the vtable.
|
||||
obj := (*Object)(malloc(unsafe.Sizeof(Object{})))
|
||||
vtbl := (*ObjectVtbl)(malloc(unsafe.Sizeof(ObjectVtbl{})))
|
||||
|
||||
// Initialize the vtable with our static callback implementations.
|
||||
vtbl.MethodOne = methodOne
|
||||
vtbl.MethodTwo = methodTwo
|
||||
vtbl.MethodThree = methodThree
|
||||
|
||||
// The returned object must be freed by GlobalFree.
|
||||
object.lpvtbl = vtbl
|
||||
return obj
|
||||
}
|
||||
```
|
||||
|
||||
## WinRT and Toast Notifications
|
||||
|
||||
For this package the vtables we need are located in various headers `Windows.ui.notifications.h` and
|
||||
`NotificationActivationCallback.h` and `combase.h`.
|
||||
|
||||
With all of the vtables replicated in Go as explained above we now need to interact with the Windows
|
||||
Runtime.
|
||||
|
||||
First we need to initialize the Windows Runtime with `RoInitialize`.
|
||||
|
||||
```go
|
||||
ole.RoInitialize(0)
|
||||
```
|
||||
|
||||
Traditional COM uses GUIDs to identify objects and interfaces. WinRT uses strings (mapped to GUIDS
|
||||
at runtime).
|
||||
|
||||
To instantiate a WinRT COM object we invoke `RoGetActivationFactory` with the class string along with
|
||||
the interface GUID we expect to use.
|
||||
|
||||
|
||||
```go
|
||||
CLSID_ToastNotification := "Windows.UI.Notifications.ToastNotification"
|
||||
IID_IToastNotificationFactory := ole.NewGUID("{50AC103F-D235-4598-BBEF-98FE4D1A3AD4}")
|
||||
|
||||
factoryObject, err := ole.RoGetActivationFactory(CLSID_ToastNotification, IID_ToastNotificationFactory)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting activation factory: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
From there we can unsafe cast to our callback definition (ole doesn't provide direct access to the methods).
|
||||
|
||||
```go
|
||||
factory := (*IToastNotificationFactory)(unsafe.Pointer(factoryObject))
|
||||
notification, err := factory.CreateToastNotification(xml)
|
||||
```
|
||||
|
||||
Repeat this process per object we need to instantiate.
|
||||
|
||||
To generate a toast notification from XML with a callback we need to instantiate several COM objects:
|
||||
|
||||
1. `INotificationActivationCallback` our implementation to be invoked by the runtime
|
||||
1. `ClassFactory` which can instantiate our `INotificationActivationCallback` implementation
|
||||
1. `XmlDocument` to contain the xml content of the notification
|
||||
1. `XmlDocumentIO` to provide an IO interface to the xml document (so we can write the xml to it)
|
||||
1. `Notification` specifying the content of the notification
|
||||
1. `Notifier` for showing notifications
|
||||
|
||||
Finally we register our class factory using `CoRegisterClassObject` so the runtime can call us back
|
||||
and then we invoke `Notifier.Show` passing in the `Notification` object to display the notification.
|
||||
|
||||
In addition to calling and implementing COM objects we need to manipulate registry state to tell the
|
||||
Windows Runtime metadata about our application.
|
||||
|
||||
1. register a CLSID (GUID) for our INotificationActivationCallback; this is how the runtime knows
|
||||
what object to ask for
|
||||
2. optionally provide an icon and an activation executable to be invoked when our application is not running
|
||||
|
||||
|
||||
With all of that correctly configured we can generate toast notifications on Windows in pure Go!
|
||||
61
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/LICENSE
vendored
Normal file
61
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/LICENSE
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
This project is dual-licensed under the UNLICENSE or
|
||||
the MIT license with the SPDX identifier:
|
||||
|
||||
SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
You may use the project under the terms of either license.
|
||||
|
||||
Both licenses are reproduced below.
|
||||
|
||||
----
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2023 Jack Mordaunt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
---
|
||||
|
||||
---
|
||||
The UNLICENSE
|
||||
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <https://unlicense.org/>
|
||||
---
|
||||
86
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/README.md
vendored
Normal file
86
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/README.md
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
# go-toast
|
||||
|
||||
This package implements Windows toast notifications using the Windows Runtime COM API.
|
||||
|
||||
The XML schema used to describe such notifications is here:
|
||||
|
||||
https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/adaptive-interactive-toasts
|
||||
|
||||
Package `wintoast` offers a lower-level api.
|
||||
Package `toast` offers a higher-level wrapper.
|
||||
|
||||
`wintoast` uses build tags to guard Windows only code. It will still compile on
|
||||
non-Windows platforms, however the functions are stubbed out and will do nothing
|
||||
when invoked.
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic
|
||||
|
||||
```go
|
||||
noti := toast.Notification{
|
||||
AppID: "My cool app",
|
||||
Title: "Title",
|
||||
Body: "Body",
|
||||
}
|
||||
|
||||
err := noti.Push()
|
||||
```
|
||||
|
||||
### Actions / Inputs with Callback
|
||||
|
||||
Additionally, we can respond to notification activation with a callback.
|
||||
|
||||
```go
|
||||
// Set the callback that receives the data from the notification.
|
||||
// Any data from actions or inputs will be accessible here.
|
||||
toast.SetActivationCallback(func(args string, data []UserData) {
|
||||
fmt.Printf("args: %q, data: %v\n", args, data)
|
||||
})
|
||||
|
||||
n := toast.Notification{
|
||||
AppID: "My cool app",
|
||||
Title: "Title",
|
||||
Body: "Body",
|
||||
}
|
||||
|
||||
n.Inputs = append(n.Inputs, toast.Input{
|
||||
ID: "reply-to:john-doe",
|
||||
Title: "Reply",
|
||||
Placeholder: "Reply to John Doe",
|
||||
})
|
||||
|
||||
n.Inputs = append(n.Inputs, toast.Input{
|
||||
ID: "select-action",
|
||||
Title: "Selection Action",
|
||||
Placeholder: "Pick an action to perform",
|
||||
Selections: []toast.InputSelection{
|
||||
{
|
||||
ID: "1",
|
||||
Content: "do thing one",
|
||||
},
|
||||
{
|
||||
ID: "2",
|
||||
Content: "do thing two",
|
||||
},
|
||||
{
|
||||
ID: "3",
|
||||
Content: "do thing three",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
n.Actions = append(n.Actions, toast.Action{
|
||||
Type: toast.Foreground,
|
||||
Content: "Send",
|
||||
Arguments: "send",
|
||||
})
|
||||
|
||||
n.Actions = append(n.Actions, toast.Action{
|
||||
Type: toast.Foreground,
|
||||
Content: "Close",
|
||||
Arguments: "close",
|
||||
})
|
||||
|
||||
err := n.Push()
|
||||
```
|
||||
65
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/constants.go
vendored
Normal file
65
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/constants.go
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
package toast
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrorInvalidAudio error = errors.New("toast: invalid audio")
|
||||
ErrorInvalidDuration = errors.New("toast: invalid duration")
|
||||
)
|
||||
|
||||
// toastAudio identifies audio that Windows can play.
|
||||
type toastAudio = string
|
||||
|
||||
const (
|
||||
Default toastAudio = "ms-winsoundevent:Notification.Default"
|
||||
IM toastAudio = "ms-winsoundevent:Notification.IM"
|
||||
Mail toastAudio = "ms-winsoundevent:Notification.Mail"
|
||||
Reminder toastAudio = "ms-winsoundevent:Notification.Reminder"
|
||||
SMS toastAudio = "ms-winsoundevent:Notification.SMS"
|
||||
LoopingAlarm toastAudio = "ms-winsoundevent:Notification.Looping.Alarm"
|
||||
LoopingAlarm2 toastAudio = "ms-winsoundevent:Notification.Looping.Alarm2"
|
||||
LoopingAlarm3 toastAudio = "ms-winsoundevent:Notification.Looping.Alarm3"
|
||||
LoopingAlarm4 toastAudio = "ms-winsoundevent:Notification.Looping.Alarm4"
|
||||
LoopingAlarm5 toastAudio = "ms-winsoundevent:Notification.Looping.Alarm5"
|
||||
LoopingAlarm6 toastAudio = "ms-winsoundevent:Notification.Looping.Alarm6"
|
||||
LoopingAlarm7 toastAudio = "ms-winsoundevent:Notification.Looping.Alarm7"
|
||||
LoopingAlarm8 toastAudio = "ms-winsoundevent:Notification.Looping.Alarm8"
|
||||
LoopingAlarm9 toastAudio = "ms-winsoundevent:Notification.Looping.Alarm9"
|
||||
LoopingAlarm10 toastAudio = "ms-winsoundevent:Notification.Looping.Alarm10"
|
||||
LoopingCall toastAudio = "ms-winsoundevent:Notification.Looping.Call"
|
||||
LoopingCall2 toastAudio = "ms-winsoundevent:Notification.Looping.Call2"
|
||||
LoopingCall3 toastAudio = "ms-winsoundevent:Notification.Looping.Call3"
|
||||
LoopingCall4 toastAudio = "ms-winsoundevent:Notification.Looping.Call4"
|
||||
LoopingCall5 toastAudio = "ms-winsoundevent:Notification.Looping.Call5"
|
||||
LoopingCall6 toastAudio = "ms-winsoundevent:Notification.Looping.Call6"
|
||||
LoopingCall7 toastAudio = "ms-winsoundevent:Notification.Looping.Call7"
|
||||
LoopingCall8 toastAudio = "ms-winsoundevent:Notification.Looping.Call8"
|
||||
LoopingCall9 toastAudio = "ms-winsoundevent:Notification.Looping.Call9"
|
||||
LoopingCall10 toastAudio = "ms-winsoundevent:Notification.Looping.Call10"
|
||||
Silent toastAudio = "silent"
|
||||
)
|
||||
|
||||
// toastduration identifies toast duration for audio playback.
|
||||
type toastDuration = string
|
||||
|
||||
const (
|
||||
Short toastDuration = "short"
|
||||
Long toastDuration = "long"
|
||||
)
|
||||
|
||||
// ActivationType identifies the method that Windows Runtime will use to handle
|
||||
// notification interactions.
|
||||
//
|
||||
// See https://learn.microsoft.com/en-us/dotnet/api/microsoft.toolkit.uwp.notifications.toastactivationtype
|
||||
type ActivationType = string
|
||||
|
||||
const (
|
||||
// Protocol is for launching third-party applications using a protocol uri, like https or mailto.
|
||||
Protocol ActivationType = "protocol"
|
||||
// Foreground is for launching your foreground application. This is required to enable the activation
|
||||
// callback. There is a third option: Background, however for Desktop applications Foreground and
|
||||
// Background behave identically.
|
||||
//
|
||||
// See https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/send-local-toast-desktop-cpp-wrl#foreground-vs-background-activation
|
||||
Foreground ActivationType = "foreground"
|
||||
)
|
||||
73
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/internal/winrt/data/xml/dom/xmldocument.go
vendored
Normal file
73
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/internal/winrt/data/xml/dom/xmldocument.go
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
// Code generated by winrt-go-gen. DO NOT EDIT.
|
||||
|
||||
//go:build windows
|
||||
|
||||
//nolint:all
|
||||
package dom
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/go-ole/go-ole"
|
||||
)
|
||||
|
||||
const SignatureXmlDocument string = "rc(Windows.Data.Xml.Dom.XmlDocument;{f7f3a506-1e87-42d6-bcfb-b8c809fa5494})"
|
||||
|
||||
type XmlDocument struct {
|
||||
ole.IUnknown
|
||||
}
|
||||
|
||||
func NewXmlDocument() (*XmlDocument, error) {
|
||||
inspectable, err := ole.RoActivateInstance("Windows.Data.Xml.Dom.XmlDocument")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (*XmlDocument)(unsafe.Pointer(inspectable)), nil
|
||||
}
|
||||
|
||||
func (impl *XmlDocument) LoadXml(xml string) error {
|
||||
itf := impl.MustQueryInterface(ole.NewGUID(GUIDiXmlDocumentIO))
|
||||
defer itf.Release()
|
||||
v := (*iXmlDocumentIO)(unsafe.Pointer(itf))
|
||||
return v.LoadXml(xml)
|
||||
}
|
||||
|
||||
const (
|
||||
GUIDiXmlDocumentIO string = "6cd0e74e-ee65-4489-9ebf-ca43e87ba637"
|
||||
SignatureiXmlDocumentIO string = "{6cd0e74e-ee65-4489-9ebf-ca43e87ba637}"
|
||||
)
|
||||
|
||||
type iXmlDocumentIO struct {
|
||||
ole.IInspectable
|
||||
}
|
||||
|
||||
type iXmlDocumentIOVtbl struct {
|
||||
ole.IInspectableVtbl
|
||||
|
||||
LoadXml uintptr
|
||||
LoadXmlWithSettings uintptr
|
||||
SaveToFileAsync uintptr
|
||||
}
|
||||
|
||||
func (v *iXmlDocumentIO) VTable() *iXmlDocumentIOVtbl {
|
||||
return (*iXmlDocumentIOVtbl)(unsafe.Pointer(v.RawVTable))
|
||||
}
|
||||
|
||||
func (v *iXmlDocumentIO) LoadXml(xml string) error {
|
||||
xmlHStr, err := ole.NewHString(xml)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hr, _, _ := syscall.SyscallN(
|
||||
v.VTable().LoadXml,
|
||||
uintptr(unsafe.Pointer(v)), // this
|
||||
uintptr(xmlHStr), // in string
|
||||
)
|
||||
|
||||
if hr != 0 {
|
||||
return ole.NewError(hr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
88
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/internal/winrt/ui/notifications/toastnotification.go
vendored
Normal file
88
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/internal/winrt/ui/notifications/toastnotification.go
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
// Code generated by winrt-go-gen. DO NOT EDIT.
|
||||
|
||||
//go:build windows
|
||||
|
||||
//nolint:all
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"git.sr.ht/~jackmordaunt/go-toast/v2/internal/winrt/data/xml/dom"
|
||||
"github.com/go-ole/go-ole"
|
||||
)
|
||||
|
||||
const SignatureToastNotification string = "rc(Windows.UI.Notifications.ToastNotification;{997e2675-059e-4e60-8b06-1760917c8b80})"
|
||||
|
||||
func CreateToastNotification(content *dom.XmlDocument) (*ToastNotification, error) {
|
||||
inspectable, err := ole.RoGetActivationFactory("Windows.UI.Notifications.ToastNotification", ole.NewGUID(GUIDiToastNotificationFactory))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := (*iToastNotificationFactory)(unsafe.Pointer(inspectable))
|
||||
|
||||
var out *ToastNotification
|
||||
hr, _, _ := syscall.SyscallN(
|
||||
v.VTable().CreateToastNotification,
|
||||
0, // this is a static func, so there's no this
|
||||
uintptr(unsafe.Pointer(content)), // in dom.XmlDocument
|
||||
uintptr(unsafe.Pointer(&out)), // out ToastNotification
|
||||
)
|
||||
|
||||
if hr != 0 {
|
||||
return nil, ole.NewError(hr)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type ToastNotification struct {
|
||||
ole.IUnknown
|
||||
}
|
||||
|
||||
const (
|
||||
GUIDiToastNotification string = "997e2675-059e-4e60-8b06-1760917c8b80"
|
||||
SignatureiToastNotification string = "{997e2675-059e-4e60-8b06-1760917c8b80}"
|
||||
)
|
||||
|
||||
type iToastNotification struct {
|
||||
ole.IInspectable
|
||||
}
|
||||
|
||||
type iToastNotificationVtbl struct {
|
||||
ole.IInspectableVtbl
|
||||
|
||||
GetContent uintptr
|
||||
SetExpirationTime uintptr
|
||||
GetExpirationTime uintptr
|
||||
AddDismissed uintptr
|
||||
RemoveDismissed uintptr
|
||||
AddActivated uintptr
|
||||
RemoveActivated uintptr
|
||||
AddFailed uintptr
|
||||
RemoveFailed uintptr
|
||||
}
|
||||
|
||||
func (v *iToastNotification) VTable() *iToastNotificationVtbl {
|
||||
return (*iToastNotificationVtbl)(unsafe.Pointer(v.RawVTable))
|
||||
}
|
||||
|
||||
const (
|
||||
GUIDiToastNotificationFactory string = "04124b20-82c6-4229-b109-fd9ed4662b53"
|
||||
SignatureiToastNotificationFactory string = "{04124b20-82c6-4229-b109-fd9ed4662b53}"
|
||||
)
|
||||
|
||||
type iToastNotificationFactory struct {
|
||||
ole.IInspectable
|
||||
}
|
||||
|
||||
type iToastNotificationFactoryVtbl struct {
|
||||
ole.IInspectableVtbl
|
||||
|
||||
CreateToastNotification uintptr
|
||||
}
|
||||
|
||||
func (v *iToastNotificationFactory) VTable() *iToastNotificationFactoryVtbl {
|
||||
return (*iToastNotificationFactoryVtbl)(unsafe.Pointer(v.RawVTable))
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Code generated by winrt-go-gen. DO NOT EDIT.
|
||||
|
||||
//go:build windows
|
||||
|
||||
//nolint:all
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/go-ole/go-ole"
|
||||
)
|
||||
|
||||
const (
|
||||
GUIDiToastNotificationManagerStatics5 string = "d6f5f569-d40d-407c-8989-88cab42cfd14"
|
||||
SignatureiToastNotificationManagerStatics5 string = "{d6f5f569-d40d-407c-8989-88cab42cfd14}"
|
||||
)
|
||||
|
||||
type iToastNotificationManagerStatics5 struct {
|
||||
ole.IInspectable
|
||||
}
|
||||
|
||||
type iToastNotificationManagerStatics5Vtbl struct {
|
||||
ole.IInspectableVtbl
|
||||
|
||||
GetDefault uintptr
|
||||
}
|
||||
|
||||
func (v *iToastNotificationManagerStatics5) VTable() *iToastNotificationManagerStatics5Vtbl {
|
||||
return (*iToastNotificationManagerStatics5Vtbl)(unsafe.Pointer(v.RawVTable))
|
||||
}
|
||||
|
||||
func GetDefault() (*ToastNotificationManagerForUser, error) {
|
||||
inspectable, err := ole.RoGetActivationFactory("Windows.UI.Notifications.ToastNotificationManager", ole.NewGUID(GUIDiToastNotificationManagerStatics5))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := (*iToastNotificationManagerStatics5)(unsafe.Pointer(inspectable))
|
||||
|
||||
var out *ToastNotificationManagerForUser
|
||||
hr, _, _ := syscall.SyscallN(
|
||||
v.VTable().GetDefault,
|
||||
0, // this is a static func, so there's no this
|
||||
uintptr(unsafe.Pointer(&out)), // out ToastNotificationManagerForUser
|
||||
)
|
||||
|
||||
if hr != 0 {
|
||||
return nil, ole.NewError(hr)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Code generated by winrt-go-gen. DO NOT EDIT.
|
||||
|
||||
//go:build windows
|
||||
|
||||
//nolint:all
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/go-ole/go-ole"
|
||||
)
|
||||
|
||||
const SignatureToastNotificationManagerForUser string = "rc(Windows.UI.Notifications.ToastNotificationManagerForUser;{79ab57f6-43fe-487b-8a7f-99567200ae94})"
|
||||
|
||||
type ToastNotificationManagerForUser struct {
|
||||
ole.IUnknown
|
||||
}
|
||||
|
||||
func (impl *ToastNotificationManagerForUser) CreateToastNotifierWithId(applicationId string) (*ToastNotifier, error) {
|
||||
itf := impl.MustQueryInterface(ole.NewGUID(GUIDiToastNotificationManagerForUser))
|
||||
defer itf.Release()
|
||||
v := (*iToastNotificationManagerForUser)(unsafe.Pointer(itf))
|
||||
return v.CreateToastNotifierWithId(applicationId)
|
||||
}
|
||||
|
||||
const (
|
||||
GUIDiToastNotificationManagerForUser string = "79ab57f6-43fe-487b-8a7f-99567200ae94"
|
||||
SignatureiToastNotificationManagerForUser string = "{79ab57f6-43fe-487b-8a7f-99567200ae94}"
|
||||
)
|
||||
|
||||
type iToastNotificationManagerForUser struct {
|
||||
ole.IInspectable
|
||||
}
|
||||
|
||||
type iToastNotificationManagerForUserVtbl struct {
|
||||
ole.IInspectableVtbl
|
||||
|
||||
CreateToastNotifier uintptr
|
||||
CreateToastNotifierWithId uintptr
|
||||
GetHistory uintptr
|
||||
GetUser uintptr
|
||||
}
|
||||
|
||||
func (v *iToastNotificationManagerForUser) VTable() *iToastNotificationManagerForUserVtbl {
|
||||
return (*iToastNotificationManagerForUserVtbl)(unsafe.Pointer(v.RawVTable))
|
||||
}
|
||||
|
||||
func (v *iToastNotificationManagerForUser) CreateToastNotifierWithId(applicationId string) (*ToastNotifier, error) {
|
||||
var out *ToastNotifier
|
||||
applicationIdHStr, err := ole.NewHString(applicationId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hr, _, _ := syscall.SyscallN(
|
||||
v.VTable().CreateToastNotifierWithId,
|
||||
uintptr(unsafe.Pointer(v)), // this
|
||||
uintptr(applicationIdHStr), // in string
|
||||
uintptr(unsafe.Pointer(&out)), // out ToastNotifier
|
||||
)
|
||||
|
||||
if hr != 0 {
|
||||
return nil, ole.NewError(hr)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
64
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/internal/winrt/ui/notifications/toastnotifier.go
vendored
Normal file
64
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/internal/winrt/ui/notifications/toastnotifier.go
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
// Code generated by winrt-go-gen. DO NOT EDIT.
|
||||
|
||||
//go:build windows
|
||||
|
||||
//nolint:all
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/go-ole/go-ole"
|
||||
)
|
||||
|
||||
const SignatureToastNotifier string = "rc(Windows.UI.Notifications.ToastNotifier;{75927b93-03f3-41ec-91d3-6e5bac1b38e7})"
|
||||
|
||||
type ToastNotifier struct {
|
||||
ole.IUnknown
|
||||
}
|
||||
|
||||
func (impl *ToastNotifier) Show(notification *ToastNotification) error {
|
||||
itf := impl.MustQueryInterface(ole.NewGUID(GUIDiToastNotifier))
|
||||
defer itf.Release()
|
||||
v := (*iToastNotifier)(unsafe.Pointer(itf))
|
||||
return v.Show(notification)
|
||||
}
|
||||
|
||||
const (
|
||||
GUIDiToastNotifier string = "75927b93-03f3-41ec-91d3-6e5bac1b38e7"
|
||||
SignatureiToastNotifier string = "{75927b93-03f3-41ec-91d3-6e5bac1b38e7}"
|
||||
)
|
||||
|
||||
type iToastNotifier struct {
|
||||
ole.IInspectable
|
||||
}
|
||||
|
||||
type iToastNotifierVtbl struct {
|
||||
ole.IInspectableVtbl
|
||||
|
||||
Show uintptr
|
||||
Hide uintptr
|
||||
GetSetting uintptr
|
||||
AddToSchedule uintptr
|
||||
RemoveFromSchedule uintptr
|
||||
GetScheduledToastNotifications uintptr
|
||||
}
|
||||
|
||||
func (v *iToastNotifier) VTable() *iToastNotifierVtbl {
|
||||
return (*iToastNotifierVtbl)(unsafe.Pointer(v.RawVTable))
|
||||
}
|
||||
|
||||
func (v *iToastNotifier) Show(notification *ToastNotification) error {
|
||||
hr, _, _ := syscall.SyscallN(
|
||||
v.VTable().Show,
|
||||
uintptr(unsafe.Pointer(v)), // this
|
||||
uintptr(unsafe.Pointer(notification)), // in ToastNotification
|
||||
)
|
||||
|
||||
if hr != 0 {
|
||||
return ole.NewError(hr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
14
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/tmpl/powershell.go.tmpl
vendored
Normal file
14
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/tmpl/powershell.go.tmpl
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
|
||||
[Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
|
||||
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null
|
||||
|
||||
$APP_ID = '{{if .AppID}}{{.AppID}}{{else}}Windows App{{end}}'
|
||||
|
||||
$template = @"
|
||||
{{.XML}}
|
||||
"@
|
||||
|
||||
$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
|
||||
$xml.LoadXml($template)
|
||||
$toast = New-Object Windows.UI.Notifications.ToastNotification $xml
|
||||
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($APP_ID).Show($toast)
|
||||
28
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/tmpl/tmpl.go
vendored
Normal file
28
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/tmpl/tmpl.go
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
// Package tmpl wraps several templates that are used to generate notifications.
|
||||
//
|
||||
// The primary template describes the XML structure that the Windows Runtime expects
|
||||
// to consume. The powershell template describes a script that we can use to execute
|
||||
// the notification if the Windows Runtime is unavailable.
|
||||
//
|
||||
// For more information about the xml schema:
|
||||
// https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/schema-root
|
||||
package tmpl
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
//go:embed xml.go.tmpl
|
||||
var xml string
|
||||
|
||||
//go:embed powershell.go.tmpl
|
||||
var powershell string
|
||||
|
||||
// XMLTemplate describes the XML content that the Windows Runtime uses to build
|
||||
// toast notifications.
|
||||
var XMLTemplate = template.Must(template.New("toast-xml").Parse(xml))
|
||||
|
||||
// ScriptTemplate describes the Powershell script that will invoke a toast notification
|
||||
// given some XML.
|
||||
var ScriptTemplate = template.Must(template.New("script").Parse(powershell))
|
||||
38
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/tmpl/xml.go.tmpl
vendored
Normal file
38
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/tmpl/xml.go.tmpl
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<toast activationType="{{.ActivationType}}" launch="{{.ActivationArguments}}" duration="{{.Duration}}">
|
||||
<visual>
|
||||
<binding template="ToastGeneric">
|
||||
{{if .HeroIcon}}
|
||||
<image placement="hero" src="{{.HeroIcon}}" />
|
||||
{{end}}
|
||||
{{if .Icon}}
|
||||
<image placement="appLogoOverride" src="{{.Icon}}" {{if .IconCrop}} hint-crop="{{.IconCrop}}" {{end}} />
|
||||
{{end}}
|
||||
{{if .Title}}
|
||||
<text hint-maxLines="1"><![CDATA[{{.Title}}]]></text>
|
||||
{{end}}
|
||||
{{if .Body}}
|
||||
<text><![CDATA[{{.Body}}]]></text>
|
||||
{{end}}
|
||||
</binding>
|
||||
</visual>
|
||||
{{if ne .Audio "silent"}}
|
||||
<audio src="{{.Audio}}" loop="{{.Loop}}" />
|
||||
{{else}}
|
||||
<audio silent="true" />
|
||||
{{end}}
|
||||
{{if .Actions}}
|
||||
<actions>
|
||||
{{range .Inputs}}
|
||||
<input id="{{.ID}}" title="{{.Title}}" placeHolderContent="{{.Placeholder}}" {{if .Selections}} type="selection" {{else}} type="text" {{end}}>
|
||||
{{range .Selections}}
|
||||
<selection id="{{.ID}}" content="{{.Content}}" />
|
||||
{{end}}
|
||||
</input>
|
||||
{{end}}
|
||||
{{range .Actions}}
|
||||
<action activationType="{{.Type}}" content="{{.Content}}" arguments="{{.Arguments}}" {{if .InputID}} hint-inputId="{{.InputID}}" {{end}} />
|
||||
{{end}}
|
||||
</actions>
|
||||
{{end}}
|
||||
</toast>
|
||||
233
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/toast.go
vendored
Normal file
233
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/toast.go
vendored
Normal file
@@ -0,0 +1,233 @@
|
||||
// Package toast wraps the lower-level wintoast api and provides an easy way
|
||||
// to send and respond to toast notifications on Windows.
|
||||
//
|
||||
// First, setup your AppData vis SetAppData function. This will install your
|
||||
// application metadata into the Windows Registry.
|
||||
//
|
||||
// Then, if you want in-process callback to be invoked upon user interaction,
|
||||
// invoke SetActivationCallback.
|
||||
//
|
||||
// Finally, generate your notification by instantiation a toast.Notification
|
||||
// and pushing it with Push method.
|
||||
package toast
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"git.sr.ht/~jackmordaunt/go-toast/v2/tmpl"
|
||||
"git.sr.ht/~jackmordaunt/go-toast/v2/wintoast"
|
||||
)
|
||||
|
||||
// Notification
|
||||
//
|
||||
// The toast notification data. The following fields are strongly recommended;
|
||||
// - AppID
|
||||
// - Title
|
||||
//
|
||||
// If no toastAudio is provided, then the toast notification will be silent.
|
||||
//
|
||||
// The AppID is shown beneath the toast message (in certain cases), and above the notification within the Action
|
||||
// Center - and is used to group your notifications together. It is recommended that you provide a "pretty"
|
||||
// name for your app, and not something like "com.example.MyApp". It can be ellided if the value has already
|
||||
// been set via SetAppData.
|
||||
//
|
||||
// If no Title is provided, but a Body is, the body will display as the toast notification's title -
|
||||
// which is a slightly different font style (heavier).
|
||||
//
|
||||
// The Icon should be an absolute path to the icon (as the toast is invoked from a temporary path on the user's
|
||||
// system, not the working directory).
|
||||
//
|
||||
// If you would like the toast to call an external process/open a webpage, then you can set ActivationArguments
|
||||
// to the uri you would like to trigger when the toast is clicked. For example: "https://google.com" would open
|
||||
// the Google homepage when the user clicks the toast notification.
|
||||
// By default, clicking the toast just hides/dismisses it.
|
||||
//
|
||||
// The following would show a notification to the user letting them know they received an email, and opens
|
||||
// gmail.com when they click the notification. It also makes the Windows 10 "mail" sound effect.
|
||||
//
|
||||
// toast := toast.Notification{
|
||||
// AppID: "Google Mail",
|
||||
// Title: email.Subject,
|
||||
// Message: email.Preview,
|
||||
// Icon: "C:/Program Files/Google Mail/icons/logo.png",
|
||||
// ActivationArguments: "https://gmail.com",
|
||||
// Audio: toast.Mail,
|
||||
// }
|
||||
//
|
||||
// err := toast.Push()
|
||||
type Notification struct {
|
||||
// The name of your app. This value shows up in Windows Action Centre, so make it
|
||||
// something readable for your users.
|
||||
AppID string
|
||||
|
||||
// The main title/heading for the toast notification.
|
||||
Title string
|
||||
|
||||
// The single/multi line message to display for the toast notification.
|
||||
Body string
|
||||
|
||||
// An optional path to an image on the OS to display to the left of the title & message.
|
||||
Icon string
|
||||
|
||||
// An optional crop style for the Icon.
|
||||
IconCrop CropStyle
|
||||
|
||||
// An optional path to an image to display as a bold hero image.
|
||||
HeroIcon string
|
||||
|
||||
// A color to show as the icon background.
|
||||
IconBackgroundColor string
|
||||
|
||||
// Action to take when the notification is as a whole activated.
|
||||
ActivationType ActivationType
|
||||
|
||||
// The activation/action arguments (invoked when the user clicks the notification).
|
||||
// This is returned to the callback when activated.
|
||||
ActivationArguments string
|
||||
|
||||
// Optional text input to display before the actions.
|
||||
Inputs []Input
|
||||
|
||||
// Optional action buttons to display below the notification title & message.
|
||||
Actions []Action
|
||||
|
||||
// The audio to play when displaying the toast
|
||||
Audio toastAudio
|
||||
|
||||
// Whether to loop the audio (default false).
|
||||
Loop bool
|
||||
|
||||
// How long the toast should show up for (short/long).
|
||||
Duration toastDuration
|
||||
|
||||
// This is an absolute path to an executable that will launched by the
|
||||
// Windows Runtime when the COM server is not running. This executable must be able
|
||||
// to handle the -Embedding flag that Windows invokes it with.
|
||||
ActivationExe string
|
||||
}
|
||||
|
||||
// CropStyle specifies the hint-crop attribute for an image.
|
||||
type CropStyle = string
|
||||
|
||||
const (
|
||||
CropStyleEmpty CropStyle = ""
|
||||
CropStyleSquare CropStyle = "square"
|
||||
CropStyleCircle CropStyle = "circle"
|
||||
)
|
||||
|
||||
// UserData contains user supplied data from the notification, such as text input
|
||||
// or a selection.
|
||||
type UserData = wintoast.UserData
|
||||
|
||||
// Input
|
||||
//
|
||||
// Defines an input element, generally a text input.
|
||||
// See https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-input for more info.
|
||||
//
|
||||
// Inputs are by default textual, however if selections are supplied the input will be rendered
|
||||
// as a select input.
|
||||
type Input struct {
|
||||
ID string
|
||||
Title string
|
||||
Placeholder string
|
||||
Selections []InputSelection
|
||||
}
|
||||
|
||||
// InputSelection
|
||||
//
|
||||
// Defines an input selection for use with select inputs.
|
||||
// See https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-selection for more info.
|
||||
type InputSelection struct {
|
||||
ID string
|
||||
Content string
|
||||
}
|
||||
|
||||
// Action
|
||||
//
|
||||
// Defines an actionable button.
|
||||
// See https://msdn.microsoft.com/en-us/windows/uwp/controls-and-patterns/tiles-and-notifications-adaptive-interactive-toasts for more info.
|
||||
//
|
||||
// toast.Action{toast.Protocol, "Open Maps", "bingmaps:?q=sushi"}
|
||||
//
|
||||
// TODO(jfm): we can likely support an activation callback directly in the Action.
|
||||
type Action struct {
|
||||
Type ActivationType
|
||||
Content string
|
||||
Arguments string
|
||||
InputID string // optional ID of any related input, affects styling.
|
||||
}
|
||||
|
||||
// Push the notification to the Windows Runtime via the COM API.
|
||||
// Ensure [SetAppData] has been called prior to pushing notifications.
|
||||
//
|
||||
// notification := toast.Notification{
|
||||
// AppID: "Example App",
|
||||
// Title: "My notification",
|
||||
// Message: "Some message about how important something is...",
|
||||
// Icon: "go.png",
|
||||
// Actions: []toast.Action{
|
||||
// {"protocol", "I'm a button", ""},
|
||||
// {"protocol", "Me too!", ""},
|
||||
// },
|
||||
// }
|
||||
// err := notification.Push()
|
||||
// if err != nil {
|
||||
// log.Fatalln(err)
|
||||
// }
|
||||
func (n *Notification) Push() error {
|
||||
n.applyDefaults()
|
||||
xml, err := n.buildXML()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return wintoast.Push(n.AppID, xml, wintoast.PowershellFallback)
|
||||
}
|
||||
|
||||
func (n *Notification) applyDefaults() {
|
||||
if n.ActivationType == "" {
|
||||
n.ActivationType = Foreground
|
||||
}
|
||||
if n.Duration == "" {
|
||||
n.Duration = Short
|
||||
}
|
||||
if n.Audio == "" {
|
||||
n.Audio = Default
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Notification) buildXML() (string, error) {
|
||||
var out bytes.Buffer
|
||||
err := tmpl.XMLTemplate.Execute(&out, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return out.String(), nil
|
||||
}
|
||||
|
||||
// SetActivationCallback sets the global activation callback.
|
||||
//
|
||||
// The first argument contains application defined data (embedded within the xml),
|
||||
// which is how the callback knows which part of the toast was activated.
|
||||
// Argument data is defined by `toast.Action.Arguments` on the notification.
|
||||
//
|
||||
// The second argument contains user defined data (input/selected by user).
|
||||
// All elements of user input will be supplied here, even if the value is empty.
|
||||
// User inputs correspond to all `toast.Input`s defined on the notification.
|
||||
//
|
||||
// This function will be invoked when a toast notification is interacted with.
|
||||
//
|
||||
// This will do nothing if the the powershell fallback is in-effect.
|
||||
func SetActivationCallback(cb func(args string, data []UserData)) {
|
||||
wintoast.SetActivationCallback(func(appUserModelId, invokedArgs string, userData []wintoast.UserData) {
|
||||
cb(invokedArgs, userData)
|
||||
})
|
||||
}
|
||||
|
||||
type AppData = wintoast.AppData
|
||||
|
||||
// SetAppData sets application metadata in the Windows Registry.
|
||||
// This is required to display the application name, as well as any branding.
|
||||
// Registry is global state, hence it makes sense to set it global.
|
||||
func SetAppData(data AppData) error {
|
||||
return wintoast.SetAppData(data)
|
||||
}
|
||||
89
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/wintoast/bind.go
vendored
Normal file
89
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/wintoast/bind.go
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// Package wintoast provides a pure-Go implementation of toast notifications on Windows.
|
||||
package wintoast
|
||||
|
||||
import "errors"
|
||||
|
||||
// AppData describes the application to the Windows Runtime.
|
||||
// See toast.Notification for more thorough documentation off these fields.
|
||||
type AppData struct {
|
||||
AppID string
|
||||
GUID string
|
||||
ActivationExe string // optional
|
||||
IconPath string // optional
|
||||
IconBackgroundColor string // optional
|
||||
}
|
||||
|
||||
// UserData contains Key:Value pairs generated within the notification, based
|
||||
// on the XML content of the notification. Specifically, all inputs within
|
||||
// the XML will generate a corresponding UserData struct.
|
||||
type UserData struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
// Callback is a function that gets invoked when the notification is activated.
|
||||
type Callback func(appUserModelId string, invokedArgs string, userData []UserData)
|
||||
|
||||
// SetAppData teaches the Windows Runtime about our application and establishes the activation GUID
|
||||
// so Windows will know how to invoke us back.
|
||||
func SetAppData(data AppData) (err error) {
|
||||
return setAppData(data)
|
||||
}
|
||||
|
||||
// SetActivationCallback establishes the callback `cb` to be invoked when
|
||||
// the toast notification is activated. This callback instance should handle
|
||||
// being activated from any available toast notification.
|
||||
func SetActivationCallback(cb Callback) {
|
||||
callback = cb
|
||||
}
|
||||
|
||||
// Push a notification described by the XML to the Windows Runtime.
|
||||
//
|
||||
// App data should be set first via a call to SetAppData before calling
|
||||
// this function.
|
||||
//
|
||||
// If the powershell fallback is engaged, activation callbacks will not
|
||||
// work as expected and the COM error will still be returned.
|
||||
func Push(appID, xml string, op ...option) error {
|
||||
var opts options
|
||||
for _, opt := range op {
|
||||
opt(&opts)
|
||||
}
|
||||
if opts.PowershellPreferred {
|
||||
return pushPowershell(xml)
|
||||
}
|
||||
if appID == "" {
|
||||
appID = appData.AppID
|
||||
}
|
||||
if err := pushCOM(appID, xml); err != nil {
|
||||
if opts.PowershellFallback {
|
||||
return errors.Join(err, pushPowershell(xml))
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type options struct {
|
||||
PowershellFallback bool
|
||||
PowershellPreferred bool
|
||||
}
|
||||
|
||||
type option func(*options)
|
||||
|
||||
// PreferPowershell indicates to use the powershell method by default.
|
||||
// COM will not be used.
|
||||
func PreferPowershell(opt *options) {
|
||||
opt.PowershellPreferred = true
|
||||
}
|
||||
|
||||
// PowershellFallback specifies to use the powershell method as a fallback
|
||||
// if the COM api fails.
|
||||
func PowershellFallback(opt *options) {
|
||||
opt.PowershellFallback = true
|
||||
}
|
||||
|
||||
// callback is the global callback reference that is invoked by Activate.
|
||||
//
|
||||
// NOTE(jfm): synchronize access to this?
|
||||
var callback Callback = func(model, args string, data []UserData) {}
|
||||
21
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/wintoast/bind_noop.go
vendored
Normal file
21
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/wintoast/bind_noop.go
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
//go:build !windows
|
||||
|
||||
package wintoast
|
||||
|
||||
var appData AppData
|
||||
|
||||
func setAppData(data AppData) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateToast(appID string, xml string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func pushPowershell(xml string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func pushCOM(appID, xml string) error {
|
||||
return nil
|
||||
}
|
||||
211
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/wintoast/bind_windows.go
vendored
Normal file
211
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/wintoast/bind_windows.go
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
//go:build windows
|
||||
|
||||
package wintoast
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"git.sr.ht/~jackmordaunt/go-toast/v2/internal/winrt/data/xml/dom"
|
||||
"git.sr.ht/~jackmordaunt/go-toast/v2/internal/winrt/ui/notifications"
|
||||
"git.sr.ht/~jackmordaunt/go-toast/v2/tmpl"
|
||||
"github.com/go-ole/go-ole"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func pushPowershell(xml string) error {
|
||||
f, err := os.CreateTemp("", "*.ps1")
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating temporary script file: %w", err)
|
||||
}
|
||||
|
||||
defer func() { err = errors.Join(err, os.Remove(f.Name())) }()
|
||||
|
||||
// This BOM ensures we can support non-ascii characters in the toast content.
|
||||
bomUtf8 := []byte{0xef, 0xbb, 0xbf}
|
||||
if _, err := f.Write(bomUtf8); err != nil {
|
||||
return fmt.Errorf("writing utf8 byte marker: %w", err)
|
||||
}
|
||||
|
||||
if err := buildPowershell(xml, f); err != nil {
|
||||
return fmt.Errorf("generating powershell script: %w", err)
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
return fmt.Errorf("closing script file: %w", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command("PowerShell", "-ExecutionPolicy", "Bypass", "-File", f.Name())
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("executing powershell: %q: %w", string(out), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildPowershell(xml string, w io.Writer) error {
|
||||
type scriptData struct {
|
||||
AppID string
|
||||
XML string
|
||||
}
|
||||
return tmpl.ScriptTemplate.Execute(w, scriptData{AppID: appData.AppID, XML: xml})
|
||||
}
|
||||
|
||||
// HRESULT E_NOINTERFACE
|
||||
const errNoInterface = 0x80004002
|
||||
|
||||
var comDisabled atomic.Bool
|
||||
|
||||
func pushCOM(appID, xml string) (err error) {
|
||||
if comDisabled.Load() {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// On Windows 7 WinRT interfaces can be stubbed out, and fail to produce
|
||||
// error values. This leads to a panic when trying to use the interface.
|
||||
// This recover transforms such panics back into an error value for the
|
||||
// caller.
|
||||
//
|
||||
// If the error is "interface not supported" we will permanently disable
|
||||
// this API henceforth.
|
||||
if v := recover(); v != nil {
|
||||
if verr, ok := v.(error); ok {
|
||||
err = verr
|
||||
}
|
||||
if oleErr, ok := v.(*ole.OleError); ok {
|
||||
if oleErr.Code() == errNoInterface {
|
||||
comDisabled.Store(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err := initialize(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := registerClassFactory(ClassFactory); err != nil {
|
||||
return fmt.Errorf("registering class factory: %w", err)
|
||||
}
|
||||
|
||||
doc, err := dom.NewXmlDocument()
|
||||
if err != nil {
|
||||
return fmt.Errorf("dom.NewXmlDocument(): %w", err)
|
||||
}
|
||||
|
||||
defer doc.Release()
|
||||
|
||||
if err := doc.LoadXml(xml); err != nil {
|
||||
return fmt.Errorf("doc.LoadXml(tmpl): %w", err)
|
||||
}
|
||||
|
||||
manager, err := notifications.GetDefault()
|
||||
if err != nil {
|
||||
return fmt.Errorf("notifications.GetDefault(): %w", err)
|
||||
}
|
||||
|
||||
defer manager.Release()
|
||||
|
||||
notifier, err := manager.CreateToastNotifierWithId(appID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("manager.CreateToastNotifier(%q): %w", appID, err)
|
||||
}
|
||||
|
||||
defer notifier.Release()
|
||||
|
||||
toast, err := notifications.CreateToastNotification(doc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("notifications.CreateToastNotification(doc): %w", err)
|
||||
}
|
||||
|
||||
defer toast.Release()
|
||||
|
||||
if err := notifier.Show(toast); err != nil {
|
||||
return fmt.Errorf("notifier.Show(): %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setAppData(data AppData) (err error) {
|
||||
appDataMu.Lock()
|
||||
defer appDataMu.Unlock()
|
||||
|
||||
// Early out if we have already set this data.
|
||||
//
|
||||
// In the case the data is empty, we don't want to overrite
|
||||
// all of the registry entries to empty.
|
||||
//
|
||||
// This allows the caller to either globally set the app data
|
||||
// or provide it per notification.
|
||||
if appData == data || data.AppID == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if data.GUID != "" {
|
||||
GUID_ImplNotificationActivationCallback = ole.NewGUID(data.GUID)
|
||||
}
|
||||
|
||||
// Keep a copy of the saved data for later.
|
||||
defer func() {
|
||||
if err == nil {
|
||||
appData = data
|
||||
}
|
||||
}()
|
||||
|
||||
if err := setAppDataFunc(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var initialized atomic.Bool
|
||||
|
||||
// initialize attempts to initialize the Windows Runtime.
|
||||
// Each invocation will retry RoInitialize until a successful initialization
|
||||
// is achieved. Once initialized, we avoid invoking RoInitialize since subsequent
|
||||
// reinitialization generates errors.
|
||||
func initialize() (err error) {
|
||||
if initialized.CompareAndSwap(false, true) {
|
||||
if err := ole.RoInitialize(1); err != nil {
|
||||
return fmt.Errorf("RoInitialize: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sliceUserDataFromUnsafe builds a slice of UserData out of an unsafe pointer.
|
||||
func sliceUserDataFromUnsafe(ptr unsafe.Pointer, count int) []UserData {
|
||||
// Layout mirrors the memory layout of the C struct that contains this data.
|
||||
// I'm not sure if there's special alignment or packing - though I don't notice
|
||||
// anything in the definition to indicate as such.
|
||||
type layout struct {
|
||||
Key unsafe.Pointer
|
||||
Value unsafe.Pointer
|
||||
}
|
||||
|
||||
// Create a new slice with the appropriate length
|
||||
out := make([]UserData, count)
|
||||
|
||||
// Create a slice with the unsafe data layout.
|
||||
tmp := unsafe.Slice((*layout)(ptr), count)
|
||||
|
||||
// Convert the unsafe layout to safe strings.
|
||||
for ii, it := range tmp {
|
||||
out[ii] = UserData{
|
||||
Key: windows.UTF16PtrToString((*uint16)(it.Key)),
|
||||
Value: windows.UTF16PtrToString((*uint16)(it.Value)),
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
179
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/wintoast/impl.go
vendored
Normal file
179
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/wintoast/impl.go
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
//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
|
||||
})
|
||||
)
|
||||
37
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/wintoast/procs.go
vendored
Normal file
37
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/wintoast/procs.go
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
//go:build windows
|
||||
|
||||
package wintoast
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/go-ole/go-ole"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
// Define procs that go-ole doesn't provide. This is how we register our Go-implemented
|
||||
// COM objects.
|
||||
modcombase = windows.NewLazySystemDLL("combase.dll")
|
||||
procRegisterClassObject = modcombase.NewProc("CoRegisterClassObject")
|
||||
)
|
||||
|
||||
// registerClassFactory teaches the Windows Runtime about our factory that can allocate
|
||||
// instances of our ActivationCallback.
|
||||
func registerClassFactory(factory *IClassFactory) error {
|
||||
// cookie is used as a handle to this class. It is used when calling CoRevokeClassObject
|
||||
// which unregisters the class. We don't need it until we plan to revoke this registration
|
||||
// for some reason.
|
||||
var cookie int64
|
||||
hr, _, _ := procRegisterClassObject.Call(
|
||||
uintptr(unsafe.Pointer(GUID_ImplNotificationActivationCallback)),
|
||||
uintptr(unsafe.Pointer(factory)),
|
||||
uintptr(ole.CLSCTX_LOCAL_SERVER),
|
||||
uintptr(1), /* REGCLS_MULTIPLEUSE */
|
||||
uintptr(unsafe.Pointer(&cookie)),
|
||||
)
|
||||
if hr != ole.S_OK {
|
||||
return ole.NewError(hr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
110
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/wintoast/registry.go
vendored
Normal file
110
vendor/git.sr.ht/~jackmordaunt/go-toast/v2/wintoast/registry.go
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
//go:build windows
|
||||
|
||||
// This file contains registry manipulation code.
|
||||
// This logic is orthogonal to, but works in tandem with the COM code; since the
|
||||
// Windows Runtime uses the registry as it's primary source of state.
|
||||
package wintoast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
var (
|
||||
// allows diffing the new call from the previous so that we can early-out,
|
||||
// and avoid touching the registry more than necessary.
|
||||
// It also allows empty app data to be supplied to the Notifcation type,
|
||||
// without erasing the data that has been set via the global function.
|
||||
appData AppData
|
||||
appDataMu sync.Mutex
|
||||
)
|
||||
|
||||
// Overridden in testing.
|
||||
var (
|
||||
writeStringValue = writeStringValueImpl
|
||||
setAppDataFunc = setAppDataImpl
|
||||
)
|
||||
|
||||
var (
|
||||
// appKeyRoot is the root path for app metadata.
|
||||
appKeyRoot = filepath.Join("SOFTWARE", "Classes", "AppUserModelId")
|
||||
// activationKey is the root path to the activation executable.
|
||||
activationKey = filepath.Join("SOFTWARE", "Classes", "CLSID", GUID_ImplNotificationActivationCallback.String(), "LocalServer32")
|
||||
)
|
||||
|
||||
// The Windows registry package uses empty string for the "(Default)" key.
|
||||
const registryDefaultKey string = ""
|
||||
|
||||
func setAppDataImpl(data AppData) error {
|
||||
if data.AppID == "" {
|
||||
return fmt.Errorf("empty app ID")
|
||||
}
|
||||
|
||||
appKey := filepath.Join(appKeyRoot, data.AppID)
|
||||
|
||||
if err := writeStringValue(appKey, "DisplayName", data.AppID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// CustomActivator teaches Window what COM class to use as the callback when
|
||||
// a toast notification is activated.
|
||||
if err := writeStringValue(appKey, "CustomActivator", GUID_ImplNotificationActivationCallback.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if data.IconPath != "" {
|
||||
if err := writeStringValue(appKey, "IconUri", data.IconPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if data.IconBackgroundColor != "" {
|
||||
if err := writeStringValue(appKey, "IconBackgroundColor", data.IconBackgroundColor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if data.ActivationExe != "" {
|
||||
if err := writeStringValue(activationKey, registryDefaultKey, data.ActivationExe); err != nil {
|
||||
return fmt.Errorf("setting activation executable: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeStringValue writes a string value to the path, where name is the subkey and
|
||||
// value is the literal value.
|
||||
func writeStringValueImpl(path, name, value string) error {
|
||||
if keyExists(path, name) {
|
||||
return nil
|
||||
}
|
||||
key, _, err := registry.CreateKey(registry.CURRENT_USER, path, registry.SET_VALUE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening registry key: %s: %w", path, err)
|
||||
}
|
||||
if err := key.SetStringValue(name, value); err != nil {
|
||||
return fmt.Errorf("setting string value: (%s) %s=%s: %w", path, name, value, err)
|
||||
}
|
||||
if err := key.Close(); err != nil {
|
||||
return fmt.Errorf("closing key: %s: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// keyExists returns true if the key exists.
|
||||
func keyExists(path, name string) bool {
|
||||
key, err := registry.OpenKey(registry.CURRENT_USER, path, registry.READ)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer key.Close()
|
||||
v, _, err := key.GetStringValue(name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return v != ""
|
||||
}
|
||||
Reference in New Issue
Block a user