mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-05 17:35:36 +00:00
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:
@@ -9,6 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode/utf16"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
syscall "golang.org/x/sys/windows"
|
syscall "golang.org/x/sys/windows"
|
||||||
@@ -75,14 +76,26 @@ type MonitorInfo struct {
|
|||||||
const (
|
const (
|
||||||
TRUE = 1
|
TRUE = 1
|
||||||
|
|
||||||
CS_HREDRAW = 0x0002
|
CPS_CANCEL = 0x0004
|
||||||
CS_VREDRAW = 0x0001
|
|
||||||
CS_OWNDC = 0x0020
|
CS_HREDRAW = 0x0002
|
||||||
|
CS_INSERTCHAR = 0x2000
|
||||||
|
CS_NOMOVECARET = 0x4000
|
||||||
|
CS_VREDRAW = 0x0001
|
||||||
|
CS_OWNDC = 0x0020
|
||||||
|
|
||||||
CW_USEDEFAULT = -2147483648
|
CW_USEDEFAULT = -2147483648
|
||||||
|
|
||||||
GWL_STYLE = ^(uint32(16) - 1) // -16
|
GWL_STYLE = ^(uint32(16) - 1) // -16
|
||||||
HWND_TOPMOST = ^(uint32(1) - 1) // -1
|
|
||||||
|
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
|
HTCLIENT = 1
|
||||||
|
|
||||||
@@ -102,10 +115,14 @@ const (
|
|||||||
|
|
||||||
MONITOR_DEFAULTTOPRIMARY = 1
|
MONITOR_DEFAULTTOPRIMARY = 1
|
||||||
|
|
||||||
|
NI_COMPOSITIONSTR = 0x0015
|
||||||
|
|
||||||
SIZE_MAXIMIZED = 2
|
SIZE_MAXIMIZED = 2
|
||||||
SIZE_MINIMIZED = 1
|
SIZE_MINIMIZED = 1
|
||||||
SIZE_RESTORED = 0
|
SIZE_RESTORED = 0
|
||||||
|
|
||||||
|
SCS_SETSTR = GCS_COMPREADSTR | GCS_COMPSTR
|
||||||
|
|
||||||
SW_SHOWDEFAULT = 10
|
SW_SHOWDEFAULT = 10
|
||||||
SW_SHOWMINIMIZED = 2
|
SW_SHOWMINIMIZED = 2
|
||||||
SW_SHOWMAXIMIZED = 3
|
SW_SHOWMAXIMIZED = 3
|
||||||
@@ -170,44 +187,46 @@ const (
|
|||||||
|
|
||||||
UNICODE_NOCHAR = 65535
|
UNICODE_NOCHAR = 65535
|
||||||
|
|
||||||
WM_CANCELMODE = 0x001F
|
WM_CANCELMODE = 0x001F
|
||||||
WM_CHAR = 0x0102
|
WM_CHAR = 0x0102
|
||||||
WM_CREATE = 0x0001
|
WM_CLOSE = 0x0010
|
||||||
WM_DPICHANGED = 0x02E0
|
WM_CREATE = 0x0001
|
||||||
WM_DESTROY = 0x0002
|
WM_DPICHANGED = 0x02E0
|
||||||
WM_ERASEBKGND = 0x0014
|
WM_DESTROY = 0x0002
|
||||||
WM_KEYDOWN = 0x0100
|
WM_ERASEBKGND = 0x0014
|
||||||
WM_KEYUP = 0x0101
|
WM_GETMINMAXINFO = 0x0024
|
||||||
WM_LBUTTONDOWN = 0x0201
|
WM_IME_COMPOSITION = 0x010F
|
||||||
WM_LBUTTONUP = 0x0202
|
WM_IME_ENDCOMPOSITION = 0x010E
|
||||||
WM_MBUTTONDOWN = 0x0207
|
WM_KEYDOWN = 0x0100
|
||||||
WM_MBUTTONUP = 0x0208
|
WM_KEYUP = 0x0101
|
||||||
WM_MOUSEMOVE = 0x0200
|
WM_KILLFOCUS = 0x0008
|
||||||
WM_MOUSEWHEEL = 0x020A
|
WM_LBUTTONDOWN = 0x0201
|
||||||
WM_MOUSEHWHEEL = 0x020E
|
WM_LBUTTONUP = 0x0202
|
||||||
WM_PAINT = 0x000F
|
WM_MBUTTONDOWN = 0x0207
|
||||||
WM_CLOSE = 0x0010
|
WM_MBUTTONUP = 0x0208
|
||||||
WM_QUIT = 0x0012
|
WM_MOUSEMOVE = 0x0200
|
||||||
WM_SETCURSOR = 0x0020
|
WM_MOUSEWHEEL = 0x020A
|
||||||
WM_SETFOCUS = 0x0007
|
WM_MOUSEHWHEEL = 0x020E
|
||||||
WM_KILLFOCUS = 0x0008
|
WM_PAINT = 0x000F
|
||||||
WM_SHOWWINDOW = 0x0018
|
WM_QUIT = 0x0012
|
||||||
WM_SIZE = 0x0005
|
WM_SETCURSOR = 0x0020
|
||||||
WM_SYSKEYDOWN = 0x0104
|
WM_SETFOCUS = 0x0007
|
||||||
WM_SYSKEYUP = 0x0105
|
WM_SHOWWINDOW = 0x0018
|
||||||
WM_RBUTTONDOWN = 0x0204
|
WM_SIZE = 0x0005
|
||||||
WM_RBUTTONUP = 0x0205
|
WM_SYSKEYDOWN = 0x0104
|
||||||
WM_TIMER = 0x0113
|
WM_SYSKEYUP = 0x0105
|
||||||
WM_UNICHAR = 0x0109
|
WM_RBUTTONDOWN = 0x0204
|
||||||
WM_USER = 0x0400
|
WM_RBUTTONUP = 0x0205
|
||||||
WM_GETMINMAXINFO = 0x0024
|
WM_TIMER = 0x0113
|
||||||
WM_WINDOWPOSCHANGED = 0x0047
|
WM_UNICHAR = 0x0109
|
||||||
|
WM_USER = 0x0400
|
||||||
|
WM_WINDOWPOSCHANGED = 0x0047
|
||||||
|
|
||||||
WS_CLIPCHILDREN = 0x00010000
|
WS_CLIPCHILDREN = 0x00010000
|
||||||
WS_CLIPSIBLINGS = 0x04000000
|
WS_CLIPSIBLINGS = 0x04000000
|
||||||
WS_MAXIMIZE = 0x01000000
|
WS_MAXIMIZE = 0x01000000
|
||||||
WS_ICONIC = 0x20000000
|
WS_ICONIC = 0x20000000
|
||||||
WS_VISIBLE = 0x10000000
|
WS_VISIBLE = 0x10000000
|
||||||
|
|
||||||
WS_OVERLAPPED = 0x00000000
|
WS_OVERLAPPED = 0x00000000
|
||||||
WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME |
|
WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME |
|
||||||
WS_MINIMIZEBOX | WS_MAXIMIZEBOX
|
WS_MINIMIZEBOX | WS_MAXIMIZEBOX
|
||||||
@@ -313,6 +332,12 @@ var (
|
|||||||
|
|
||||||
gdi32 = syscall.NewLazySystemDLL("gdi32")
|
gdi32 = syscall.NewLazySystemDLL("gdi32")
|
||||||
_GetDeviceCaps = gdi32.NewProc("GetDeviceCaps")
|
_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) {
|
func AdjustWindowRectEx(r *Rect, dwStyle uint32, bMenu int, dwExStyle uint32) {
|
||||||
@@ -487,6 +512,34 @@ func GetWindowLong(hwnd syscall.Handle) (style uintptr) {
|
|||||||
return
|
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) {
|
func SetWindowLong(hwnd syscall.Handle, idx uint32, style uintptr) {
|
||||||
if runtime.GOARCH == "386" {
|
if runtime.GOARCH == "386" {
|
||||||
_SetWindowLong32.Call(uintptr(hwnd), uintptr(idx), style)
|
_SetWindowLong32.Call(uintptr(hwnd), uintptr(idx), style)
|
||||||
|
|||||||
+51
-1
@@ -13,6 +13,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
syscall "golang.org/x/sys/windows"
|
syscall "golang.org/x/sys/windows"
|
||||||
@@ -349,6 +350,48 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
|
|||||||
}
|
}
|
||||||
case _WM_WAKEUP:
|
case _WM_WAKEUP:
|
||||||
w.w.Event(wakeupEvent{})
|
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)
|
return windows.DefWindowProc(hwnd, msg, wParam, lParam)
|
||||||
@@ -451,7 +494,14 @@ loop:
|
|||||||
return nil
|
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) {
|
func (w *window) SetAnimating(anim bool) {
|
||||||
w.animating = anim
|
w.animating = anim
|
||||||
|
|||||||
Reference in New Issue
Block a user