app,app/internal/windows: [Windows] implement IME support

References: https://todo.sr.ht/~eliasnaur/gio/246
Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2022-02-11 19:05:46 +00:00
parent bace706240
commit 27e0b9cc7d
2 changed files with 142 additions and 39 deletions
+91 -38
View File
@@ -9,6 +9,7 @@ import (
"fmt"
"runtime"
"time"
"unicode/utf16"
"unsafe"
syscall "golang.org/x/sys/windows"
@@ -75,14 +76,26 @@ type MonitorInfo struct {
const (
TRUE = 1
CS_HREDRAW = 0x0002
CS_VREDRAW = 0x0001
CS_OWNDC = 0x0020
CPS_CANCEL = 0x0004
CS_HREDRAW = 0x0002
CS_INSERTCHAR = 0x2000
CS_NOMOVECARET = 0x4000
CS_VREDRAW = 0x0001
CS_OWNDC = 0x0020
CW_USEDEFAULT = -2147483648
GWL_STYLE = ^(uint32(16) - 1) // -16
HWND_TOPMOST = ^(uint32(1) - 1) // -1
GWL_STYLE = ^(uint32(16) - 1) // -16
GCS_COMPSTR = 0x0008
GCS_COMPREADSTR = 0x0001
GCS_CURSORPOS = 0x0080
GCS_DELTASTART = 0x0100
GCS_RESULTREADSTR = 0x0200
GCS_RESULTSTR = 0x0800
HWND_TOPMOST = ^(uint32(1) - 1) // -1
HTCLIENT = 1
@@ -102,10 +115,14 @@ const (
MONITOR_DEFAULTTOPRIMARY = 1
NI_COMPOSITIONSTR = 0x0015
SIZE_MAXIMIZED = 2
SIZE_MINIMIZED = 1
SIZE_RESTORED = 0
SCS_SETSTR = GCS_COMPREADSTR | GCS_COMPSTR
SW_SHOWDEFAULT = 10
SW_SHOWMINIMIZED = 2
SW_SHOWMAXIMIZED = 3
@@ -170,44 +187,46 @@ const (
UNICODE_NOCHAR = 65535
WM_CANCELMODE = 0x001F
WM_CHAR = 0x0102
WM_CREATE = 0x0001
WM_DPICHANGED = 0x02E0
WM_DESTROY = 0x0002
WM_ERASEBKGND = 0x0014
WM_KEYDOWN = 0x0100
WM_KEYUP = 0x0101
WM_LBUTTONDOWN = 0x0201
WM_LBUTTONUP = 0x0202
WM_MBUTTONDOWN = 0x0207
WM_MBUTTONUP = 0x0208
WM_MOUSEMOVE = 0x0200
WM_MOUSEWHEEL = 0x020A
WM_MOUSEHWHEEL = 0x020E
WM_PAINT = 0x000F
WM_CLOSE = 0x0010
WM_QUIT = 0x0012
WM_SETCURSOR = 0x0020
WM_SETFOCUS = 0x0007
WM_KILLFOCUS = 0x0008
WM_SHOWWINDOW = 0x0018
WM_SIZE = 0x0005
WM_SYSKEYDOWN = 0x0104
WM_SYSKEYUP = 0x0105
WM_RBUTTONDOWN = 0x0204
WM_RBUTTONUP = 0x0205
WM_TIMER = 0x0113
WM_UNICHAR = 0x0109
WM_USER = 0x0400
WM_GETMINMAXINFO = 0x0024
WM_WINDOWPOSCHANGED = 0x0047
WM_CANCELMODE = 0x001F
WM_CHAR = 0x0102
WM_CLOSE = 0x0010
WM_CREATE = 0x0001
WM_DPICHANGED = 0x02E0
WM_DESTROY = 0x0002
WM_ERASEBKGND = 0x0014
WM_GETMINMAXINFO = 0x0024
WM_IME_COMPOSITION = 0x010F
WM_IME_ENDCOMPOSITION = 0x010E
WM_KEYDOWN = 0x0100
WM_KEYUP = 0x0101
WM_KILLFOCUS = 0x0008
WM_LBUTTONDOWN = 0x0201
WM_LBUTTONUP = 0x0202
WM_MBUTTONDOWN = 0x0207
WM_MBUTTONUP = 0x0208
WM_MOUSEMOVE = 0x0200
WM_MOUSEWHEEL = 0x020A
WM_MOUSEHWHEEL = 0x020E
WM_PAINT = 0x000F
WM_QUIT = 0x0012
WM_SETCURSOR = 0x0020
WM_SETFOCUS = 0x0007
WM_SHOWWINDOW = 0x0018
WM_SIZE = 0x0005
WM_SYSKEYDOWN = 0x0104
WM_SYSKEYUP = 0x0105
WM_RBUTTONDOWN = 0x0204
WM_RBUTTONUP = 0x0205
WM_TIMER = 0x0113
WM_UNICHAR = 0x0109
WM_USER = 0x0400
WM_WINDOWPOSCHANGED = 0x0047
WS_CLIPCHILDREN = 0x00010000
WS_CLIPSIBLINGS = 0x04000000
WS_MAXIMIZE = 0x01000000
WS_ICONIC = 0x20000000
WS_VISIBLE = 0x10000000
WS_OVERLAPPED = 0x00000000
WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME |
WS_MINIMIZEBOX | WS_MAXIMIZEBOX
@@ -313,6 +332,12 @@ var (
gdi32 = syscall.NewLazySystemDLL("gdi32")
_GetDeviceCaps = gdi32.NewProc("GetDeviceCaps")
imm32 = syscall.NewLazySystemDLL("imm32")
_ImmGetContext = imm32.NewProc("ImmGetContext")
_ImmGetCompositionString = imm32.NewProc("ImmGetCompositionStringW")
_ImmNotifyIME = imm32.NewProc("ImmNotifyIME")
_ImmReleaseContext = imm32.NewProc("ImmReleaseContext")
)
func AdjustWindowRectEx(r *Rect, dwStyle uint32, bMenu int, dwExStyle uint32) {
@@ -487,6 +512,34 @@ func GetWindowLong(hwnd syscall.Handle) (style uintptr) {
return
}
func ImmGetContext(hwnd syscall.Handle) syscall.Handle {
h, _, _ := _ImmGetContext.Call(uintptr(hwnd))
return syscall.Handle(h)
}
func ImmReleaseContext(hwnd, imc syscall.Handle) {
_ImmReleaseContext.Call(uintptr(hwnd), uintptr(imc))
}
func ImmNotifyIME(imc syscall.Handle, action, index, value int) {
_ImmNotifyIME.Call(uintptr(imc), uintptr(action), uintptr(index), uintptr(value))
}
func ImmGetCompositionString(imc syscall.Handle, key int) string {
size, _, _ := _ImmGetCompositionString.Call(uintptr(imc), uintptr(key), 0, 0)
if int32(size) <= 0 {
return ""
}
u16 := make([]uint16, size/unsafe.Sizeof(uint16(0)))
_ImmGetCompositionString.Call(uintptr(imc), uintptr(key), uintptr(unsafe.Pointer(&u16[0])), size)
return string(utf16.Decode(u16))
}
func ImmGetCompositionValue(imc syscall.Handle, key int) int {
val, _, _ := _ImmGetCompositionString.Call(uintptr(imc), uintptr(key), 0, 0)
return int(int32(val))
}
func SetWindowLong(hwnd syscall.Handle, idx uint32, style uintptr) {
if runtime.GOARCH == "386" {
_SetWindowLong32.Call(uintptr(hwnd), uintptr(idx), style)
+51 -1
View File
@@ -13,6 +13,7 @@ import (
"sync"
"time"
"unicode"
"unicode/utf8"
"unsafe"
syscall "golang.org/x/sys/windows"
@@ -349,6 +350,48 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
}
case _WM_WAKEUP:
w.w.Event(wakeupEvent{})
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
}
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)
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
}
return windows.DefWindowProc(hwnd, msg, wParam, lParam)
@@ -451,7 +494,14 @@ loop:
return nil
}
func (w *window) EditorStateChanged(old, new editorState) {}
func (w *window) EditorStateChanged(old, new editorState) {
imc := windows.ImmGetContext(w.hwnd)
if imc == 0 {
return
}
defer windows.ImmReleaseContext(w.hwnd, imc)
windows.ImmNotifyIME(imc, windows.NI_COMPOSITIONSTR, windows.CPS_CANCEL, 0)
}
func (w *window) SetAnimating(anim bool) {
w.animating = anim