mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
5191409708
Use HWND_TOPMOST and HWND_NOTOPMOST when applying Config.TopMost on Windows. Leave SWP_NOZORDER set when TopMost hasn't changed, so other Configure calls don't reorder the window. Signed-off-by: qiannian <qianniancn@gmail.com>
1304 lines
33 KiB
Go
1304 lines
33 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package app
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"gioui.org/io/transfer"
|
|
syscall "golang.org/x/sys/windows"
|
|
"golang.org/x/sys/windows/registry"
|
|
"image"
|
|
"io"
|
|
"os"
|
|
"runtime"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
"unsafe"
|
|
|
|
"gioui.org/app/internal/windows"
|
|
"gioui.org/op"
|
|
"gioui.org/unit"
|
|
gowindows "golang.org/x/sys/windows"
|
|
|
|
"gioui.org/f32"
|
|
"gioui.org/io/event"
|
|
"gioui.org/io/key"
|
|
"gioui.org/io/pointer"
|
|
"gioui.org/io/system"
|
|
)
|
|
|
|
type Win32ViewEvent struct {
|
|
HWND uintptr
|
|
}
|
|
|
|
type window struct {
|
|
hwnd syscall.Handle
|
|
hdc syscall.Handle
|
|
w *callbacks
|
|
|
|
// cursorIn tracks whether the cursor was inside the window according
|
|
// to the most recent WM_SETCURSOR.
|
|
cursorIn bool
|
|
cursor syscall.Handle
|
|
|
|
animating bool
|
|
|
|
borderSize image.Point
|
|
config Config
|
|
// frameDims stores the last seen window frame width and height.
|
|
frameDims image.Point
|
|
loop *eventLoop
|
|
}
|
|
|
|
const _WM_WAKEUP = windows.WM_USER + iota
|
|
|
|
const copyDataURLType = 0xffffff00
|
|
|
|
type gpuAPI struct {
|
|
priority int
|
|
initializer func(w *window) (context, error)
|
|
}
|
|
|
|
// drivers is the list of potential Context implementations.
|
|
var drivers []gpuAPI
|
|
|
|
// winMap maps win32 HWNDs to *windows.
|
|
var winMap sync.Map
|
|
|
|
// iconID is the ID of the icon in the resource file.
|
|
const iconID = 1
|
|
|
|
var resources struct {
|
|
once sync.Once
|
|
// handle is the module handle from GetModuleHandle.
|
|
handle syscall.Handle
|
|
// class is the Gio window class from RegisterClassEx.
|
|
class uint16
|
|
// cursor is the arrow cursor resource.
|
|
cursor syscall.Handle
|
|
}
|
|
|
|
func osMain() {
|
|
processURLEvent(startupURI())
|
|
select {}
|
|
}
|
|
|
|
func newWindow(win *callbacks, options []Option) {
|
|
done := make(chan struct{})
|
|
go func() {
|
|
// GetMessage and PeekMessage can filter on a window HWND, but
|
|
// then thread-specific messages such as WM_QUIT are ignored.
|
|
// Instead lock the thread so window messages arrive through
|
|
// unfiltered GetMessage calls.
|
|
runtime.LockOSThread()
|
|
|
|
w := &window{
|
|
w: win,
|
|
}
|
|
w.loop = newEventLoop(w.w, w.wakeup)
|
|
w.w.SetDriver(w)
|
|
err := w.init()
|
|
done <- struct{}{}
|
|
if err != nil {
|
|
w.ProcessEvent(DestroyEvent{Err: err})
|
|
return
|
|
}
|
|
winMap.Store(w.hwnd, w)
|
|
defer winMap.Delete(w.hwnd)
|
|
w.Configure(options)
|
|
w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)})
|
|
windows.SetForegroundWindow(w.hwnd)
|
|
windows.SetFocus(w.hwnd)
|
|
// Since the window class for the cursor is null,
|
|
// set it here to show the cursor.
|
|
w.SetCursor(pointer.CursorDefault)
|
|
w.runLoop()
|
|
}()
|
|
<-done
|
|
}
|
|
|
|
// initResources initializes the resources global.
|
|
func initResources() error {
|
|
windows.SetProcessDPIAware()
|
|
hInst, err := windows.GetModuleHandle()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resources.handle = hInst
|
|
c, err := windows.LoadCursor(windows.IDC_ARROW)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resources.cursor = c
|
|
icon, _ := windows.LoadImage(hInst, iconID, windows.IMAGE_ICON, 0, 0, windows.LR_DEFAULTSIZE|windows.LR_SHARED)
|
|
|
|
appid, err := syscall.UTF16PtrFromString(ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
wcls := windows.WndClassEx{
|
|
CbSize: uint32(unsafe.Sizeof(windows.WndClassEx{})),
|
|
Style: windows.CS_HREDRAW | windows.CS_VREDRAW | windows.CS_OWNDC,
|
|
LpfnWndProc: syscall.NewCallback(windowProc),
|
|
HInstance: hInst,
|
|
HIcon: icon,
|
|
LpszClassName: appid,
|
|
}
|
|
cls, err := windows.RegisterClassEx(&wcls)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resources.class = cls
|
|
return nil
|
|
}
|
|
|
|
const dwExStyle = windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE
|
|
|
|
func (w *window) init() error {
|
|
var resErr error
|
|
resources.once.Do(func() {
|
|
resErr = initResources()
|
|
})
|
|
if resErr != nil {
|
|
return resErr
|
|
}
|
|
const dwStyle = windows.WS_OVERLAPPEDWINDOW
|
|
|
|
hwnd, err := windows.CreateWindowEx(
|
|
dwExStyle,
|
|
resources.class,
|
|
"",
|
|
dwStyle|windows.WS_CLIPSIBLINGS|windows.WS_CLIPCHILDREN,
|
|
windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
|
|
windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
|
|
0,
|
|
0,
|
|
resources.handle,
|
|
0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := windows.RegisterTouchWindow(hwnd, 0); err != nil {
|
|
return err
|
|
}
|
|
if err := windows.EnableMouseInPointer(1); err != nil {
|
|
return err
|
|
}
|
|
w.hdc, err = windows.GetDC(hwnd)
|
|
if err != nil {
|
|
windows.DestroyWindow(hwnd)
|
|
return err
|
|
}
|
|
w.hwnd = hwnd
|
|
return nil
|
|
}
|
|
|
|
// update handles changes done by the user, and updates the configuration.
|
|
// It reads the window style and size/position and updates w.config.
|
|
// If anything has changed it emits a ConfigEvent to notify the application.
|
|
func (w *window) update() {
|
|
p := windows.GetWindowPlacement(w.hwnd)
|
|
if !p.IsMinimized() {
|
|
r := windows.GetWindowRect(w.hwnd)
|
|
cr := windows.GetClientRect(w.hwnd)
|
|
w.config.Size = image.Point{
|
|
X: int(cr.Right - cr.Left),
|
|
Y: int(cr.Bottom - cr.Top),
|
|
}
|
|
w.frameDims = image.Point{
|
|
X: int(r.Right - r.Left),
|
|
Y: int(r.Bottom - r.Top),
|
|
}.Sub(w.config.Size)
|
|
}
|
|
|
|
w.borderSize = image.Pt(
|
|
windows.GetSystemMetrics(windows.SM_CXSIZEFRAME),
|
|
windows.GetSystemMetrics(windows.SM_CYSIZEFRAME),
|
|
)
|
|
style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
|
|
switch {
|
|
case p.IsMaximized() && style&windows.WS_OVERLAPPEDWINDOW != 0:
|
|
w.config.Mode = Maximized
|
|
case p.IsMaximized():
|
|
w.config.Mode = Fullscreen
|
|
default:
|
|
w.config.Mode = Windowed
|
|
}
|
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
|
w.draw(true)
|
|
}
|
|
|
|
func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
|
|
win, exists := winMap.Load(hwnd)
|
|
if !exists {
|
|
return windows.DefWindowProc(hwnd, msg, wParam, lParam)
|
|
}
|
|
|
|
w := win.(*window)
|
|
|
|
switch msg {
|
|
case windows.WM_UNICHAR:
|
|
if wParam == windows.UNICODE_NOCHAR {
|
|
// Tell the system that we accept WM_UNICHAR messages.
|
|
return windows.TRUE
|
|
}
|
|
fallthrough
|
|
case windows.WM_CHAR:
|
|
if r := rune(wParam); unicode.IsPrint(r) {
|
|
w.w.EditorInsert(string(r))
|
|
}
|
|
// The message is processed.
|
|
return windows.TRUE
|
|
case windows.WM_DPICHANGED:
|
|
// Let Windows know we're prepared for runtime DPI changes.
|
|
return windows.TRUE
|
|
case windows.WM_ERASEBKGND:
|
|
// Avoid flickering between GPU content and background color.
|
|
return windows.TRUE
|
|
case windows.WM_KEYDOWN, windows.WM_KEYUP, windows.WM_SYSKEYDOWN, windows.WM_SYSKEYUP:
|
|
if n, ok := convertKeyCode(wParam); ok {
|
|
e := key.Event{
|
|
Name: n,
|
|
Modifiers: getModifiers(),
|
|
State: key.Press,
|
|
}
|
|
if msg == windows.WM_KEYUP || msg == windows.WM_SYSKEYUP {
|
|
e.State = key.Release
|
|
}
|
|
|
|
w.ProcessEvent(e)
|
|
|
|
if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
|
|
// Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs
|
|
// such as cmd.exe and graphical debuggers also reserve F10.
|
|
return 0
|
|
}
|
|
}
|
|
case windows.WM_POINTERDOWN, windows.WM_POINTERUP, windows.WM_POINTERUPDATE, windows.WM_POINTERCAPTURECHANGED:
|
|
pid := getPointerIDwParam(wParam)
|
|
pi, err := windows.GetPointerInfo(uint32(pid))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
switch msg {
|
|
case windows.WM_POINTERDOWN:
|
|
windows.SetCapture(w.hwnd)
|
|
case windows.WM_POINTERUP:
|
|
windows.ReleaseCapture()
|
|
}
|
|
|
|
kind := pointer.Move
|
|
switch pi.ButtonChangeType {
|
|
case windows.POINTER_CHANGE_FIRSTBUTTON_DOWN, windows.POINTER_CHANGE_SECONDBUTTON_DOWN, windows.POINTER_CHANGE_THIRDBUTTON_DOWN, windows.POINTER_CHANGE_FOURTHBUTTON_DOWN, windows.POINTER_CHANGE_FIFTHBUTTON_DOWN:
|
|
kind = pointer.Press
|
|
case windows.POINTER_CHANGE_FIRSTBUTTON_UP, windows.POINTER_CHANGE_SECONDBUTTON_UP, windows.POINTER_CHANGE_THIRDBUTTON_UP, windows.POINTER_CHANGE_FOURTHBUTTON_UP, windows.POINTER_CHANGE_FIFTHBUTTON_UP:
|
|
kind = pointer.Release
|
|
}
|
|
|
|
if (pi.PointerFlags&windows.POINTER_FLAG_CANCELED != 0) || (msg == windows.WM_POINTERCAPTURECHANGED) {
|
|
kind = pointer.Cancel
|
|
}
|
|
|
|
w.pointerUpdate(pi, pid, kind, lParam)
|
|
case windows.WM_CANCELMODE:
|
|
w.ProcessEvent(pointer.Event{
|
|
Kind: pointer.Cancel,
|
|
})
|
|
case windows.WM_SETFOCUS:
|
|
w.config.Focused = true
|
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
|
case windows.WM_KILLFOCUS:
|
|
w.config.Focused = false
|
|
w.ProcessEvent(ConfigEvent{Config: w.config})
|
|
case windows.WM_NCHITTEST:
|
|
if w.config.Decorated {
|
|
// Let the system handle it.
|
|
break
|
|
}
|
|
x, y := coordsFromlParam(lParam)
|
|
np := windows.Point{X: int32(x), Y: int32(y)}
|
|
windows.ScreenToClient(w.hwnd, &np)
|
|
return w.hitTest(int(np.X), int(np.Y))
|
|
case windows.WM_POINTERWHEEL:
|
|
w.scrollEvent(wParam, lParam, false, getModifiers())
|
|
case windows.WM_POINTERHWHEEL:
|
|
w.scrollEvent(wParam, lParam, true, getModifiers())
|
|
case windows.WM_DESTROY:
|
|
w.ProcessEvent(Win32ViewEvent{})
|
|
w.ProcessEvent(DestroyEvent{})
|
|
w.w = nil
|
|
if w.hdc != 0 {
|
|
windows.ReleaseDC(w.hdc)
|
|
w.hdc = 0
|
|
}
|
|
// The system destroys the HWND for us.
|
|
w.hwnd = 0
|
|
windows.PostQuitMessage(0)
|
|
return 0
|
|
case windows.WM_NCCALCSIZE:
|
|
if w.config.Decorated {
|
|
// Let Windows handle decorations.
|
|
break
|
|
}
|
|
// No client areas; we draw decorations ourselves.
|
|
if wParam != 1 {
|
|
return 0
|
|
}
|
|
// lParam contains an NCCALCSIZE_PARAMS for us to adjust.
|
|
place := windows.GetWindowPlacement(w.hwnd)
|
|
if !place.IsMaximized() {
|
|
// Nothing do adjust.
|
|
return 0
|
|
}
|
|
// Adjust window position to avoid the extra padding in maximized
|
|
// state. See https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543.
|
|
// Note that trying to do the adjustment in WM_GETMINMAXINFO is ignored by Windows.
|
|
szp := (*windows.NCCalcSizeParams)(unsafe.Pointer(lParam))
|
|
mi := windows.GetMonitorInfo(w.hwnd)
|
|
szp.Rgrc[0] = mi.WorkArea
|
|
return 0
|
|
case windows.WM_PAINT:
|
|
w.draw(true)
|
|
case windows.WM_STYLECHANGED:
|
|
w.update()
|
|
case windows.WM_WINDOWPOSCHANGED:
|
|
w.update()
|
|
return 0
|
|
case windows.WM_GETMINMAXINFO:
|
|
mm := (*windows.MinMaxInfo)(unsafe.Pointer(lParam))
|
|
|
|
var frameDims image.Point
|
|
if w.config.Decorated {
|
|
frameDims = w.frameDims
|
|
}
|
|
if p := w.config.MinSize; p.X > 0 || p.Y > 0 {
|
|
p = p.Add(frameDims)
|
|
mm.PtMinTrackSize = windows.Point{
|
|
X: int32(p.X),
|
|
Y: int32(p.Y),
|
|
}
|
|
}
|
|
if p := w.config.MaxSize; p.X > 0 || p.Y > 0 {
|
|
p = p.Add(frameDims)
|
|
mm.PtMaxTrackSize = windows.Point{
|
|
X: int32(p.X),
|
|
Y: int32(p.Y),
|
|
}
|
|
}
|
|
return 0
|
|
case windows.WM_SETCURSOR:
|
|
w.cursorIn = (lParam & 0xffff) == windows.HTCLIENT
|
|
if w.cursorIn {
|
|
windows.SetCursor(w.cursor)
|
|
return windows.TRUE
|
|
}
|
|
case _WM_WAKEUP:
|
|
w.loop.Wakeup()
|
|
w.loop.FlushEvents()
|
|
case windows.WM_IME_STARTCOMPOSITION:
|
|
imc := windows.ImmGetContext(w.hwnd)
|
|
if imc == 0 {
|
|
return windows.TRUE
|
|
}
|
|
defer windows.ImmReleaseContext(w.hwnd, imc)
|
|
sel := w.w.EditorState().Selection
|
|
caret := sel.Transform.Transform(sel.Caret.Pos.Add(f32.Pt(0, sel.Caret.Descent)))
|
|
icaret := image.Pt(int(caret.X+.5), int(caret.Y+.5))
|
|
windows.ImmSetCompositionWindow(imc, icaret.X, icaret.Y)
|
|
windows.ImmSetCandidateWindow(imc, icaret.X, icaret.Y)
|
|
return windows.TRUE
|
|
case windows.WM_IME_COMPOSITION:
|
|
imc := windows.ImmGetContext(w.hwnd)
|
|
if imc == 0 {
|
|
return windows.TRUE
|
|
}
|
|
defer windows.ImmReleaseContext(w.hwnd, imc)
|
|
state := w.w.EditorState()
|
|
rng := state.compose
|
|
if rng.Start == -1 {
|
|
rng = state.Selection.Range
|
|
}
|
|
if rng.Start > rng.End {
|
|
rng.Start, rng.End = rng.End, rng.Start
|
|
}
|
|
var replacement string
|
|
switch {
|
|
case lParam&windows.GCS_RESULTSTR != 0:
|
|
replacement = windows.ImmGetCompositionString(imc, windows.GCS_RESULTSTR)
|
|
case lParam&windows.GCS_COMPSTR != 0:
|
|
replacement = windows.ImmGetCompositionString(imc, windows.GCS_COMPSTR)
|
|
}
|
|
end := rng.Start + utf8.RuneCountInString(replacement)
|
|
w.w.EditorReplace(rng, replacement)
|
|
state = w.w.EditorState()
|
|
comp := key.Range{
|
|
Start: rng.Start,
|
|
End: end,
|
|
}
|
|
if lParam&windows.GCS_DELTASTART != 0 {
|
|
start := windows.ImmGetCompositionValue(imc, windows.GCS_DELTASTART)
|
|
comp.Start = state.RunesIndex(state.UTF16Index(comp.Start) + start)
|
|
}
|
|
w.w.SetComposingRegion(comp)
|
|
pos := end
|
|
if lParam&windows.GCS_CURSORPOS != 0 {
|
|
rel := windows.ImmGetCompositionValue(imc, windows.GCS_CURSORPOS)
|
|
pos = state.RunesIndex(state.UTF16Index(rng.Start) + rel)
|
|
}
|
|
w.w.SetEditorSelection(key.Range{Start: pos, End: pos})
|
|
return windows.TRUE
|
|
case windows.WM_IME_ENDCOMPOSITION:
|
|
w.w.SetComposingRegion(key.Range{Start: -1, End: -1})
|
|
return windows.TRUE
|
|
case windows.WM_COPYDATA:
|
|
data := (*windows.CopyDataStruct)(unsafe.Pointer(lParam))
|
|
switch data.DwData {
|
|
case copyDataURLType:
|
|
if schemesURI == "" {
|
|
return windows.TRUE
|
|
}
|
|
|
|
uri := syscall.UTF16PtrToString((*uint16)(unsafe.Pointer(data.LpData)))
|
|
if processURLEvent(uri) {
|
|
w.Perform(system.ActionRaise)
|
|
}
|
|
return windows.TRUE
|
|
}
|
|
}
|
|
|
|
return windows.DefWindowProc(hwnd, msg, wParam, lParam)
|
|
}
|
|
|
|
func getModifiers() key.Modifiers {
|
|
var kmods key.Modifiers
|
|
if windows.GetKeyState(windows.VK_LWIN)&0x1000 != 0 || windows.GetKeyState(windows.VK_RWIN)&0x1000 != 0 {
|
|
kmods |= key.ModSuper
|
|
}
|
|
if windows.GetKeyState(windows.VK_MENU)&0x1000 != 0 {
|
|
kmods |= key.ModAlt
|
|
}
|
|
if windows.GetKeyState(windows.VK_CONTROL)&0x1000 != 0 {
|
|
kmods |= key.ModCtrl
|
|
}
|
|
if windows.GetKeyState(windows.VK_SHIFT)&0x1000 != 0 {
|
|
kmods |= key.ModShift
|
|
}
|
|
return kmods
|
|
}
|
|
|
|
// hitTest returns the non-client area hit by the point, needed to
|
|
// process WM_NCHITTEST.
|
|
func (w *window) hitTest(x, y int) uintptr {
|
|
if w.config.Mode == Windowed {
|
|
// Check for resize handle before system actions; otherwise it can be impossible to
|
|
// resize a custom-decorations window when the system move area is flush with the
|
|
// edge of the window.
|
|
top := y <= w.borderSize.Y
|
|
bottom := y >= w.config.Size.Y-w.borderSize.Y
|
|
left := x <= w.borderSize.X
|
|
right := x >= w.config.Size.X-w.borderSize.X
|
|
switch {
|
|
case top && left:
|
|
return windows.HTTOPLEFT
|
|
case top && right:
|
|
return windows.HTTOPRIGHT
|
|
case bottom && left:
|
|
return windows.HTBOTTOMLEFT
|
|
case bottom && right:
|
|
return windows.HTBOTTOMRIGHT
|
|
case top:
|
|
return windows.HTTOP
|
|
case bottom:
|
|
return windows.HTBOTTOM
|
|
case left:
|
|
return windows.HTLEFT
|
|
case right:
|
|
return windows.HTRIGHT
|
|
}
|
|
}
|
|
p := f32.Pt(float32(x), float32(y))
|
|
if a, ok := w.w.ActionAt(p); ok && a == system.ActionMove {
|
|
return windows.HTCAPTION
|
|
}
|
|
return windows.HTCLIENT
|
|
}
|
|
|
|
func (w *window) pointerUpdate(pi windows.PointerInfo, pid pointer.ID, kind pointer.Kind, lParam uintptr) {
|
|
if !w.config.Focused {
|
|
windows.SetFocus(w.hwnd)
|
|
}
|
|
|
|
src := pointer.Touch
|
|
if pi.PointerType == windows.PT_MOUSE {
|
|
src = pointer.Mouse
|
|
}
|
|
|
|
x, y := coordsFromlParam(lParam)
|
|
np := windows.Point{X: int32(x), Y: int32(y)}
|
|
windows.ScreenToClient(w.hwnd, &np)
|
|
p := f32.Point{X: float32(np.X), Y: float32(np.Y)}
|
|
w.ProcessEvent(pointer.Event{
|
|
Kind: kind,
|
|
Source: src,
|
|
Position: p,
|
|
PointerID: pid,
|
|
Buttons: getPointerButtons(pi),
|
|
Time: windows.GetMessageTime(),
|
|
Modifiers: getModifiers(),
|
|
})
|
|
}
|
|
|
|
func coordsFromlParam(lParam uintptr) (int, int) {
|
|
x := int(int16(lParam & 0xffff))
|
|
y := int(int16((lParam >> 16) & 0xffff))
|
|
return x, y
|
|
}
|
|
|
|
func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.Modifiers) {
|
|
pid := getPointerIDwParam(wParam)
|
|
pi, err := windows.GetPointerInfo(uint32(pid))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
x, y := coordsFromlParam(lParam)
|
|
// The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
|
|
// to other mouse events.
|
|
np := windows.Point{X: int32(x), Y: int32(y)}
|
|
windows.ScreenToClient(w.hwnd, &np)
|
|
p := f32.Point{X: float32(np.X), Y: float32(np.Y)}
|
|
dist := float32(int16(wParam >> 16))
|
|
var sp f32.Point
|
|
if horizontal {
|
|
sp.X = dist
|
|
} else {
|
|
// support horizontal scroll (shift + mousewheel)
|
|
if kmods == key.ModShift {
|
|
sp.X = -dist
|
|
} else {
|
|
sp.Y = -dist
|
|
}
|
|
}
|
|
w.ProcessEvent(pointer.Event{
|
|
Kind: pointer.Scroll,
|
|
Source: pointer.Mouse,
|
|
Position: p,
|
|
Buttons: getPointerButtons(pi),
|
|
Scroll: sp,
|
|
Modifiers: kmods,
|
|
Time: windows.GetMessageTime(),
|
|
})
|
|
}
|
|
|
|
// Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
|
|
func (w *window) runLoop() {
|
|
msg := new(windows.Msg)
|
|
loop:
|
|
for {
|
|
anim := w.animating
|
|
p := windows.GetWindowPlacement(w.hwnd)
|
|
if anim && !p.IsMinimized() && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
|
|
w.draw(false)
|
|
continue
|
|
}
|
|
switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
|
|
case -1:
|
|
panic(errors.New("GetMessage failed"))
|
|
case 0:
|
|
// WM_QUIT received.
|
|
break loop
|
|
}
|
|
windows.TranslateMessage(msg)
|
|
windows.DispatchMessage(msg)
|
|
}
|
|
}
|
|
|
|
func (w *window) EditorStateChanged(old, new editorState) {
|
|
imc := windows.ImmGetContext(w.hwnd)
|
|
if imc == 0 {
|
|
return
|
|
}
|
|
defer windows.ImmReleaseContext(w.hwnd, imc)
|
|
if old.Selection.Range != new.Selection.Range || old.Snippet != new.Snippet {
|
|
windows.ImmNotifyIME(imc, windows.NI_COMPOSITIONSTR, windows.CPS_CANCEL, 0)
|
|
}
|
|
}
|
|
|
|
func (w *window) SetAnimating(anim bool) {
|
|
w.animating = anim
|
|
}
|
|
|
|
func (w *window) ProcessEvent(e event.Event) {
|
|
w.w.ProcessEvent(e)
|
|
w.loop.FlushEvents()
|
|
}
|
|
|
|
func (w *window) Event() event.Event {
|
|
return w.loop.Event()
|
|
}
|
|
|
|
func (w *window) Invalidate() {
|
|
w.loop.Invalidate()
|
|
}
|
|
|
|
func (w *window) Run(f func()) {
|
|
w.loop.Run(f)
|
|
}
|
|
|
|
func (w *window) Frame(frame *op.Ops) {
|
|
w.loop.Frame(frame)
|
|
}
|
|
|
|
func (w *window) wakeup() {
|
|
if err := windows.PostMessage(w.hwnd, _WM_WAKEUP, 0, 0); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (w *window) draw(sync bool) {
|
|
if w.config.Size.X == 0 || w.config.Size.Y == 0 {
|
|
return
|
|
}
|
|
dpi := windows.GetWindowDPI(w.hwnd)
|
|
cfg := configForDPI(dpi)
|
|
w.ProcessEvent(frameEvent{
|
|
FrameEvent: FrameEvent{
|
|
Now: time.Now(),
|
|
Size: w.config.Size,
|
|
Metric: cfg,
|
|
},
|
|
Sync: sync,
|
|
})
|
|
}
|
|
|
|
func (w *window) NewContext() (context, error) {
|
|
sort.Slice(drivers, func(i, j int) bool {
|
|
return drivers[i].priority < drivers[j].priority
|
|
})
|
|
var errs []string
|
|
for _, b := range drivers {
|
|
ctx, err := b.initializer(w)
|
|
if err == nil {
|
|
return ctx, nil
|
|
}
|
|
errs = append(errs, err.Error())
|
|
}
|
|
if len(errs) > 0 {
|
|
return nil, fmt.Errorf("NewContext: failed to create a GPU device, tried: %s", strings.Join(errs, ", "))
|
|
}
|
|
return nil, errors.New("NewContext: no available GPU drivers")
|
|
}
|
|
|
|
func (w *window) ReadClipboard() {
|
|
w.readClipboard()
|
|
}
|
|
|
|
func (w *window) readClipboard() (cerr error) {
|
|
defer func() {
|
|
if cerr != nil {
|
|
w.processDataEvent("")
|
|
}
|
|
}()
|
|
|
|
if err := windows.OpenClipboard(w.hwnd); err != nil {
|
|
return err
|
|
}
|
|
defer windows.CloseClipboard()
|
|
mem, err := windows.GetClipboardData(windows.CF_UNICODETEXT)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ptr, err := windows.GlobalLock(mem)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer windows.GlobalUnlock(mem)
|
|
content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
|
|
w.processDataEvent(content)
|
|
return nil
|
|
}
|
|
|
|
func (w *window) processDataEvent(content string) {
|
|
w.ProcessEvent(transfer.DataEvent{
|
|
Type: "application/text",
|
|
Open: func() io.ReadCloser {
|
|
return io.NopCloser(strings.NewReader(content))
|
|
},
|
|
})
|
|
}
|
|
|
|
func (w *window) Configure(options []Option) {
|
|
dpi := windows.GetSystemDPI()
|
|
metric := configForDPI(dpi)
|
|
cnf := w.config
|
|
cnf.apply(metric, options)
|
|
w.config.Title = cnf.Title
|
|
w.config.Decorated = cnf.Decorated
|
|
w.config.MinSize = cnf.MinSize
|
|
w.config.MaxSize = cnf.MaxSize
|
|
windows.SetWindowText(w.hwnd, cnf.Title)
|
|
|
|
style := windows.GetWindowLong(w.hwnd, windows.GWL_STYLE)
|
|
var showMode int32
|
|
var x, y, width, height int32
|
|
swpStyle := uintptr(windows.SWP_FRAMECHANGED)
|
|
if cnf.TopMost == w.config.TopMost {
|
|
// Don't change the z-order if TopMost didn't change.
|
|
swpStyle |= windows.SWP_NOZORDER
|
|
}
|
|
hwndAfter := windows.HWND_NOTOPMOST
|
|
if cnf.TopMost {
|
|
hwndAfter = windows.HWND_TOPMOST
|
|
}
|
|
w.config.TopMost = cnf.TopMost
|
|
winStyle := uintptr(windows.WS_OVERLAPPEDWINDOW)
|
|
style &^= winStyle
|
|
switch cnf.Mode {
|
|
case Minimized:
|
|
style |= winStyle
|
|
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
|
|
showMode = windows.SW_SHOWMINIMIZED
|
|
|
|
case Maximized:
|
|
style |= winStyle
|
|
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
|
|
showMode = windows.SW_SHOWMAXIMIZED
|
|
|
|
case Windowed:
|
|
style |= winStyle
|
|
showMode = windows.SW_SHOWNORMAL
|
|
// Get target for client area size.
|
|
width = int32(cnf.Size.X)
|
|
height = int32(cnf.Size.Y)
|
|
// Get the current window size and position.
|
|
wr := windows.GetWindowRect(w.hwnd)
|
|
x = wr.Left
|
|
y = wr.Top
|
|
if cnf.Decorated {
|
|
// Compute client size and position. Note that the client size is
|
|
// equal to the window size when we are in control of decorations.
|
|
r := windows.Rect{
|
|
Right: width,
|
|
Bottom: height,
|
|
}
|
|
windows.AdjustWindowRectEx(&r, uint32(style), 0, dwExStyle)
|
|
width = r.Right - r.Left
|
|
height = r.Bottom - r.Top
|
|
} else {
|
|
// Enable drop shadows when we draw decorations.
|
|
windows.DwmExtendFrameIntoClientArea(w.hwnd, windows.Margins{-1, -1, -1, -1})
|
|
}
|
|
|
|
case Fullscreen:
|
|
swpStyle |= windows.SWP_NOMOVE | windows.SWP_NOSIZE
|
|
showMode = windows.SW_SHOWMAXIMIZED
|
|
}
|
|
|
|
// Disable window resizing if MinSize and MaxSize are equal.
|
|
if cnf.MaxSize != (image.Point{}) && cnf.MinSize == cnf.MaxSize {
|
|
style &^= windows.WS_MAXIMIZEBOX
|
|
style &^= windows.WS_THICKFRAME
|
|
}
|
|
|
|
// Note: these invocation all trigger the windows callback method which may process a pending system.ActionCenter
|
|
// action, so SetWindowPos should come first so as to not "overwrite" system.ActionCenter.
|
|
windows.SetWindowPos(w.hwnd, hwndAfter, x, y, width, height, swpStyle)
|
|
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style)
|
|
windows.ShowWindow(w.hwnd, showMode)
|
|
}
|
|
|
|
func (w *window) WriteClipboard(mime string, s []byte) {
|
|
w.writeClipboard(string(s))
|
|
}
|
|
|
|
func (w *window) writeClipboard(s string) error {
|
|
if err := windows.OpenClipboard(w.hwnd); err != nil {
|
|
return err
|
|
}
|
|
defer windows.CloseClipboard()
|
|
if err := windows.EmptyClipboard(); err != nil {
|
|
return err
|
|
}
|
|
u16, err := gowindows.UTF16FromString(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
n := len(u16) * int(unsafe.Sizeof(u16[0]))
|
|
mem, err := windows.GlobalAlloc(n)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ptr, err := windows.GlobalLock(mem)
|
|
if err != nil {
|
|
windows.GlobalFree(mem)
|
|
return err
|
|
}
|
|
u16v := unsafe.Slice((*uint16)(ptr), len(u16))
|
|
copy(u16v, u16)
|
|
windows.GlobalUnlock(mem)
|
|
if err := windows.SetClipboardData(windows.CF_UNICODETEXT, mem); err != nil {
|
|
windows.GlobalFree(mem)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *window) SetCursor(cursor pointer.Cursor) {
|
|
c, err := loadCursor(cursor)
|
|
if err != nil {
|
|
c = resources.cursor
|
|
}
|
|
w.cursor = c
|
|
if w.cursorIn {
|
|
windows.SetCursor(w.cursor)
|
|
}
|
|
}
|
|
|
|
// windowsCursor contains mapping from pointer.Cursor to an IDC.
|
|
var windowsCursor = [...]uint16{
|
|
pointer.CursorDefault: windows.IDC_ARROW,
|
|
pointer.CursorNone: 0,
|
|
pointer.CursorText: windows.IDC_IBEAM,
|
|
pointer.CursorVerticalText: windows.IDC_IBEAM,
|
|
pointer.CursorPointer: windows.IDC_HAND,
|
|
pointer.CursorCrosshair: windows.IDC_CROSS,
|
|
pointer.CursorAllScroll: windows.IDC_SIZEALL,
|
|
pointer.CursorColResize: windows.IDC_SIZEWE,
|
|
pointer.CursorRowResize: windows.IDC_SIZENS,
|
|
pointer.CursorGrab: windows.IDC_SIZEALL,
|
|
pointer.CursorGrabbing: windows.IDC_SIZEALL,
|
|
pointer.CursorNotAllowed: windows.IDC_NO,
|
|
pointer.CursorWait: windows.IDC_WAIT,
|
|
pointer.CursorProgress: windows.IDC_APPSTARTING,
|
|
pointer.CursorNorthWestResize: windows.IDC_SIZENWSE,
|
|
pointer.CursorNorthEastResize: windows.IDC_SIZENESW,
|
|
pointer.CursorSouthWestResize: windows.IDC_SIZENESW,
|
|
pointer.CursorSouthEastResize: windows.IDC_SIZENWSE,
|
|
pointer.CursorNorthSouthResize: windows.IDC_SIZENS,
|
|
pointer.CursorEastWestResize: windows.IDC_SIZEWE,
|
|
pointer.CursorWestResize: windows.IDC_SIZEWE,
|
|
pointer.CursorEastResize: windows.IDC_SIZEWE,
|
|
pointer.CursorNorthResize: windows.IDC_SIZENS,
|
|
pointer.CursorSouthResize: windows.IDC_SIZENS,
|
|
pointer.CursorNorthEastSouthWestResize: windows.IDC_SIZENESW,
|
|
pointer.CursorNorthWestSouthEastResize: windows.IDC_SIZENWSE,
|
|
}
|
|
|
|
func loadCursor(cursor pointer.Cursor) (syscall.Handle, error) {
|
|
switch cursor {
|
|
case pointer.CursorDefault:
|
|
return resources.cursor, nil
|
|
case pointer.CursorNone:
|
|
return 0, nil
|
|
default:
|
|
return windows.LoadCursor(windowsCursor[cursor])
|
|
}
|
|
}
|
|
|
|
func (w *window) ShowTextInput(show bool) {}
|
|
|
|
func (w *window) SetInputHint(_ key.InputHint) {}
|
|
|
|
func (w *window) HDC() syscall.Handle {
|
|
return w.hdc
|
|
}
|
|
|
|
func (w *window) HWND() (syscall.Handle, int, int) {
|
|
return w.hwnd, w.config.Size.X, w.config.Size.Y
|
|
}
|
|
|
|
func (w *window) Perform(acts system.Action) {
|
|
walkActions(acts, func(a system.Action) {
|
|
switch a {
|
|
case system.ActionCenter:
|
|
if w.config.Mode != Windowed {
|
|
break
|
|
}
|
|
r := windows.GetWindowRect(w.hwnd)
|
|
dx := r.Right - r.Left
|
|
dy := r.Bottom - r.Top
|
|
// Calculate center position on current monitor.
|
|
mi := windows.GetMonitorInfo(w.hwnd).Monitor
|
|
x := (mi.Right - mi.Left - dx) / 2
|
|
y := (mi.Bottom - mi.Top - dy) / 2
|
|
windows.SetWindowPos(w.hwnd, 0, x, y, dx, dy, windows.SWP_NOZORDER|windows.SWP_FRAMECHANGED)
|
|
case system.ActionRaise:
|
|
windows.SetForegroundWindow(w.hwnd)
|
|
windows.SetWindowPos(w.hwnd, windows.HWND_TOP, 0, 0, 0, 0,
|
|
windows.SWP_NOMOVE|windows.SWP_NOSIZE|windows.SWP_SHOWWINDOW)
|
|
case system.ActionClose:
|
|
windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0)
|
|
}
|
|
})
|
|
}
|
|
|
|
func convertKeyCode(code uintptr) (key.Name, bool) {
|
|
if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' {
|
|
return key.Name(rune(code)), true
|
|
}
|
|
var r key.Name
|
|
|
|
switch code {
|
|
case windows.VK_ESCAPE:
|
|
r = key.NameEscape
|
|
case windows.VK_LEFT:
|
|
r = key.NameLeftArrow
|
|
case windows.VK_RIGHT:
|
|
r = key.NameRightArrow
|
|
case windows.VK_RETURN:
|
|
r = key.NameReturn
|
|
case windows.VK_UP:
|
|
r = key.NameUpArrow
|
|
case windows.VK_DOWN:
|
|
r = key.NameDownArrow
|
|
case windows.VK_HOME:
|
|
r = key.NameHome
|
|
case windows.VK_END:
|
|
r = key.NameEnd
|
|
case windows.VK_BACK:
|
|
r = key.NameDeleteBackward
|
|
case windows.VK_DELETE:
|
|
r = key.NameDeleteForward
|
|
case windows.VK_PRIOR:
|
|
r = key.NamePageUp
|
|
case windows.VK_NEXT:
|
|
r = key.NamePageDown
|
|
case windows.VK_F1:
|
|
r = key.NameF1
|
|
case windows.VK_F2:
|
|
r = key.NameF2
|
|
case windows.VK_F3:
|
|
r = key.NameF3
|
|
case windows.VK_F4:
|
|
r = key.NameF4
|
|
case windows.VK_F5:
|
|
r = key.NameF5
|
|
case windows.VK_F6:
|
|
r = key.NameF6
|
|
case windows.VK_F7:
|
|
r = key.NameF7
|
|
case windows.VK_F8:
|
|
r = key.NameF8
|
|
case windows.VK_F9:
|
|
r = key.NameF9
|
|
case windows.VK_F10:
|
|
r = key.NameF10
|
|
case windows.VK_F11:
|
|
r = key.NameF11
|
|
case windows.VK_F12:
|
|
r = key.NameF12
|
|
case windows.VK_TAB:
|
|
r = key.NameTab
|
|
case windows.VK_SPACE:
|
|
r = key.NameSpace
|
|
case windows.VK_OEM_1:
|
|
r = ";"
|
|
case windows.VK_OEM_PLUS:
|
|
r = "+"
|
|
case windows.VK_OEM_COMMA:
|
|
r = ","
|
|
case windows.VK_OEM_MINUS:
|
|
r = "-"
|
|
case windows.VK_OEM_PERIOD:
|
|
r = "."
|
|
case windows.VK_OEM_2:
|
|
r = "/"
|
|
case windows.VK_OEM_3:
|
|
r = "`"
|
|
case windows.VK_OEM_4:
|
|
r = "["
|
|
case windows.VK_OEM_5, windows.VK_OEM_102:
|
|
r = "\\"
|
|
case windows.VK_OEM_6:
|
|
r = "]"
|
|
case windows.VK_OEM_7:
|
|
r = "'"
|
|
case windows.VK_CONTROL:
|
|
r = key.NameCtrl
|
|
case windows.VK_SHIFT:
|
|
r = key.NameShift
|
|
case windows.VK_MENU:
|
|
r = key.NameAlt
|
|
case windows.VK_LWIN, windows.VK_RWIN:
|
|
r = key.NameSuper
|
|
default:
|
|
return "", false
|
|
}
|
|
return r, true
|
|
}
|
|
|
|
func configForDPI(dpi int) unit.Metric {
|
|
const inchPrDp = 1.0 / 96.0
|
|
ppdp := float32(dpi) * inchPrDp
|
|
return unit.Metric{
|
|
PxPerDp: ppdp,
|
|
PxPerSp: ppdp,
|
|
}
|
|
}
|
|
|
|
func (Win32ViewEvent) implementsViewEvent() {}
|
|
func (Win32ViewEvent) ImplementsEvent() {}
|
|
func (w Win32ViewEvent) Valid() bool {
|
|
return w != (Win32ViewEvent{})
|
|
}
|
|
|
|
// LOWORD (minwindef.h)
|
|
func loWord(val uint32) uint16 {
|
|
return uint16(val & 0xFFFF)
|
|
}
|
|
|
|
// GET_POINTERID_WPARAM (winuser.h)
|
|
func getPointerIDwParam(wParam uintptr) pointer.ID {
|
|
return pointer.ID(loWord(uint32(wParam)))
|
|
}
|
|
|
|
func getPointerButtons(pi windows.PointerInfo) pointer.Buttons {
|
|
var btns pointer.Buttons
|
|
|
|
if pi.PointerFlags&windows.POINTER_FLAG_FIRSTBUTTON != 0 {
|
|
btns |= pointer.ButtonPrimary
|
|
} else {
|
|
btns &^= pointer.ButtonPrimary
|
|
}
|
|
if pi.PointerFlags&windows.POINTER_FLAG_SECONDBUTTON != 0 {
|
|
btns |= pointer.ButtonSecondary
|
|
} else {
|
|
btns &^= pointer.ButtonSecondary
|
|
}
|
|
if pi.PointerFlags&windows.POINTER_FLAG_THIRDBUTTON != 0 {
|
|
btns |= pointer.ButtonTertiary
|
|
} else {
|
|
btns &^= pointer.ButtonTertiary
|
|
}
|
|
if pi.PointerFlags&windows.POINTER_FLAG_FOURTHBUTTON != 0 {
|
|
btns |= pointer.ButtonQuaternary
|
|
} else {
|
|
btns &^= pointer.ButtonQuaternary
|
|
}
|
|
if pi.PointerFlags&windows.POINTER_FLAG_FIFTHBUTTON != 0 {
|
|
btns |= pointer.ButtonQuinary
|
|
} else {
|
|
btns &^= pointer.ButtonQuinary
|
|
}
|
|
|
|
return btns
|
|
}
|
|
|
|
// schemesURI is a list of schemes, comma separated, that must be
|
|
// defined using -X compiler ldflag, that used in gogio.
|
|
var schemesURI string
|
|
|
|
func init() {
|
|
if schemesURI == "" {
|
|
return
|
|
}
|
|
|
|
currentSchemes := strings.Split(schemesURI, ",")
|
|
oldSchemes := registeredSchemes(ID)
|
|
|
|
for _, s := range currentSchemes {
|
|
for i, o := range oldSchemes {
|
|
if s == o {
|
|
oldSchemes = append(oldSchemes[:i], oldSchemes[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(oldSchemes) > 0 {
|
|
go unregisterSchemes(ID, oldSchemes)
|
|
}
|
|
|
|
if len(currentSchemes) == 0 {
|
|
return
|
|
}
|
|
|
|
// On Windows, launching the app using a URI will start a new instance of the app,
|
|
// a new window. That behavior, by default, doesn't align with iOS/Android/macOS, where
|
|
// the deeplink sends the event to the running app (if any). We are emulating it.
|
|
if hwnd, _ := windows.FindWindow(ID); hwnd != 0 {
|
|
if u := startupURI(); u != "" {
|
|
broadcastURI(hwnd, u)
|
|
}
|
|
os.Exit(0)
|
|
return
|
|
}
|
|
|
|
go registerSchemes(ID, currentSchemes)
|
|
}
|
|
|
|
func startupURI() string {
|
|
if len(os.Args) == 3 && os.Args[1] == "-gio_launch_url" {
|
|
return os.Args[2]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func processURLEvent(rawurl string) bool {
|
|
if rawurl == "" {
|
|
return false
|
|
}
|
|
|
|
evt, err := newURLEvent(rawurl)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
for _, scheme := range strings.Split(schemesURI, ",") {
|
|
if strings.EqualFold(scheme, evt.URL.Scheme) {
|
|
processGlobalEvent(evt)
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func broadcastURI(hwnd syscall.Handle, uri string) {
|
|
data, err := syscall.UTF16FromString(uri)
|
|
if err != nil {
|
|
return // Only happens if uri contains NULL character.
|
|
}
|
|
|
|
pinner := new(runtime.Pinner)
|
|
defer pinner.Unpin()
|
|
pinner.Pin(unsafe.Pointer(&data[0]))
|
|
|
|
msg := &windows.CopyDataStruct{
|
|
DwData: copyDataURLType,
|
|
CbData: uint32(len(data) * int(unsafe.Sizeof(data[0]))),
|
|
LpData: uintptr(unsafe.Pointer(unsafe.SliceData(data))),
|
|
}
|
|
pinner.Pin(unsafe.Pointer(msg))
|
|
|
|
// SendMessage blocks until the message is processed.
|
|
windows.SendMessage(hwnd, windows.WM_COPYDATA, 0, uintptr(unsafe.Pointer(msg)))
|
|
}
|
|
|
|
func registeredSchemes(appid string) []string {
|
|
meta, err := registry.OpenKey(registry.CURRENT_USER, `Software\\`+appid, registry.ALL_ACCESS)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
defer meta.Close()
|
|
|
|
schemes, _, _ := meta.GetStringsValue("URISchemes")
|
|
return schemes
|
|
}
|
|
|
|
func registerSchemes(appid string, schemes []string) error {
|
|
reg := func(scheme string) error {
|
|
key, existent, err := registry.CreateKey(registry.CURRENT_USER, `Software\\Classes\\`+scheme, registry.ALL_ACCESS)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer key.Close()
|
|
|
|
if existent {
|
|
// Check if the existent key belongs to the current application
|
|
id, _, err := key.GetStringValue("appid")
|
|
if err != nil || id != appid {
|
|
return fmt.Errorf("scheme %s already registered by another application", scheme)
|
|
}
|
|
}
|
|
|
|
path, err := os.Executable()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = key.SetStringValue("", "URL:"+scheme+" Protocol"); err != nil {
|
|
return err
|
|
}
|
|
if err = key.SetStringValue("URL Protocol", ""); err != nil {
|
|
return err
|
|
}
|
|
if err = key.SetStringValue("appid", appid); err != nil {
|
|
return err
|
|
}
|
|
|
|
icon, _, err := registry.CreateKey(key, `DefaultIcon`, registry.ALL_ACCESS)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer icon.Close()
|
|
|
|
if err = icon.SetStringValue("", `"`+path+`",1`); err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd, _, err := registry.CreateKey(key, `shell\\open\\command`, registry.ALL_ACCESS)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer cmd.Close()
|
|
|
|
if err = cmd.SetStringValue("", `"`+path+`" -gio_launch_url "%1"`); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
for _, scheme := range schemes {
|
|
if scheme == "" {
|
|
continue // just in case
|
|
}
|
|
if err := reg(scheme); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
meta, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\\`+appid, registry.ALL_ACCESS)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer meta.Close()
|
|
|
|
if err = meta.SetStringsValue("URISchemes", schemes); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func unregisterSchemes(appid string, schemes []string) {
|
|
classes, err := registry.OpenKey(registry.CURRENT_USER, `Software\\Classes`, registry.ALL_ACCESS)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer classes.Close()
|
|
|
|
for _, scheme := range schemes {
|
|
if scheme == "" {
|
|
continue // just in case
|
|
}
|
|
|
|
key, err := registry.OpenKey(classes, scheme, registry.ALL_ACCESS)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
id, _, err := key.GetStringValue("appid")
|
|
if err == nil && id != appid {
|
|
continue
|
|
}
|
|
|
|
for _, k := range []string{`DefaultIcon`, `shell\\open\\command`, `shell\\open`, `shell`} {
|
|
registry.DeleteKey(key, k)
|
|
}
|
|
|
|
if err := key.Close(); err != nil {
|
|
continue
|
|
}
|
|
|
|
registry.DeleteKey(classes, scheme)
|
|
}
|
|
}
|