Refactor (windows).
This commit is contained in:
parent
4bfddad741
commit
b273c0992b
14 changed files with 214 additions and 145 deletions
|
@ -117,7 +117,7 @@ func (dlg *calendarDialog) setup(text string, opts options) (time.Time, error) {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := messageLoop(dlg.wnd); err != nil {
|
if err := win.MessageLoop(win.HWND(dlg.wnd)); err != nil {
|
||||||
return time.Time{}, err
|
return time.Time{}, err
|
||||||
}
|
}
|
||||||
if opts.ctx != nil && opts.ctx.Err() != nil {
|
if opts.ctx != nil && opts.ctx.Err() != nil {
|
||||||
|
|
|
@ -111,7 +111,7 @@ func (dlg *entryDialog) setup(text string, opts options) (string, error) {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := messageLoop(dlg.wnd); err != nil {
|
if err := win.MessageLoop(win.HWND(dlg.wnd)); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if opts.ctx != nil && opts.ctx.Err() != nil {
|
if opts.ctx != nil && opts.ctx.Err() != nil {
|
||||||
|
|
|
@ -17,6 +17,8 @@ const (
|
||||||
_FOS_FORCEFILESYSTEM = 0x00000040
|
_FOS_FORCEFILESYSTEM = 0x00000040
|
||||||
_FOS_ALLOWMULTISELECT = 0x00000200
|
_FOS_ALLOWMULTISELECT = 0x00000200
|
||||||
_FOS_FORCESHOWHIDDEN = 0x10000000
|
_FOS_FORCESHOWHIDDEN = 0x10000000
|
||||||
|
|
||||||
|
_SIGDN_FILESYSPATH = 0x80058000
|
||||||
)
|
)
|
||||||
|
|
||||||
func selectFile(opts options) (string, error) {
|
func selectFile(opts options) (string, error) {
|
||||||
|
@ -191,7 +193,7 @@ func selectFileSave(opts options) (string, error) {
|
||||||
func pickFolders(opts options, multi bool) (str string, lst []string, err error) {
|
func pickFolders(opts options, multi bool) (str string, lst []string, err error) {
|
||||||
defer setup()()
|
defer setup()()
|
||||||
|
|
||||||
err = win.CoInitializeEx(0, 0x6) // COINIT_APARTMENTTHREADED|COINIT_DISABLE_OLE1DDE
|
err = win.CoInitializeEx(0, win.COINIT_APARTMENTTHREADED|win.COINIT_DISABLE_OLE1DDE)
|
||||||
if err != win.RPC_E_CHANGED_MODE {
|
if err != win.RPC_E_CHANGED_MODE {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
|
@ -201,7 +203,7 @@ func pickFolders(opts options, multi bool) (str string, lst []string, err error)
|
||||||
|
|
||||||
var dialog *_IFileOpenDialog
|
var dialog *_IFileOpenDialog
|
||||||
err = win.CoCreateInstance(
|
err = win.CoCreateInstance(
|
||||||
_CLSID_FileOpenDialog, nil, 0x17, // CLSCTX_ALL
|
_CLSID_FileOpenDialog, nil, win.CLSCTX_ALL,
|
||||||
_IID_IFileOpenDialog, unsafe.Pointer(&dialog))
|
_IID_IFileOpenDialog, unsafe.Pointer(&dialog))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if multi {
|
if multi {
|
||||||
|
@ -257,7 +259,7 @@ func pickFolders(opts options, multi bool) (str string, lst []string, err error)
|
||||||
if opts.ctx != nil && opts.ctx.Err() != nil {
|
if opts.ctx != nil && opts.ctx.Err() != nil {
|
||||||
return "", nil, opts.ctx.Err()
|
return "", nil, opts.ctx.Err()
|
||||||
}
|
}
|
||||||
if hr == 0x800704c7 { // ERROR_CANCELLED
|
if hr == uintptr(win.E_CANCELED) {
|
||||||
return "", nil, ErrCanceled
|
return "", nil, ErrCanceled
|
||||||
}
|
}
|
||||||
if int32(hr) < 0 {
|
if int32(hr) < 0 {
|
||||||
|
@ -272,10 +274,9 @@ func pickFolders(opts options, multi bool) (str string, lst []string, err error)
|
||||||
}
|
}
|
||||||
defer item.Call(item.Release)
|
defer item.Call(item.Release)
|
||||||
|
|
||||||
var ptr uintptr
|
var ptr unsafe.Pointer
|
||||||
hr, _, _ = item.Call(item.GetDisplayName,
|
hr, _, _ = item.Call(item.GetDisplayName,
|
||||||
0x80058000, // SIGDN_FILESYSPATH
|
_SIGDN_FILESYSPATH, uintptr(unsafe.Pointer(&ptr)))
|
||||||
uintptr(unsafe.Pointer(&ptr)))
|
|
||||||
if int32(hr) < 0 {
|
if int32(hr) < 0 {
|
||||||
return syscall.Errno(hr)
|
return syscall.Errno(hr)
|
||||||
}
|
}
|
||||||
|
@ -314,7 +315,7 @@ func pickFolders(opts options, multi bool) (str string, lst []string, err error)
|
||||||
func browseForFolder(opts options) (string, []string, error) {
|
func browseForFolder(opts options) (string, []string, error) {
|
||||||
var args win.BROWSEINFO
|
var args win.BROWSEINFO
|
||||||
args.Owner, _ = opts.attach.(win.HWND)
|
args.Owner, _ = opts.attach.(win.HWND)
|
||||||
args.Flags = 0x1 // BIF_RETURNONLYFSDIRS
|
args.Flags = win.BIF_RETURNONLYFSDIRS
|
||||||
|
|
||||||
if opts.title != nil {
|
if opts.title != nil {
|
||||||
args.Title = syscall.StringToUTF16Ptr(*opts.title)
|
args.Title = syscall.StringToUTF16Ptr(*opts.title)
|
||||||
|
@ -336,7 +337,7 @@ func browseForFolder(opts options) (string, []string, error) {
|
||||||
if opts.ctx != nil && opts.ctx.Err() != nil {
|
if opts.ctx != nil && opts.ctx.Err() != nil {
|
||||||
return "", nil, opts.ctx.Err()
|
return "", nil, opts.ctx.Err()
|
||||||
}
|
}
|
||||||
if ptr == 0 {
|
if ptr == nil {
|
||||||
return "", nil, ErrCanceled
|
return "", nil, ErrCanceled
|
||||||
}
|
}
|
||||||
defer win.CoTaskMemFree(ptr)
|
defer win.CoTaskMemFree(ptr)
|
||||||
|
@ -349,8 +350,8 @@ func browseForFolder(opts options) (string, []string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func browseForFolderCallback(wnd win.HWND, msg uint32, lparam, data uintptr) uintptr {
|
func browseForFolderCallback(wnd win.HWND, msg uint32, lparam, data uintptr) uintptr {
|
||||||
if msg == 1 { // BFFM_INITIALIZED
|
if msg == win.BFFM_INITIALIZED {
|
||||||
win.SendMessage(wnd, 1024+103 /* BFFM_SETSELECTIONW */, 1 /* TRUE */, data)
|
win.SendMessage(wnd, win.BFFM_SETSELECTION, 1, data)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ type CHOOSECOLOR struct {
|
||||||
RgbResult uint32
|
RgbResult uint32
|
||||||
CustColors *[16]uint32
|
CustColors *[16]uint32
|
||||||
Flags uint32
|
Flags uint32
|
||||||
CustData uintptr
|
CustData Pointer
|
||||||
FnHook uintptr
|
FnHook uintptr
|
||||||
TemplateName *uint16
|
TemplateName *uint16
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ type OPENFILENAME struct {
|
||||||
FileOffset uint16
|
FileOffset uint16
|
||||||
FileExtension uint16
|
FileExtension uint16
|
||||||
DefExt *uint16
|
DefExt *uint16
|
||||||
CustData uintptr
|
CustData Pointer
|
||||||
FnHook uintptr
|
FnHook uintptr
|
||||||
TemplateName *uint16
|
TemplateName *uint16
|
||||||
PvReserved uintptr
|
PvReserved uintptr
|
||||||
|
|
|
@ -10,7 +10,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RPC_E_CHANGED_MODE syscall.Errno = 0x80010106
|
COINIT_MULTITHREADED = windows.COINIT_MULTITHREADED
|
||||||
|
COINIT_APARTMENTTHREADED = windows.COINIT_APARTMENTTHREADED
|
||||||
|
COINIT_DISABLE_OLE1DDE = windows.COINIT_DISABLE_OLE1DDE
|
||||||
|
COINIT_SPEED_OVER_MEMORY = windows.COINIT_SPEED_OVER_MEMORY
|
||||||
|
|
||||||
|
CLSCTX_INPROC_SERVER = windows.CLSCTX_INPROC_SERVER
|
||||||
|
CLSCTX_INPROC_HANDLER = windows.CLSCTX_INPROC_HANDLER
|
||||||
|
CLSCTX_LOCAL_SERVER = windows.CLSCTX_LOCAL_SERVER
|
||||||
|
CLSCTX_REMOTE_SERVER = windows.CLSCTX_REMOTE_SERVER
|
||||||
|
CLSCTX_ALL = windows.CLSCTX_INPROC_SERVER | windows.CLSCTX_INPROC_HANDLER | windows.CLSCTX_LOCAL_SERVER | windows.CLSCTX_REMOTE_SERVER
|
||||||
|
|
||||||
|
E_CANCELED = windows.ERROR_CANCELLED | windows.FACILITY_WIN32<<16 | 0x80000000
|
||||||
|
RPC_E_CHANGED_MODE = syscall.Errno(windows.RPC_E_CHANGED_MODE)
|
||||||
)
|
)
|
||||||
|
|
||||||
func CoInitializeEx(reserved uintptr, coInit uint32) error {
|
func CoInitializeEx(reserved uintptr, coInit uint32) error {
|
||||||
|
@ -19,6 +31,8 @@ func CoInitializeEx(reserved uintptr, coInit uint32) error {
|
||||||
|
|
||||||
func CoUninitialize() { windows.CoUninitialize() }
|
func CoUninitialize() { windows.CoUninitialize() }
|
||||||
|
|
||||||
|
func CoTaskMemFree(address unsafe.Pointer) { windows.CoTaskMemFree(address) }
|
||||||
|
|
||||||
// https://github.com/wine-mirror/wine/blob/master/include/unknwn.idl
|
// https://github.com/wine-mirror/wine/blob/master/include/unknwn.idl
|
||||||
|
|
||||||
type IUnknownVtbl struct {
|
type IUnknownVtbl struct {
|
||||||
|
@ -43,5 +57,4 @@ func (o *COMObject) Call(trap uintptr, a ...uintptr) (r1, r2 uintptr, lastErr er
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//sys CoTaskMemFree(address uintptr) = ole32.CoTaskMemFree
|
|
||||||
//sys CoCreateInstance(clsid uintptr, unkOuter unsafe.Pointer, clsContext int32, iid uintptr, address unsafe.Pointer) (res error) = ole32.CoCreateInstance
|
//sys CoCreateInstance(clsid uintptr, unkOuter unsafe.Pointer, clsContext int32, iid uintptr, address unsafe.Pointer) (res error) = ole32.CoCreateInstance
|
||||||
|
|
|
@ -3,6 +3,11 @@
|
||||||
package win
|
package win
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
BIF_RETURNONLYFSDIRS = 0x00000001
|
||||||
|
|
||||||
|
BFFM_INITIALIZED = 1
|
||||||
|
BFFM_SETSELECTION = WM_USER + 103
|
||||||
|
|
||||||
NIM_ADD = 0
|
NIM_ADD = 0
|
||||||
NIM_DELETE = 2
|
NIM_DELETE = 2
|
||||||
)
|
)
|
||||||
|
@ -10,7 +15,7 @@ const (
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/ns-shlobj_core-browseinfow
|
// https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/ns-shlobj_core-browseinfow
|
||||||
type BROWSEINFO struct {
|
type BROWSEINFO struct {
|
||||||
Owner HWND
|
Owner HWND
|
||||||
Root uintptr
|
Root Pointer
|
||||||
DisplayName *uint16
|
DisplayName *uint16
|
||||||
Title *uint16
|
Title *uint16
|
||||||
Flags uint32
|
Flags uint32
|
||||||
|
@ -52,7 +57,7 @@ type _IShellItemVtbl struct {
|
||||||
Compare uintptr
|
Compare uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
//sys SHBrowseForFolder(bi *BROWSEINFO) (ptr uintptr) = shell32.SHBrowseForFolder
|
//sys SHBrowseForFolder(bi *BROWSEINFO) (ptr unsafe.Pointer) = shell32.SHBrowseForFolder
|
||||||
//sys SHCreateItemFromParsingName(path *uint16, bc unsafe.Pointer, iid uintptr, item **IShellItem) (res error) = shell32.SHCreateItemFromParsingName
|
//sys SHCreateItemFromParsingName(path *uint16, bc unsafe.Pointer, iid uintptr, item **IShellItem) (res error) = shell32.SHCreateItemFromParsingName
|
||||||
//sys SHGetPathFromIDListEx(ptr uintptr, path *uint16, pathLen int, opts int) (ok bool) = shell32.SHGetPathFromIDListEx
|
//sys SHGetPathFromIDListEx(ptr unsafe.Pointer, path *uint16, pathLen int, opts int) (ok bool) = shell32.SHGetPathFromIDListEx
|
||||||
//sys ShellNotifyIcon(message uint32, data *NOTIFYICONDATA) (ret int, err error) = shell32.Shell_NotifyIconW
|
//sys ShellNotifyIcon(message uint32, data *NOTIFYICONDATA) (ret int, err error) = shell32.Shell_NotifyIconW
|
||||||
|
|
|
@ -2,7 +2,12 @@
|
||||||
|
|
||||||
package win
|
package win
|
||||||
|
|
||||||
import "golang.org/x/sys/windows"
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
IDOK = 1
|
IDOK = 1
|
||||||
|
@ -49,6 +54,12 @@ const (
|
||||||
PBM_SETRANGE32 = WM_USER + 6
|
PBM_SETRANGE32 = WM_USER + 6
|
||||||
PBM_SETMARQUEE = WM_USER + 10
|
PBM_SETMARQUEE = WM_USER + 10
|
||||||
STM_SETICON = 0x0170
|
STM_SETICON = 0x0170
|
||||||
|
|
||||||
|
DPI_AWARENESS_CONTEXT_UNAWARE = ^uintptr(1) + 1
|
||||||
|
DPI_AWARENESS_CONTEXT_SYSTEM_AWARE = ^uintptr(2) + 1
|
||||||
|
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = ^uintptr(3) + 1
|
||||||
|
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = ^uintptr(4) + 1
|
||||||
|
DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED = ^uintptr(5) + 1
|
||||||
)
|
)
|
||||||
|
|
||||||
func MessageBox(hwnd HWND, text *uint16, caption *uint16, boxtype uint32) (ret int32, err error) {
|
func MessageBox(hwnd HWND, text *uint16, caption *uint16, boxtype uint32) (ret int32, err error) {
|
||||||
|
@ -59,8 +70,61 @@ func GetWindowThreadProcessId(hwnd HWND, pid *uint32) (tid uint32, err error) {
|
||||||
return windows.GetWindowThreadProcessId(hwnd, pid)
|
return windows.GetWindowThreadProcessId(hwnd, pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetThreadDpiAwarenessContext(dpiContext uintptr) (ret uintptr, err error) {
|
||||||
|
if err := procSetThreadDpiAwarenessContext.Find(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return setThreadDpiAwarenessContext(dpiContext), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/winmsg/using-messages-and-message-queues
|
||||||
|
func MessageLoop(wnd HWND) error {
|
||||||
|
getMessage := procGetMessageW.Addr()
|
||||||
|
translateMessage := procTranslateMessage.Addr()
|
||||||
|
dispatchMessage := procDispatchMessageW.Addr()
|
||||||
|
isDialogMessage := procIsDialogMessageW.Addr()
|
||||||
|
|
||||||
|
for {
|
||||||
|
var msg MSG
|
||||||
|
s, _, err := syscall.Syscall6(getMessage, 4, uintptr(unsafe.Pointer(&msg)), 0, 0, 0, 0, 0)
|
||||||
|
if int32(s) == -1 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s, _, _ = syscall.Syscall(isDialogMessage, 2, uintptr(wnd), uintptr(unsafe.Pointer(&msg)), 0)
|
||||||
|
if s == 0 {
|
||||||
|
syscall.Syscall(translateMessage, 1, uintptr(unsafe.Pointer(&msg)), 0, 0)
|
||||||
|
syscall.Syscall(dispatchMessage, 1, uintptr(unsafe.Pointer(&msg)), 0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-msg
|
||||||
|
type MSG struct {
|
||||||
|
Owner syscall.Handle
|
||||||
|
Message uint32
|
||||||
|
WParam uintptr
|
||||||
|
LParam uintptr
|
||||||
|
Time uint32
|
||||||
|
Pt POINT
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/api/windef/ns-windef-point
|
||||||
|
type POINT struct {
|
||||||
|
x, y int32
|
||||||
|
}
|
||||||
|
|
||||||
|
//sys DispatchMessage(msg *MSG) (ret uintptr) = user32.DispatchMessageW
|
||||||
|
//sys EnumChildWindows(parent HWND, enumFunc uintptr, lparam unsafe.Pointer) = user32.EnumChildWindows
|
||||||
|
//sys EnumWindows(enumFunc uintptr, lparam unsafe.Pointer) (err error) = user32.EnumChildWindows
|
||||||
//sys GetDlgCtrlID(wnd HWND) (ret int) = user32.GetDlgCtrlID
|
//sys GetDlgCtrlID(wnd HWND) (ret int) = user32.GetDlgCtrlID
|
||||||
|
//sys GetMessage(msg *MSG, wnd HWND, msgFilterMin uint32, msgFilterMax uint32) (ret uintptr) = user32.GetMessageW
|
||||||
|
//sys IsDialogMessage(wnd HWND, msg *MSG) (ok bool) = user32.IsDialogMessageW
|
||||||
//sys SendMessage(wnd HWND, msg uint32, wparam uintptr, lparam uintptr) (ret uintptr) = user32.SendMessageW
|
//sys SendMessage(wnd HWND, msg uint32, wparam uintptr, lparam uintptr) (ret uintptr) = user32.SendMessageW
|
||||||
|
//sys SetForegroundWindow(wnd HWND) (ok bool) = user32.SetForegroundWindow
|
||||||
|
//sys setThreadDpiAwarenessContext(dpiContext uintptr) (ret uintptr) = user32.SetThreadDpiAwarenessContext
|
||||||
//sys SetWindowText(wnd HWND, text *uint16) (err error) = user32.SetWindowTextW
|
//sys SetWindowText(wnd HWND, text *uint16) (err error) = user32.SetWindowTextW
|
||||||
//sys EnumChildWindows(parent HWND, enumFunc uintptr, lparam uintptr) = user32.EnumChildWindows
|
//sys TranslateMessage(msg *MSG) (ok bool) = user32.TranslateMessage
|
||||||
//sys EnumWindows(enumFunc uintptr, lparam uintptr) (err error) = user32.EnumChildWindows
|
|
||||||
|
|
|
@ -7,5 +7,6 @@ import "golang.org/x/sys/windows"
|
||||||
|
|
||||||
type Handle = windows.Handle
|
type Handle = windows.Handle
|
||||||
type HWND = windows.HWND
|
type HWND = windows.HWND
|
||||||
|
type Pointer = windows.Pointer
|
||||||
|
|
||||||
//sys RtlGetNtVersionNumbers(major *uint32, minor *uint32, build *uint32) = ntdll.RtlGetNtVersionNumbers
|
//sys RtlGetNtVersionNumbers(major *uint32, minor *uint32, build *uint32) = ntdll.RtlGetNtVersionNumbers
|
||||||
|
|
|
@ -48,28 +48,33 @@ var (
|
||||||
moduser32 = windows.NewLazySystemDLL("user32.dll")
|
moduser32 = windows.NewLazySystemDLL("user32.dll")
|
||||||
modwtsapi32 = windows.NewLazySystemDLL("wtsapi32.dll")
|
modwtsapi32 = windows.NewLazySystemDLL("wtsapi32.dll")
|
||||||
|
|
||||||
procInitCommonControlsEx = modcomctl32.NewProc("InitCommonControlsEx")
|
procInitCommonControlsEx = modcomctl32.NewProc("InitCommonControlsEx")
|
||||||
procChooseColorW = modcomdlg32.NewProc("ChooseColorW")
|
procChooseColorW = modcomdlg32.NewProc("ChooseColorW")
|
||||||
procCommDlgExtendedError = modcomdlg32.NewProc("CommDlgExtendedError")
|
procCommDlgExtendedError = modcomdlg32.NewProc("CommDlgExtendedError")
|
||||||
procGetOpenFileNameW = modcomdlg32.NewProc("GetOpenFileNameW")
|
procGetOpenFileNameW = modcomdlg32.NewProc("GetOpenFileNameW")
|
||||||
procGetSaveFileNameW = modcomdlg32.NewProc("GetSaveFileNameW")
|
procGetSaveFileNameW = modcomdlg32.NewProc("GetSaveFileNameW")
|
||||||
procCreateFontIndirectW = modgdi32.NewProc("CreateFontIndirectW")
|
procCreateFontIndirectW = modgdi32.NewProc("CreateFontIndirectW")
|
||||||
procDeleteObject = modgdi32.NewProc("DeleteObject")
|
procDeleteObject = modgdi32.NewProc("DeleteObject")
|
||||||
procGetDeviceCaps = modgdi32.NewProc("GetDeviceCaps")
|
procGetDeviceCaps = modgdi32.NewProc("GetDeviceCaps")
|
||||||
procGetConsoleWindow = modkernel32.NewProc("GetConsoleWindow")
|
procGetConsoleWindow = modkernel32.NewProc("GetConsoleWindow")
|
||||||
procGetModuleHandleW = modkernel32.NewProc("GetModuleHandleW")
|
procGetModuleHandleW = modkernel32.NewProc("GetModuleHandleW")
|
||||||
procRtlGetNtVersionNumbers = modntdll.NewProc("RtlGetNtVersionNumbers")
|
procRtlGetNtVersionNumbers = modntdll.NewProc("RtlGetNtVersionNumbers")
|
||||||
procCoCreateInstance = modole32.NewProc("CoCreateInstance")
|
procCoCreateInstance = modole32.NewProc("CoCreateInstance")
|
||||||
procCoTaskMemFree = modole32.NewProc("CoTaskMemFree")
|
procSHBrowseForFolder = modshell32.NewProc("SHBrowseForFolder")
|
||||||
procSHBrowseForFolder = modshell32.NewProc("SHBrowseForFolder")
|
procSHCreateItemFromParsingName = modshell32.NewProc("SHCreateItemFromParsingName")
|
||||||
procSHCreateItemFromParsingName = modshell32.NewProc("SHCreateItemFromParsingName")
|
procSHGetPathFromIDListEx = modshell32.NewProc("SHGetPathFromIDListEx")
|
||||||
procSHGetPathFromIDListEx = modshell32.NewProc("SHGetPathFromIDListEx")
|
procShell_NotifyIconW = modshell32.NewProc("Shell_NotifyIconW")
|
||||||
procShell_NotifyIconW = modshell32.NewProc("Shell_NotifyIconW")
|
procDispatchMessageW = moduser32.NewProc("DispatchMessageW")
|
||||||
procEnumChildWindows = moduser32.NewProc("EnumChildWindows")
|
procEnumChildWindows = moduser32.NewProc("EnumChildWindows")
|
||||||
procGetDlgCtrlID = moduser32.NewProc("GetDlgCtrlID")
|
procGetDlgCtrlID = moduser32.NewProc("GetDlgCtrlID")
|
||||||
procSendMessageW = moduser32.NewProc("SendMessageW")
|
procGetMessageW = moduser32.NewProc("GetMessageW")
|
||||||
procSetWindowTextW = moduser32.NewProc("SetWindowTextW")
|
procIsDialogMessageW = moduser32.NewProc("IsDialogMessageW")
|
||||||
procWTSSendMessageW = modwtsapi32.NewProc("WTSSendMessageW")
|
procSendMessageW = moduser32.NewProc("SendMessageW")
|
||||||
|
procSetForegroundWindow = moduser32.NewProc("SetForegroundWindow")
|
||||||
|
procSetThreadDpiAwarenessContext = moduser32.NewProc("SetThreadDpiAwarenessContext")
|
||||||
|
procSetWindowTextW = moduser32.NewProc("SetWindowTextW")
|
||||||
|
procTranslateMessage = moduser32.NewProc("TranslateMessage")
|
||||||
|
procWTSSendMessageW = modwtsapi32.NewProc("WTSSendMessageW")
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitCommonControlsEx(icc *INITCOMMONCONTROLSEX) (ok bool) {
|
func InitCommonControlsEx(icc *INITCOMMONCONTROLSEX) (ok bool) {
|
||||||
|
@ -148,14 +153,9 @@ func CoCreateInstance(clsid uintptr, unkOuter unsafe.Pointer, clsContext int32,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func CoTaskMemFree(address uintptr) {
|
func SHBrowseForFolder(bi *BROWSEINFO) (ptr unsafe.Pointer) {
|
||||||
syscall.Syscall(procCoTaskMemFree.Addr(), 1, uintptr(address), 0, 0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func SHBrowseForFolder(bi *BROWSEINFO) (ptr uintptr) {
|
|
||||||
r0, _, _ := syscall.Syscall(procSHBrowseForFolder.Addr(), 1, uintptr(unsafe.Pointer(bi)), 0, 0)
|
r0, _, _ := syscall.Syscall(procSHBrowseForFolder.Addr(), 1, uintptr(unsafe.Pointer(bi)), 0, 0)
|
||||||
ptr = uintptr(r0)
|
ptr = unsafe.Pointer(r0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,7 +167,7 @@ func SHCreateItemFromParsingName(path *uint16, bc unsafe.Pointer, iid uintptr, i
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func SHGetPathFromIDListEx(ptr uintptr, path *uint16, pathLen int, opts int) (ok bool) {
|
func SHGetPathFromIDListEx(ptr unsafe.Pointer, path *uint16, pathLen int, opts int) (ok bool) {
|
||||||
r0, _, _ := syscall.Syscall6(procSHGetPathFromIDListEx.Addr(), 4, uintptr(ptr), uintptr(unsafe.Pointer(path)), uintptr(pathLen), uintptr(opts), 0, 0)
|
r0, _, _ := syscall.Syscall6(procSHGetPathFromIDListEx.Addr(), 4, uintptr(ptr), uintptr(unsafe.Pointer(path)), uintptr(pathLen), uintptr(opts), 0, 0)
|
||||||
ok = r0 != 0
|
ok = r0 != 0
|
||||||
return
|
return
|
||||||
|
@ -182,12 +182,18 @@ func ShellNotifyIcon(message uint32, data *NOTIFYICONDATA) (ret int, err error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func EnumChildWindows(parent HWND, enumFunc uintptr, lparam uintptr) {
|
func DispatchMessage(msg *MSG) (ret uintptr) {
|
||||||
|
r0, _, _ := syscall.Syscall(procDispatchMessageW.Addr(), 1, uintptr(unsafe.Pointer(msg)), 0, 0)
|
||||||
|
ret = uintptr(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func EnumChildWindows(parent HWND, enumFunc uintptr, lparam unsafe.Pointer) {
|
||||||
syscall.Syscall(procEnumChildWindows.Addr(), 3, uintptr(parent), uintptr(enumFunc), uintptr(lparam))
|
syscall.Syscall(procEnumChildWindows.Addr(), 3, uintptr(parent), uintptr(enumFunc), uintptr(lparam))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func EnumWindows(enumFunc uintptr, lparam uintptr) (err error) {
|
func EnumWindows(enumFunc uintptr, lparam unsafe.Pointer) (err error) {
|
||||||
r1, _, e1 := syscall.Syscall(procEnumChildWindows.Addr(), 2, uintptr(enumFunc), uintptr(lparam), 0)
|
r1, _, e1 := syscall.Syscall(procEnumChildWindows.Addr(), 2, uintptr(enumFunc), uintptr(lparam), 0)
|
||||||
if r1 == 0 {
|
if r1 == 0 {
|
||||||
err = errnoErr(e1)
|
err = errnoErr(e1)
|
||||||
|
@ -201,12 +207,36 @@ func GetDlgCtrlID(wnd HWND) (ret int) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetMessage(msg *MSG, wnd HWND, msgFilterMin uint32, msgFilterMax uint32) (ret uintptr) {
|
||||||
|
r0, _, _ := syscall.Syscall6(procGetMessageW.Addr(), 4, uintptr(unsafe.Pointer(msg)), uintptr(wnd), uintptr(msgFilterMin), uintptr(msgFilterMax), 0, 0)
|
||||||
|
ret = uintptr(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsDialogMessage(wnd HWND, msg *MSG) (ok bool) {
|
||||||
|
r0, _, _ := syscall.Syscall(procIsDialogMessageW.Addr(), 2, uintptr(wnd), uintptr(unsafe.Pointer(msg)), 0)
|
||||||
|
ok = r0 != 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func SendMessage(wnd HWND, msg uint32, wparam uintptr, lparam uintptr) (ret uintptr) {
|
func SendMessage(wnd HWND, msg uint32, wparam uintptr, lparam uintptr) (ret uintptr) {
|
||||||
r0, _, _ := syscall.Syscall6(procSendMessageW.Addr(), 4, uintptr(wnd), uintptr(msg), uintptr(wparam), uintptr(lparam), 0, 0)
|
r0, _, _ := syscall.Syscall6(procSendMessageW.Addr(), 4, uintptr(wnd), uintptr(msg), uintptr(wparam), uintptr(lparam), 0, 0)
|
||||||
ret = uintptr(r0)
|
ret = uintptr(r0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetForegroundWindow(wnd HWND) (ok bool) {
|
||||||
|
r0, _, _ := syscall.Syscall(procSetForegroundWindow.Addr(), 1, uintptr(wnd), 0, 0)
|
||||||
|
ok = r0 != 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func setThreadDpiAwarenessContext(dpiContext uintptr) (ret uintptr) {
|
||||||
|
r0, _, _ := syscall.Syscall(procSetThreadDpiAwarenessContext.Addr(), 1, uintptr(dpiContext), 0, 0)
|
||||||
|
ret = uintptr(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func SetWindowText(wnd HWND, text *uint16) (err error) {
|
func SetWindowText(wnd HWND, text *uint16) (err error) {
|
||||||
r1, _, e1 := syscall.Syscall(procSetWindowTextW.Addr(), 2, uintptr(wnd), uintptr(unsafe.Pointer(text)), 0)
|
r1, _, e1 := syscall.Syscall(procSetWindowTextW.Addr(), 2, uintptr(wnd), uintptr(unsafe.Pointer(text)), 0)
|
||||||
if r1 == 0 {
|
if r1 == 0 {
|
||||||
|
@ -215,6 +245,12 @@ func SetWindowText(wnd HWND, text *uint16) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TranslateMessage(msg *MSG) (ok bool) {
|
||||||
|
r0, _, _ := syscall.Syscall(procTranslateMessage.Addr(), 1, uintptr(unsafe.Pointer(msg)), 0, 0)
|
||||||
|
ok = r0 != 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func WTSSendMessage(server Handle, sessionID uint32, title *uint16, titleLength int, message *uint16, messageLength int, style uint32, timeout int, response *uint32, wait bool) (err error) {
|
func WTSSendMessage(server Handle, sessionID uint32, title *uint16, titleLength int, message *uint16, messageLength int, style uint32, timeout int, response *uint32, wait bool) (err error) {
|
||||||
var _p0 uint32
|
var _p0 uint32
|
||||||
if wait {
|
if wait {
|
||||||
|
|
|
@ -131,7 +131,7 @@ func (dlg *listDialog) setup(text string, opts options) ([]string, error) {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := messageLoop(dlg.wnd); err != nil {
|
if err := win.MessageLoop(win.HWND(dlg.wnd)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if opts.ctx != nil && opts.ctx.Err() != nil {
|
if opts.ctx != nil && opts.ctx.Err() != nil {
|
||||||
|
|
|
@ -90,7 +90,7 @@ func message(kind messageKind, text string, opts options) error {
|
||||||
func hookMessageDialog(opts options) (unhook context.CancelFunc, err error) {
|
func hookMessageDialog(opts options) (unhook context.CancelFunc, err error) {
|
||||||
return hookDialog(opts.ctx, opts.windowIcon, nil, func(wnd win.HWND) {
|
return hookDialog(opts.ctx, opts.windowIcon, nil, func(wnd win.HWND) {
|
||||||
win.EnumChildWindows(wnd, syscall.NewCallback(hookMessageDialogCallback),
|
win.EnumChildWindows(wnd, syscall.NewCallback(hookMessageDialogCallback),
|
||||||
uintptr(unsafe.Pointer(&opts)))
|
unsafe.Pointer(&opts))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -197,7 +197,7 @@ func (dlg *progressDialog) setup(opts options) error {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := messageLoop(dlg.wnd); err != nil {
|
if err := win.MessageLoop(win.HWND(dlg.wnd)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if opts.ctx != nil && opts.ctx.Err() != nil {
|
if opts.ctx != nil && opts.ctx.Err() != nil {
|
||||||
|
|
|
@ -128,7 +128,7 @@ func (dlg *passwordDialog) setup(opts options) (string, string, error) {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := messageLoop(dlg.wnd); err != nil {
|
if err := win.MessageLoop(win.HWND(dlg.wnd)); err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
if opts.ctx != nil && opts.ctx.Err() != nil {
|
if opts.ctx != nil && opts.ctx.Err() != nil {
|
||||||
|
|
123
util_windows.go
123
util_windows.go
|
@ -25,40 +25,34 @@ var (
|
||||||
deactivateActCtx = kernel32.NewProc("DeactivateActCtx")
|
deactivateActCtx = kernel32.NewProc("DeactivateActCtx")
|
||||||
getModuleHandle = kernel32.NewProc("GetModuleHandleW")
|
getModuleHandle = kernel32.NewProc("GetModuleHandleW")
|
||||||
|
|
||||||
callNextHookEx = user32.NewProc("CallNextHookEx")
|
callNextHookEx = user32.NewProc("CallNextHookEx")
|
||||||
createIconFromResource = user32.NewProc("CreateIconFromResource")
|
createIconFromResource = user32.NewProc("CreateIconFromResource")
|
||||||
createWindowEx = user32.NewProc("CreateWindowExW")
|
createWindowEx = user32.NewProc("CreateWindowExW")
|
||||||
defWindowProc = user32.NewProc("DefWindowProcW")
|
defWindowProc = user32.NewProc("DefWindowProcW")
|
||||||
destroyIcon = user32.NewProc("DestroyIcon")
|
destroyIcon = user32.NewProc("DestroyIcon")
|
||||||
destroyWindow = user32.NewProc("DestroyWindow")
|
destroyWindow = user32.NewProc("DestroyWindow")
|
||||||
dispatchMessage = user32.NewProc("DispatchMessageW")
|
enableWindow = user32.NewProc("EnableWindow")
|
||||||
enableWindow = user32.NewProc("EnableWindow")
|
getDpiForWindow = user32.NewProc("GetDpiForWindow")
|
||||||
getDpiForWindow = user32.NewProc("GetDpiForWindow")
|
getSystemMetrics = user32.NewProc("GetSystemMetrics")
|
||||||
getMessage = user32.NewProc("GetMessageW")
|
getWindowDC = user32.NewProc("GetWindowDC")
|
||||||
getSystemMetrics = user32.NewProc("GetSystemMetrics")
|
getWindowRect = user32.NewProc("GetWindowRect")
|
||||||
getWindowDC = user32.NewProc("GetWindowDC")
|
getWindowText = user32.NewProc("GetWindowTextW")
|
||||||
getWindowRect = user32.NewProc("GetWindowRect")
|
getWindowTextLength = user32.NewProc("GetWindowTextLengthW")
|
||||||
getWindowText = user32.NewProc("GetWindowTextW")
|
loadIcon = user32.NewProc("LoadIconW")
|
||||||
getWindowTextLength = user32.NewProc("GetWindowTextLengthW")
|
loadImage = user32.NewProc("LoadImageW")
|
||||||
isDialogMessage = user32.NewProc("IsDialogMessageW")
|
postQuitMessage = user32.NewProc("PostQuitMessage")
|
||||||
loadIcon = user32.NewProc("LoadIconW")
|
registerClassEx = user32.NewProc("RegisterClassExW")
|
||||||
loadImage = user32.NewProc("LoadImageW")
|
releaseDC = user32.NewProc("ReleaseDC")
|
||||||
postQuitMessage = user32.NewProc("PostQuitMessage")
|
sendMessage = user32.NewProc("SendMessageW")
|
||||||
registerClassEx = user32.NewProc("RegisterClassExW")
|
setFocus = user32.NewProc("SetFocus")
|
||||||
releaseDC = user32.NewProc("ReleaseDC")
|
setWindowLong = user32.NewProc("SetWindowLongW")
|
||||||
sendMessage = user32.NewProc("SendMessageW")
|
setWindowPos = user32.NewProc("SetWindowPos")
|
||||||
setFocus = user32.NewProc("SetFocus")
|
setWindowsHookEx = user32.NewProc("SetWindowsHookExW")
|
||||||
setForegroundWindow = user32.NewProc("SetForegroundWindow")
|
setWindowText = user32.NewProc("SetWindowTextW")
|
||||||
setThreadDpiAwarenessContext = user32.NewProc("SetThreadDpiAwarenessContext")
|
showWindow = user32.NewProc("ShowWindow")
|
||||||
setWindowLong = user32.NewProc("SetWindowLongW")
|
systemParametersInfo = user32.NewProc("SystemParametersInfoW")
|
||||||
setWindowPos = user32.NewProc("SetWindowPos")
|
unhookWindowsHookEx = user32.NewProc("UnhookWindowsHookEx")
|
||||||
setWindowsHookEx = user32.NewProc("SetWindowsHookExW")
|
unregisterClass = user32.NewProc("UnregisterClassW")
|
||||||
setWindowText = user32.NewProc("SetWindowTextW")
|
|
||||||
showWindow = user32.NewProc("ShowWindow")
|
|
||||||
systemParametersInfo = user32.NewProc("SystemParametersInfoW")
|
|
||||||
translateMessage = user32.NewProc("TranslateMessage")
|
|
||||||
unhookWindowsHookEx = user32.NewProc("UnhookWindowsHookEx")
|
|
||||||
unregisterClass = user32.NewProc("UnregisterClassW")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func intptr(i int64) uintptr {
|
func intptr(i int64) uintptr {
|
||||||
|
@ -73,28 +67,23 @@ func hwnd(i uint64) win.HWND { return win.HWND(uintptr(i)) }
|
||||||
|
|
||||||
func setup() context.CancelFunc {
|
func setup() context.CancelFunc {
|
||||||
var wnd win.HWND
|
var wnd win.HWND
|
||||||
win.EnumWindows(syscall.NewCallback(setupEnumCallback), uintptr(unsafe.Pointer(&wnd)))
|
win.EnumWindows(syscall.NewCallback(setupEnumCallback), unsafe.Pointer(&wnd))
|
||||||
if wnd == 0 {
|
if wnd == 0 {
|
||||||
wnd = win.GetConsoleWindow()
|
wnd = win.GetConsoleWindow()
|
||||||
}
|
}
|
||||||
if wnd != 0 {
|
if wnd != 0 {
|
||||||
setForegroundWindow.Call(uintptr(wnd))
|
win.SetForegroundWindow(wnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
|
|
||||||
var restore uintptr
|
var restore uintptr
|
||||||
cookie := enableVisualStyles()
|
cookie := enableVisualStyles()
|
||||||
if setThreadDpiAwarenessContext.Find() == nil {
|
for dpi := win.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2; dpi <= win.DPI_AWARENESS_CONTEXT_SYSTEM_AWARE; dpi++ {
|
||||||
// try:
|
var err error
|
||||||
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
|
restore, err = win.SetThreadDpiAwarenessContext(dpi)
|
||||||
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE
|
if restore != 0 || err != nil {
|
||||||
// DPI_AWARENESS_CONTEXT_SYSTEM_AWARE
|
break
|
||||||
for i := -4; i <= -2; i++ {
|
|
||||||
restore, _, _ = setThreadDpiAwarenessContext.Call(uintptr(i))
|
|
||||||
if restore != 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +94,7 @@ func setup() context.CancelFunc {
|
||||||
|
|
||||||
return func() {
|
return func() {
|
||||||
if restore != 0 {
|
if restore != 0 {
|
||||||
setThreadDpiAwarenessContext.Call(restore)
|
win.SetThreadDpiAwarenessContext(restore)
|
||||||
}
|
}
|
||||||
if cookie != 0 {
|
if cookie != 0 {
|
||||||
deactivateActCtx.Call(0, cookie)
|
deactivateActCtx.Call(0, cookie)
|
||||||
|
@ -391,31 +380,6 @@ func registerClass(instance, icon, proc uintptr) (uintptr, error) {
|
||||||
return atom, err
|
return atom, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/winmsg/using-messages-and-message-queues
|
|
||||||
func messageLoop(wnd uintptr) error {
|
|
||||||
getMessage := getMessage.Addr()
|
|
||||||
isDialogMessage := isDialogMessage.Addr()
|
|
||||||
translateMessage := translateMessage.Addr()
|
|
||||||
dispatchMessage := dispatchMessage.Addr()
|
|
||||||
|
|
||||||
for {
|
|
||||||
var msg _MSG
|
|
||||||
s, _, err := syscall.Syscall6(getMessage, 4, uintptr(unsafe.Pointer(&msg)), 0, 0, 0, 0, 0)
|
|
||||||
if int32(s) == -1 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if s == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
s, _, _ = syscall.Syscall(isDialogMessage, 2, wnd, uintptr(unsafe.Pointer(&msg)), 0)
|
|
||||||
if s == 0 {
|
|
||||||
syscall.Syscall(translateMessage, 1, uintptr(unsafe.Pointer(&msg)), 0, 0)
|
|
||||||
syscall.Syscall(dispatchMessage, 1, uintptr(unsafe.Pointer(&msg)), 0, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/4308503/how-to-enable-visual-styles-without-a-manifest
|
// https://stackoverflow.com/questions/4308503/how-to-enable-visual-styles-without-a-manifest
|
||||||
func enableVisualStyles() (cookie uintptr) {
|
func enableVisualStyles() (cookie uintptr) {
|
||||||
dir, err := win.GetSystemDirectory()
|
dir, err := win.GetSystemDirectory()
|
||||||
|
@ -477,21 +441,6 @@ type _NONCLIENTMETRICS struct {
|
||||||
MessageFont win.LOGFONT
|
MessageFont win.LOGFONT
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-msg
|
|
||||||
type _MSG struct {
|
|
||||||
Owner syscall.Handle
|
|
||||||
Message uint32
|
|
||||||
WParam uintptr
|
|
||||||
LParam uintptr
|
|
||||||
Time uint32
|
|
||||||
Pt _POINT
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/api/windef/ns-windef-point
|
|
||||||
type _POINT struct {
|
|
||||||
x, y int32
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/api/windef/ns-windef-rect
|
// https://docs.microsoft.com/en-us/windows/win32/api/windef/ns-windef-rect
|
||||||
type _RECT struct {
|
type _RECT struct {
|
||||||
left int32
|
left int32
|
||||||
|
|
Loading…
Reference in a new issue