Compare commits

..

3 Commits

Author SHA1 Message Date
Bertrand Jung
1dcd493033
Merge 1dd96726c4 into 42209606b1 2024-08-26 23:54:10 +05:00
Hajime Hoshi
42209606b1 internal/ui: disable IME and enable it only when necessary
Closes #2918
2024-08-27 02:22:30 +09:00
Hajime Hoshi
5f595efef0 examples/textinput: remove message
Updates #3072
2024-08-27 00:02:31 +09:00
8 changed files with 122 additions and 9 deletions

View File

@ -19,7 +19,6 @@ import (
"image/color"
"log"
"math"
"runtime"
"strings"
"unicode/utf8"
@ -324,10 +323,6 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
}
func main() {
if runtime.GOOS != "darwin" && runtime.GOOS != "js" {
log.Printf("github.com/hajimehoshi/ebiten/v2/exp/textinput is not supported in this environment (GOOS=%s) yet", runtime.GOOS)
}
ebiten.SetWindowSize(screenWidth, screenHeight)
ebiten.SetWindowTitle("Text Input (Ebitengine Demo)")
if err := ebiten.RunGame(&Game{}); err != nil {

View File

@ -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)

View File

@ -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 {

View File

@ -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)))

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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.