mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-24 18:58:54 +01:00
internal/ui: disable IME and enable it only when necessary
Closes #2918
This commit is contained in:
parent
5f595efef0
commit
42209606b1
@ -15,6 +15,7 @@
|
||||
package textinput
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
@ -73,6 +74,7 @@ var (
|
||||
imm32 = windows.NewLazySystemDLL("imm32.dll")
|
||||
user32 = windows.NewLazySystemDLL("user32.dll")
|
||||
|
||||
procImmAssociateContext = imm32.NewProc("ImmAssociateContext")
|
||||
procImmGetCompositionStringW = imm32.NewProc("ImmGetCompositionStringW")
|
||||
procImmGetContext = imm32.NewProc("ImmGetContext")
|
||||
procImmReleaseContext = imm32.NewProc("ImmReleaseContext")
|
||||
@ -94,6 +96,14 @@ func _GetActiveWindow() windows.HWND {
|
||||
return windows.HWND(r)
|
||||
}
|
||||
|
||||
func _ImmAssociateContext(hwnd windows.HWND, hIMC uintptr) (uintptr, error) {
|
||||
r, _, e := procImmAssociateContext.Call(uintptr(hwnd), hIMC)
|
||||
if e != nil && !errors.Is(e, windows.ERROR_SUCCESS) {
|
||||
return 0, fmt.Errorf("textinput: ImmAssociateContext failed: error code: %w", e)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func _ImmGetCompositionStringW(unnamedParam1 _HIMC, unnamedParam2 uint32, lpBuf unsafe.Pointer, dwBufLen uint32) (uint32, error) {
|
||||
r, _, e := procImmGetCompositionStringW.Call(uintptr(unnamedParam1), uintptr(unnamedParam2), uintptr(lpBuf), uintptr(dwBufLen))
|
||||
runtime.KeepAlive(lpBuf)
|
||||
|
@ -15,6 +15,7 @@
|
||||
package textinput
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
@ -29,8 +30,13 @@ type textInput struct {
|
||||
origWndProc uintptr
|
||||
wndProcCallback uintptr
|
||||
window windows.HWND
|
||||
immContext uintptr
|
||||
|
||||
highSurrogate uint16
|
||||
|
||||
initOnce sync.Once
|
||||
|
||||
err error
|
||||
}
|
||||
|
||||
var theTextInput textInput
|
||||
@ -52,11 +58,29 @@ func (t *textInput) Start(x, y int) (chan State, func()) {
|
||||
session.ch <- State{Error: err}
|
||||
session.end()
|
||||
}
|
||||
return session.ch, session.end
|
||||
return session.ch, func() {
|
||||
ui.Get().RunOnMainThread(func() {
|
||||
// Disable IME again.
|
||||
if t.immContext != 0 {
|
||||
return
|
||||
}
|
||||
c, err := _ImmAssociateContext(t.window, 0)
|
||||
if err != nil {
|
||||
t.err = err
|
||||
return
|
||||
}
|
||||
t.immContext = c
|
||||
t.end()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// start must be called from the main thread.
|
||||
func (t *textInput) start(x, y int) error {
|
||||
if t.err != nil {
|
||||
return t.err
|
||||
}
|
||||
|
||||
if t.window == 0 {
|
||||
t.window = _GetActiveWindow()
|
||||
}
|
||||
@ -72,6 +96,22 @@ func (t *textInput) start(x, y int) error {
|
||||
t.origWndProc = h
|
||||
}
|
||||
|
||||
// By default, IME was disabled by setting 0 as the IMM context.
|
||||
// Restore the context once.
|
||||
var err error
|
||||
t.initOnce.Do(func() {
|
||||
err = ui.Get().RestoreIMMContextOnMainThread()
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if t.immContext != 0 {
|
||||
if _, err := _ImmAssociateContext(t.window, t.immContext); err != nil {
|
||||
return err
|
||||
}
|
||||
t.immContext = 0
|
||||
}
|
||||
h := _ImmGetContext(t.window)
|
||||
if err := _ImmSetCandidateWindow(h, &_CANDIDATEFORM{
|
||||
dwIndex: 0,
|
||||
@ -168,14 +208,20 @@ func (t *textInput) send(text string, startInBytes, endInBytes int, committed bo
|
||||
|
||||
// end must be called from the main thread.
|
||||
func (t *textInput) end() {
|
||||
if t.session != nil {
|
||||
t.session.end()
|
||||
t.session = nil
|
||||
if t.session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.session.end()
|
||||
t.session = nil
|
||||
}
|
||||
|
||||
// update must be called from the main thread.
|
||||
func (t *textInput) update() (ferr error) {
|
||||
if t.err != nil {
|
||||
return t.err
|
||||
}
|
||||
|
||||
hIMC := _ImmGetContext(t.window)
|
||||
defer func() {
|
||||
if err := _ImmReleaseContext(t.window, hIMC); err != nil && ferr != nil {
|
||||
@ -236,6 +282,10 @@ func (t *textInput) update() (ferr error) {
|
||||
|
||||
// commit must be called from the main thread.
|
||||
func (t *textInput) commit() (ferr error) {
|
||||
if t.err != nil {
|
||||
return t.err
|
||||
}
|
||||
|
||||
hIMC := _ImmGetContext(t.window)
|
||||
defer func() {
|
||||
if err := _ImmReleaseContext(t.window, hIMC); err != nil && ferr != nil {
|
||||
|
@ -68,9 +68,12 @@ type _POINT struct {
|
||||
}
|
||||
|
||||
var (
|
||||
imm32 = windows.NewLazySystemDLL("imm32.dll")
|
||||
ole32 = windows.NewLazySystemDLL("ole32.dll")
|
||||
user32 = windows.NewLazySystemDLL("user32.dll")
|
||||
|
||||
procImmAssociateContext = imm32.NewProc("ImmAssociateContext")
|
||||
|
||||
procCoCreateInstance = ole32.NewProc("CoCreateInstance")
|
||||
|
||||
procGetSystemMetrics = user32.NewProc("GetSystemMetrics")
|
||||
@ -79,6 +82,14 @@ var (
|
||||
procGetCursorPos = user32.NewProc("GetCursorPos")
|
||||
)
|
||||
|
||||
func _ImmAssociateContext(hwnd windows.HWND, hIMC uintptr) (uintptr, error) {
|
||||
r, _, e := procImmAssociateContext.Call(uintptr(hwnd), hIMC)
|
||||
if e != nil && !errors.Is(e, windows.ERROR_SUCCESS) {
|
||||
return 0, fmt.Errorf("ui: ImmAssociateContext failed: error code: %w", e)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func _CoCreateInstance(rclsid *windows.GUID, pUnkOuter unsafe.Pointer, dwClsContext uint32, riid *windows.GUID) (unsafe.Pointer, error) {
|
||||
var ptr unsafe.Pointer
|
||||
r, _, _ := procCoCreateInstance.Call(uintptr(unsafe.Pointer(rclsid)), uintptr(pUnkOuter), uintptr(dwClsContext), uintptr(unsafe.Pointer(riid)), uintptr(unsafe.Pointer(&ptr)))
|
||||
|
@ -444,3 +444,7 @@ func (u *UserInterface) setDocumentEdited(edited bool) error {
|
||||
objc.ID(w).Send(sel_setDocumentEdited, edited)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) afterWindowCreation() error {
|
||||
return nil
|
||||
}
|
||||
|
@ -106,6 +106,9 @@ type userInterfaceImpl struct {
|
||||
showWindowOnce sync.Once
|
||||
bufferOnceSwappedOnce sync.Once
|
||||
|
||||
// immContext is used only in Windows.
|
||||
immContext uintptr
|
||||
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
@ -897,6 +900,10 @@ func (u *UserInterface) createWindow() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := u.afterWindowCreation(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -204,3 +204,7 @@ func (u *UserInterface) skipTaskbar() error {
|
||||
func (u *UserInterface) setDocumentEdited(edited bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) afterWindowCreation() error {
|
||||
return nil
|
||||
}
|
||||
|
@ -246,6 +246,38 @@ func (u *UserInterface) setDocumentEdited(edited bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) afterWindowCreation() error {
|
||||
if microsoftgdk.IsXbox() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// By default, IME should be disabled (#2918).
|
||||
w, err := u.window.GetWin32Window()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := _ImmAssociateContext(w, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.immContext = c
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreIMMContextOnMainThread is called from the main thread.
|
||||
// The textinput package invokes RestoreIMMContextOnMainThread to enable IME inputting.
|
||||
func (u *UserInterface) RestoreIMMContextOnMainThread() error {
|
||||
w, err := u.window.GetWin32Window()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := _ImmAssociateContext(w, u.immContext); err != nil {
|
||||
return err
|
||||
}
|
||||
u.immContext = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
if microsoftgdk.IsXbox() {
|
||||
// TimeBeginPeriod might not be defined in Xbox.
|
||||
|
Loading…
Reference in New Issue
Block a user