- DCAuth 协议升级到 v2,签名原文只含设备码;客户端 2.0.0,旧版本返回 426 - 删除客户端机器码采集、后端 machine_code_* 字段与日志、后台机器码展示 - 102 迁移在生产执行前直接改为无机器码结构;SQL 守卫仅放行该文件 - Mock 短信不启用每日限额;修复慢电脑首次启动的协议转发竞态 - 新增 macOS DCAuth.app 打包脚本,注册 dcauth:// 协议,浏览器可直接唤起
647 lines
16 KiB
Go
647 lines
16 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime"
|
|
"syscall"
|
|
"unicode/utf16"
|
|
"unsafe"
|
|
)
|
|
|
|
const (
|
|
mbOK = 0x00000000
|
|
mbOKCancel = 0x00000001
|
|
mbIconError = 0x00000010
|
|
mbIconQuestion = 0x00000020
|
|
mbIconInformation = 0x00000040
|
|
mbSetForeground = 0x00010000
|
|
mbTopMost = 0x00040000
|
|
idOK = 1
|
|
)
|
|
|
|
const (
|
|
cfUnicodeText = 13
|
|
gmemMoveable = 0x0002
|
|
)
|
|
|
|
const (
|
|
wmNull = 0x0000
|
|
wmDestroy = 0x0002
|
|
wmClose = 0x0010
|
|
wmCopyData = 0x004A
|
|
wmCommand = 0x0111
|
|
wmLButtonUp = 0x0202
|
|
wmLButtonDblClk = 0x0203
|
|
wmRButtonUp = 0x0205
|
|
wmContextMenu = 0x007B
|
|
wmApp = 0x8000
|
|
)
|
|
|
|
const (
|
|
trayCallbackMessage = wmApp + 1
|
|
showWindowMessage = wmApp + 2
|
|
copyDataMagic = 0x44434131
|
|
)
|
|
|
|
const (
|
|
trayMenuDeviceInfo = 1001
|
|
trayMenuExit = 1002
|
|
)
|
|
|
|
const (
|
|
nimAdd = 0x00000000
|
|
nimModify = 0x00000001
|
|
nimDelete = 0x00000002
|
|
)
|
|
|
|
const (
|
|
nifMessage = 0x00000001
|
|
nifIcon = 0x00000002
|
|
nifTip = 0x00000004
|
|
nifInfo = 0x00000010
|
|
)
|
|
|
|
const (
|
|
niifInfo = 0x00000001
|
|
)
|
|
|
|
const (
|
|
mfString = 0x00000000
|
|
mfGrayed = 0x00000001
|
|
mfSeparator = 0x00000800
|
|
tpmRight = 0x00000002
|
|
tpmReturn = 0x00000100
|
|
)
|
|
|
|
const (
|
|
errorAlreadyExists = syscall.Errno(183)
|
|
errorClassAlreadyReg = syscall.Errno(1410)
|
|
)
|
|
|
|
const (
|
|
windowClassName = "DCAuthHiddenWindow"
|
|
windowTitle = "DCAuth"
|
|
mutexName = `Local\DCAuth.OrderSite`
|
|
authorizationMutexName = `Local\DCAuth.OrderSite.Authorization`
|
|
)
|
|
|
|
type point struct {
|
|
X int32
|
|
Y int32
|
|
}
|
|
|
|
type message struct {
|
|
HWnd uintptr
|
|
Message uint32
|
|
WParam uintptr
|
|
LParam uintptr
|
|
Time uint32
|
|
Pt point
|
|
Private uint32
|
|
}
|
|
|
|
type wndClassEx struct {
|
|
CbSize uint32
|
|
Style uint32
|
|
LpfnWndProc uintptr
|
|
CbClsExtra int32
|
|
CbWndExtra int32
|
|
HInstance uintptr
|
|
HIcon uintptr
|
|
HCursor uintptr
|
|
HbrBackground uintptr
|
|
LpszMenuName *uint16
|
|
LpszClassName *uint16
|
|
HIconSm uintptr
|
|
}
|
|
|
|
type copyDataStruct struct {
|
|
DwData uintptr
|
|
CbData uint32
|
|
LpData uintptr
|
|
}
|
|
|
|
type guid struct {
|
|
Data1 uint32
|
|
Data2 uint16
|
|
Data3 uint16
|
|
Data4 [8]byte
|
|
}
|
|
|
|
type notifyIconData struct {
|
|
CbSize uint32
|
|
HWnd uintptr
|
|
UID uint32
|
|
UFlags uint32
|
|
UCallbackMessage uint32
|
|
HIcon uintptr
|
|
SzTip [128]uint16
|
|
DwState uint32
|
|
DwStateMask uint32
|
|
SzInfo [256]uint16
|
|
UVersion uint32
|
|
SzInfoTitle [64]uint16
|
|
DwInfoFlags uint32
|
|
GuidItem guid
|
|
HBalloonIcon uintptr
|
|
}
|
|
|
|
var (
|
|
user32DLL = syscall.NewLazyDLL("user32.dll")
|
|
shell32DLL = syscall.NewLazyDLL("shell32.dll")
|
|
|
|
messageBoxW = user32DLL.NewProc("MessageBoxW")
|
|
openClip = user32DLL.NewProc("OpenClipboard")
|
|
closeClip = user32DLL.NewProc("CloseClipboard")
|
|
emptyClip = user32DLL.NewProc("EmptyClipboard")
|
|
setClipData = user32DLL.NewProc("SetClipboardData")
|
|
registerClassExW = user32DLL.NewProc("RegisterClassExW")
|
|
createWindowExW = user32DLL.NewProc("CreateWindowExW")
|
|
defWindowProcW = user32DLL.NewProc("DefWindowProcW")
|
|
getMessageW = user32DLL.NewProc("GetMessageW")
|
|
translateMessage = user32DLL.NewProc("TranslateMessage")
|
|
dispatchMessageW = user32DLL.NewProc("DispatchMessageW")
|
|
postQuitMessage = user32DLL.NewProc("PostQuitMessage")
|
|
destroyWindow = user32DLL.NewProc("DestroyWindow")
|
|
findWindowW = user32DLL.NewProc("FindWindowW")
|
|
sendMessageW = user32DLL.NewProc("SendMessageW")
|
|
postMessageW = user32DLL.NewProc("PostMessageW")
|
|
createPopupMenu = user32DLL.NewProc("CreatePopupMenu")
|
|
appendMenuW = user32DLL.NewProc("AppendMenuW")
|
|
trackPopupMenu = user32DLL.NewProc("TrackPopupMenu")
|
|
destroyMenu = user32DLL.NewProc("DestroyMenu")
|
|
setForegroundWindow = user32DLL.NewProc("SetForegroundWindow")
|
|
getCursorPos = user32DLL.NewProc("GetCursorPos")
|
|
loadIconW = user32DLL.NewProc("LoadIconW")
|
|
shellNotifyIconW = shell32DLL.NewProc("Shell_NotifyIconW")
|
|
|
|
globalAlloc = kernel32DLL.NewProc("GlobalAlloc")
|
|
globalLock = kernel32DLL.NewProc("GlobalLock")
|
|
globalUnlock = kernel32DLL.NewProc("GlobalUnlock")
|
|
createMutexW = kernel32DLL.NewProc("CreateMutexW")
|
|
getModuleW = kernel32DLL.NewProc("GetModuleHandleW")
|
|
sleep = kernel32DLL.NewProc("Sleep")
|
|
)
|
|
|
|
var (
|
|
mainWindow uintptr
|
|
instanceMutex uintptr
|
|
trayData notifyIconData
|
|
trayAdded bool
|
|
runtimeIdentity identity
|
|
identityReady bool
|
|
authorizationActive bool
|
|
windowProcedure = syscall.NewCallback(dcauthWindowProc)
|
|
)
|
|
|
|
func runClient(protocolArg string) {
|
|
runtime.LockOSThread()
|
|
firstInstance, err := acquireSingleInstance()
|
|
if err != nil {
|
|
showError("DCAuth 启动失败", err.Error())
|
|
return
|
|
}
|
|
if !firstInstance {
|
|
if authorizationInProgress() {
|
|
return
|
|
}
|
|
if forwardToRunningInstance(protocolArg) {
|
|
return
|
|
}
|
|
firstInstance, err = acquireSingleInstance()
|
|
if err != nil || !firstInstance {
|
|
showError("DCAuth 启动失败", "DCAuth 已在运行但暂时没有响应。请在任务管理器中结束 DCAuth.exe 后重试。")
|
|
return
|
|
}
|
|
}
|
|
|
|
registerProtocol()
|
|
if err := createHiddenWindow(); err != nil {
|
|
showError("DCAuth 启动失败", err.Error())
|
|
return
|
|
}
|
|
if err := addTrayIcon(); err != nil {
|
|
showError("DCAuth 启动失败", err.Error())
|
|
return
|
|
}
|
|
|
|
runtimeIdentity, err = loadOrCreateIdentity()
|
|
if err != nil {
|
|
removeTrayIcon()
|
|
showError("DCAuth 初始化失败", err.Error())
|
|
destroyWindow.Call(mainWindow)
|
|
mainWindow = 0
|
|
return
|
|
}
|
|
identityReady = true
|
|
|
|
if protocolArg != "" {
|
|
handleAuthorization(protocolArg)
|
|
} else {
|
|
showTrayBalloon("DCAuth v"+clientVersion+" 已在后台运行", "双击托盘图标可查看版本和设备码。")
|
|
}
|
|
|
|
runMessageLoop()
|
|
removeTrayIcon()
|
|
}
|
|
|
|
func acquireSingleInstance() (bool, error) {
|
|
namePtr, _ := syscall.UTF16PtrFromString(mutexName)
|
|
handle, _, callErr := createMutexW.Call(0, 0, uintptr(unsafe.Pointer(namePtr)))
|
|
runtime.KeepAlive(namePtr)
|
|
if handle == 0 {
|
|
return false, fmt.Errorf("无法创建单实例锁:%v", callErr)
|
|
}
|
|
if errno, ok := callErr.(syscall.Errno); ok && errno == errorAlreadyExists {
|
|
_ = syscall.CloseHandle(syscall.Handle(handle))
|
|
return false, nil
|
|
}
|
|
instanceMutex = handle
|
|
return true, nil
|
|
}
|
|
|
|
func authorizationInProgress() bool {
|
|
handle, acquired, err := acquireNamedMutex(authorizationMutexName)
|
|
if err != nil || !acquired {
|
|
if handle != 0 {
|
|
_ = syscall.CloseHandle(syscall.Handle(handle))
|
|
}
|
|
return err == nil && !acquired
|
|
}
|
|
_ = syscall.CloseHandle(syscall.Handle(handle))
|
|
return false
|
|
}
|
|
|
|
func acquireNamedMutex(name string) (uintptr, bool, error) {
|
|
namePtr, _ := syscall.UTF16PtrFromString(name)
|
|
handle, _, callErr := createMutexW.Call(0, 0, uintptr(unsafe.Pointer(namePtr)))
|
|
runtime.KeepAlive(namePtr)
|
|
if handle == 0 {
|
|
return 0, false, fmt.Errorf("无法创建互斥锁:%v", callErr)
|
|
}
|
|
if errno, ok := callErr.(syscall.Errno); ok && errno == errorAlreadyExists {
|
|
return handle, false, nil
|
|
}
|
|
return handle, true, nil
|
|
}
|
|
|
|
func forwardToRunningInstance(protocolArg string) bool {
|
|
for attempt := 0; attempt < 300; attempt++ {
|
|
if hwnd := findRunningInstanceWindow(); hwnd != 0 {
|
|
return sendToRunningInstance(hwnd, protocolArg)
|
|
}
|
|
sleep.Call(100)
|
|
}
|
|
return false
|
|
}
|
|
|
|
func findRunningInstanceWindow() uintptr {
|
|
classPtr, _ := syscall.UTF16PtrFromString(windowClassName)
|
|
titlePtr, _ := syscall.UTF16PtrFromString(windowTitle)
|
|
hwnd, _, _ := findWindowW.Call(
|
|
uintptr(unsafe.Pointer(classPtr)),
|
|
uintptr(unsafe.Pointer(titlePtr)),
|
|
)
|
|
runtime.KeepAlive(classPtr)
|
|
runtime.KeepAlive(titlePtr)
|
|
return hwnd
|
|
}
|
|
|
|
func sendToRunningInstance(hwnd uintptr, protocolArg string) bool {
|
|
if protocolArg == "" {
|
|
result, _, _ := postMessageW.Call(hwnd, showWindowMessage, 0, 0)
|
|
return result != 0
|
|
}
|
|
|
|
encoded, _ := syscall.UTF16FromString(protocolArg)
|
|
data := copyDataStruct{
|
|
DwData: copyDataMagic,
|
|
CbData: uint32(len(encoded) * 2),
|
|
LpData: uintptr(unsafe.Pointer(&encoded[0])),
|
|
}
|
|
result, _, _ := sendMessageW.Call(
|
|
hwnd,
|
|
wmCopyData,
|
|
0,
|
|
uintptr(unsafe.Pointer(&data)),
|
|
)
|
|
runtime.KeepAlive(encoded)
|
|
return result != 0
|
|
}
|
|
|
|
func createHiddenWindow() error {
|
|
instance, _, _ := getModuleW.Call(0)
|
|
if instance == 0 {
|
|
return fmt.Errorf("无法读取程序实例句柄")
|
|
}
|
|
icon, _, _ := loadIconW.Call(0, 32512)
|
|
|
|
classNamePtr, _ := syscall.UTF16PtrFromString(windowClassName)
|
|
class := wndClassEx{
|
|
CbSize: uint32(unsafe.Sizeof(wndClassEx{})),
|
|
LpfnWndProc: windowProcedure,
|
|
HInstance: instance,
|
|
HIcon: icon,
|
|
LpszClassName: classNamePtr,
|
|
HIconSm: icon,
|
|
}
|
|
result, _, callErr := registerClassExW.Call(uintptr(unsafe.Pointer(&class)))
|
|
runtime.KeepAlive(classNamePtr)
|
|
if result == 0 {
|
|
if errno, ok := callErr.(syscall.Errno); !ok || errno != errorClassAlreadyReg {
|
|
return fmt.Errorf("无法注册窗口类:%v", callErr)
|
|
}
|
|
}
|
|
|
|
titlePtr, _ := syscall.UTF16PtrFromString(windowTitle)
|
|
const wsExToolWindow = 0x00000080
|
|
hwnd, _, callErr := createWindowExW.Call(
|
|
wsExToolWindow,
|
|
uintptr(unsafe.Pointer(classNamePtr)),
|
|
uintptr(unsafe.Pointer(titlePtr)),
|
|
0,
|
|
0, 0, 0, 0,
|
|
0,
|
|
0,
|
|
instance,
|
|
0,
|
|
)
|
|
if hwnd == 0 {
|
|
return fmt.Errorf("无法创建 DCAuth 后台窗口:%v", callErr)
|
|
}
|
|
runtime.KeepAlive(classNamePtr)
|
|
runtime.KeepAlive(titlePtr)
|
|
mainWindow = hwnd
|
|
return nil
|
|
}
|
|
|
|
func runMessageLoop() {
|
|
var msg message
|
|
for {
|
|
result, _, _ := getMessageW.Call(
|
|
uintptr(unsafe.Pointer(&msg)),
|
|
0,
|
|
0,
|
|
0,
|
|
)
|
|
if int32(result) <= 0 {
|
|
return
|
|
}
|
|
translateMessage.Call(uintptr(unsafe.Pointer(&msg)))
|
|
dispatchMessageW.Call(uintptr(unsafe.Pointer(&msg)))
|
|
}
|
|
}
|
|
|
|
func dcauthWindowProc(hwnd, msg, wParam, lParam uintptr) uintptr {
|
|
switch uint32(msg) {
|
|
case wmCopyData:
|
|
if !identityReady {
|
|
return 0
|
|
}
|
|
data := (*copyDataStruct)(unsafe.Pointer(lParam))
|
|
if data != nil && data.DwData == copyDataMagic && data.CbData > 0 && data.LpData != 0 {
|
|
count := int(data.CbData / 2)
|
|
raw := syscall.UTF16ToString(unsafe.Slice((*uint16)(unsafe.Pointer(data.LpData)), count))
|
|
handleAuthorization(raw)
|
|
return 1
|
|
}
|
|
return 0
|
|
case showWindowMessage:
|
|
if identityReady {
|
|
showDeviceInfo()
|
|
}
|
|
return 0
|
|
case trayCallbackMessage:
|
|
switch uint32(lParam) {
|
|
case wmLButtonDblClk:
|
|
if identityReady {
|
|
showDeviceInfo()
|
|
} else {
|
|
showTrayBalloon("DCAuth 正在初始化", "设备信息准备完成后即可查看。")
|
|
}
|
|
case wmRButtonUp, wmContextMenu:
|
|
showTrayMenu(hwnd)
|
|
}
|
|
return 0
|
|
case wmClose:
|
|
destroyWindow.Call(hwnd)
|
|
return 0
|
|
case wmDestroy:
|
|
removeTrayIcon()
|
|
postQuitMessage.Call(0)
|
|
return 0
|
|
}
|
|
result, _, _ := defWindowProcW.Call(hwnd, msg, wParam, lParam)
|
|
return result
|
|
}
|
|
|
|
func addTrayIcon() error {
|
|
icon, _, _ := loadIconW.Call(0, 32512)
|
|
trayData = notifyIconData{
|
|
HWnd: mainWindow,
|
|
UID: 1,
|
|
UFlags: nifMessage | nifIcon | nifTip,
|
|
UCallbackMessage: trayCallbackMessage,
|
|
HIcon: icon,
|
|
}
|
|
trayData.CbSize = uint32(unsafe.Sizeof(trayData))
|
|
copyUTF16(trayData.SzTip[:], "DCAuth v"+clientVersion+" - 本机设备认证(后台运行)")
|
|
result, _, _ := shellNotifyIconW.Call(
|
|
nimAdd,
|
|
uintptr(unsafe.Pointer(&trayData)),
|
|
)
|
|
if result == 0 {
|
|
return fmt.Errorf("无法创建系统托盘图标")
|
|
}
|
|
trayAdded = true
|
|
return nil
|
|
}
|
|
|
|
func removeTrayIcon() {
|
|
if !trayAdded {
|
|
return
|
|
}
|
|
shellNotifyIconW.Call(nimDelete, uintptr(unsafe.Pointer(&trayData)))
|
|
trayAdded = false
|
|
}
|
|
|
|
func showTrayBalloon(title, text string) {
|
|
if !trayAdded {
|
|
return
|
|
}
|
|
info := trayData
|
|
info.UFlags |= nifInfo
|
|
info.UVersion = 10000
|
|
info.DwInfoFlags = niifInfo
|
|
copyUTF16(info.SzInfoTitle[:], title)
|
|
copyUTF16(info.SzInfo[:], text)
|
|
shellNotifyIconW.Call(nimModify, uintptr(unsafe.Pointer(&info)))
|
|
}
|
|
|
|
func showTrayMenu(hwnd uintptr) {
|
|
menu, _, _ := createPopupMenu.Call()
|
|
if menu == 0 {
|
|
return
|
|
}
|
|
defer destroyMenu.Call(menu)
|
|
|
|
statusText := mustUTF16("DCAuth v" + clientVersion + " · 后台运行中")
|
|
deviceText := mustUTF16("查看设备码")
|
|
exitText := mustUTF16("退出 DCAuth")
|
|
appendMenuW.Call(menu, mfString|mfGrayed, 0, uintptr(unsafe.Pointer(statusText)))
|
|
appendMenuW.Call(menu, mfString, trayMenuDeviceInfo, uintptr(unsafe.Pointer(deviceText)))
|
|
appendMenuW.Call(menu, mfSeparator, 0, 0)
|
|
appendMenuW.Call(menu, mfString, trayMenuExit, uintptr(unsafe.Pointer(exitText)))
|
|
runtime.KeepAlive(statusText)
|
|
runtime.KeepAlive(deviceText)
|
|
runtime.KeepAlive(exitText)
|
|
|
|
var cursor point
|
|
if ok, _, _ := getCursorPos.Call(uintptr(unsafe.Pointer(&cursor))); ok == 0 {
|
|
return
|
|
}
|
|
setForegroundWindow.Call(hwnd)
|
|
command, _, _ := trackPopupMenu.Call(
|
|
menu,
|
|
tpmRight|tpmReturn,
|
|
uintptr(cursor.X),
|
|
uintptr(cursor.Y),
|
|
0,
|
|
hwnd,
|
|
0,
|
|
)
|
|
postMessageW.Call(hwnd, wmNull, 0, 0)
|
|
|
|
switch command {
|
|
case trayMenuDeviceInfo:
|
|
if identityReady {
|
|
showDeviceInfo()
|
|
} else {
|
|
showTrayBalloon("DCAuth 正在初始化", "设备信息准备完成后即可查看。")
|
|
}
|
|
case trayMenuExit:
|
|
destroyWindow.Call(hwnd)
|
|
}
|
|
}
|
|
|
|
func showDeviceInfo() {
|
|
text := fmt.Sprintf(
|
|
"版本:v%s\n\n设备码:%s\n\nDCAuth 正在后台运行。浏览器登录时只会打开一个确认窗口。\n设备码已复制到剪贴板。",
|
|
clientVersion,
|
|
runtimeIdentity.deviceID,
|
|
)
|
|
copyToClipboard(runtimeIdentity.deviceID)
|
|
showInfo("DCAuth v"+clientVersion+" 设备信息", text)
|
|
}
|
|
|
|
func handleAuthorization(raw string) {
|
|
if authorizationActive {
|
|
showTrayBalloon("DCAuth v"+clientVersion, "已有一次登录确认正在进行,请先完成当前窗口。")
|
|
return
|
|
}
|
|
authorizationActive = true
|
|
defer func() { authorizationActive = false }()
|
|
|
|
authHandle, acquired, err := acquireNamedMutex(authorizationMutexName)
|
|
if err != nil {
|
|
showError("DCAuth 验证失败", err.Error())
|
|
return
|
|
}
|
|
if !acquired {
|
|
_ = syscall.CloseHandle(syscall.Handle(authHandle))
|
|
showTrayBalloon("DCAuth v"+clientVersion, "已有一次登录确认正在进行,请先完成当前窗口。")
|
|
return
|
|
}
|
|
defer syscall.CloseHandle(syscall.Handle(authHandle))
|
|
|
|
target, err := parseAuthorizeURL(raw)
|
|
if err != nil {
|
|
showError("DCAuth 协议无效", err.Error())
|
|
return
|
|
}
|
|
app := appRegistry[target.App]
|
|
if !confirm(
|
|
"DCAuth v"+clientVersion+" · "+app.DisplayName+" 本机登录确认",
|
|
fmt.Sprintf(
|
|
"是否允许使用本机设备登录「%s」?\n\n客户端版本:v%s\n设备码:%s\n\n点击“确定”允许本次登录,点击“取消”拒绝。",
|
|
app.DisplayName,
|
|
clientVersion,
|
|
runtimeIdentity.deviceID,
|
|
),
|
|
) {
|
|
return
|
|
}
|
|
if err := submitAuthorization(runtimeIdentity, target); err != nil {
|
|
showError("本机验证失败", err.Error())
|
|
return
|
|
}
|
|
showTrayBalloon("DCAuth v"+clientVersion+" 验证成功", "已确认本机设备,请返回浏览器继续登录。")
|
|
}
|
|
|
|
func showInfo(title, text string) {
|
|
showMessage(title, text, mbOK|mbIconInformation)
|
|
}
|
|
|
|
func showError(title, text string) {
|
|
showMessage(title, text, mbOK|mbIconError)
|
|
}
|
|
|
|
func confirm(title, text string) bool {
|
|
return showMessage(title, text, mbOKCancel|mbIconQuestion) == idOK
|
|
}
|
|
|
|
func showMessage(title, text string, flags uint) int {
|
|
titlePtr, _ := syscall.UTF16PtrFromString(title)
|
|
textPtr, _ := syscall.UTF16PtrFromString(text)
|
|
result, _, _ := messageBoxW.Call(
|
|
mainWindow,
|
|
uintptr(unsafe.Pointer(textPtr)),
|
|
uintptr(unsafe.Pointer(titlePtr)),
|
|
uintptr(flags|mbSetForeground|mbTopMost),
|
|
)
|
|
runtime.KeepAlive(titlePtr)
|
|
runtime.KeepAlive(textPtr)
|
|
return int(result)
|
|
}
|
|
|
|
func copyUTF16(destination []uint16, text string) {
|
|
encoded := utf16.Encode([]rune(text))
|
|
if len(encoded) >= len(destination) {
|
|
encoded = encoded[:len(destination)-1]
|
|
}
|
|
copy(destination, encoded)
|
|
destination[len(encoded)] = 0
|
|
}
|
|
|
|
func mustUTF16(text string) *uint16 {
|
|
value, _ := syscall.UTF16PtrFromString(text)
|
|
return value
|
|
}
|
|
|
|
func copyToClipboard(text string) {
|
|
opened, _, _ := openClip.Call(mainWindow)
|
|
if opened == 0 {
|
|
return
|
|
}
|
|
defer closeClip.Call()
|
|
emptyClip.Call(mainWindow)
|
|
|
|
encoded := utf16.Encode([]rune(text))
|
|
encoded = append(encoded, 0)
|
|
size := uintptr(len(encoded) * 2)
|
|
handle, _, _ := globalAlloc.Call(gmemMoveable, size)
|
|
if handle == 0 {
|
|
return
|
|
}
|
|
pointer, _, _ := globalLock.Call(handle)
|
|
if pointer == 0 {
|
|
return
|
|
}
|
|
target := unsafe.Slice((*uint16)(unsafe.Pointer(pointer)), len(encoded))
|
|
copy(target, encoded)
|
|
globalUnlock.Call(handle)
|
|
setClipData.Call(cfUnicodeText, handle)
|
|
}
|