1
0

textinput_windows.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. // Copyright 2024 The Ebitengine Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package textinput
  15. import (
  16. "sync"
  17. "unsafe"
  18. "golang.org/x/sys/windows"
  19. "github.com/hajimehoshi/ebiten/v2/internal/microsoftgdk"
  20. "github.com/hajimehoshi/ebiten/v2/internal/ui"
  21. )
  22. type textInput struct {
  23. session *session
  24. origWndProc uintptr
  25. wndProcCallback uintptr
  26. window windows.HWND
  27. immContext uintptr
  28. highSurrogate uint16
  29. initOnce sync.Once
  30. err error
  31. }
  32. var theTextInput textInput
  33. func (t *textInput) Start(x, y int) (chan State, func()) {
  34. if microsoftgdk.IsXbox() {
  35. return nil, nil
  36. }
  37. var session *session
  38. var err error
  39. ui.Get().RunOnMainThread(func() {
  40. t.end()
  41. err = t.start(x, y)
  42. session = newSession()
  43. t.session = session
  44. })
  45. if err != nil {
  46. session.ch <- State{Error: err}
  47. session.end()
  48. }
  49. return session.ch, func() {
  50. ui.Get().RunOnMainThread(func() {
  51. // Disable IME again.
  52. if t.immContext != 0 {
  53. return
  54. }
  55. c, err := _ImmAssociateContext(t.window, 0)
  56. if err != nil {
  57. t.err = err
  58. return
  59. }
  60. t.immContext = c
  61. t.end()
  62. })
  63. }
  64. }
  65. // start must be called from the main thread.
  66. func (t *textInput) start(x, y int) error {
  67. if t.err != nil {
  68. return t.err
  69. }
  70. if t.window == 0 {
  71. t.window = _GetActiveWindow()
  72. }
  73. if t.origWndProc == 0 {
  74. if t.wndProcCallback == 0 {
  75. t.wndProcCallback = windows.NewCallback(t.wndProc)
  76. }
  77. // Note that a Win32API GetActiveWindow doesn't work on Xbox.
  78. h, err := _SetWindowLongPtrW(t.window, _GWL_WNDPROC, t.wndProcCallback)
  79. if err != nil {
  80. return err
  81. }
  82. t.origWndProc = h
  83. }
  84. // By default, IME was disabled by setting 0 as the IMM context.
  85. // Restore the context once.
  86. var err error
  87. t.initOnce.Do(func() {
  88. err = ui.Get().RestoreIMMContextOnMainThread()
  89. })
  90. if err != nil {
  91. return err
  92. }
  93. if t.immContext != 0 {
  94. if _, err := _ImmAssociateContext(t.window, t.immContext); err != nil {
  95. return err
  96. }
  97. t.immContext = 0
  98. }
  99. h := _ImmGetContext(t.window)
  100. if err := _ImmSetCandidateWindow(h, &_CANDIDATEFORM{
  101. dwIndex: 0,
  102. dwStyle: _CFS_CANDIDATEPOS,
  103. ptCurrentPos: _POINT{
  104. x: int32(x),
  105. y: int32(y),
  106. },
  107. }); err != nil {
  108. return err
  109. }
  110. if err := _ImmReleaseContext(t.window, h); err != nil {
  111. return err
  112. }
  113. return nil
  114. }
  115. func (t *textInput) wndProc(hWnd uintptr, uMsg uint32, wParam, lParam uintptr) uintptr {
  116. if t.session == nil {
  117. return _CallWindowProcW(t.origWndProc, hWnd, uMsg, wParam, lParam)
  118. }
  119. switch uMsg {
  120. case _WM_IME_SETCONTEXT:
  121. // Draw preedit text by an application side.
  122. if lParam&_ISC_SHOWUICOMPOSITIONWINDOW != 0 {
  123. lParam &^= _ISC_SHOWUICOMPOSITIONWINDOW
  124. }
  125. case _WM_IME_COMPOSITION:
  126. if lParam&(_GCS_RESULTSTR|_GCS_COMPSTR) != 0 {
  127. if lParam&_GCS_RESULTSTR != 0 {
  128. if err := t.commit(); err != nil {
  129. t.session.ch <- State{Error: err}
  130. t.end()
  131. }
  132. }
  133. if lParam&_GCS_COMPSTR != 0 {
  134. if err := t.update(); err != nil {
  135. t.session.ch <- State{Error: err}
  136. t.end()
  137. }
  138. }
  139. return 1
  140. }
  141. case _WM_CHAR, _WM_SYSCHAR:
  142. if wParam >= 0xd800 && wParam <= 0xdbff {
  143. t.highSurrogate = uint16(wParam)
  144. } else {
  145. var c rune
  146. if wParam >= 0xdc00 && wParam <= 0xdfff {
  147. if t.highSurrogate != 0 {
  148. c += (rune(t.highSurrogate) - 0xd800) << 10
  149. c += (rune(wParam) & 0xffff) - 0xdc00
  150. c += 0x10000
  151. }
  152. } else {
  153. c = rune(wParam) & 0xffff
  154. }
  155. t.highSurrogate = 0
  156. if c >= 0x20 {
  157. str := string(c)
  158. t.send(str, 0, len(str), true)
  159. }
  160. }
  161. case _WM_UNICHAR:
  162. if wParam == _UNICODE_NOCHAR {
  163. // WM_UNICHAR is not sent by Windows, but is sent by some third-party input method engine.
  164. // Returning TRUE here announces support for this message.
  165. return 1
  166. }
  167. if r := rune(wParam); r >= 0x20 {
  168. str := string(r)
  169. t.send(str, 0, len(str), true)
  170. }
  171. }
  172. return _CallWindowProcW(t.origWndProc, hWnd, uMsg, wParam, lParam)
  173. }
  174. // send must be called from the main thread.
  175. func (t *textInput) send(text string, startInBytes, endInBytes int, committed bool) {
  176. if t.session != nil {
  177. t.session.trySend(State{
  178. Text: text,
  179. CompositionSelectionStartInBytes: startInBytes,
  180. CompositionSelectionEndInBytes: endInBytes,
  181. Committed: committed,
  182. })
  183. }
  184. if committed {
  185. t.end()
  186. }
  187. }
  188. // end must be called from the main thread.
  189. func (t *textInput) end() {
  190. if t.session == nil {
  191. return
  192. }
  193. t.session.end()
  194. t.session = nil
  195. }
  196. // update must be called from the main thread.
  197. func (t *textInput) update() (ferr error) {
  198. if t.err != nil {
  199. return t.err
  200. }
  201. hIMC := _ImmGetContext(t.window)
  202. defer func() {
  203. if err := _ImmReleaseContext(t.window, hIMC); err != nil && ferr != nil {
  204. ferr = err
  205. }
  206. }()
  207. bufferLen, err := _ImmGetCompositionStringW(hIMC, _GCS_COMPSTR, nil, 0)
  208. if err != nil {
  209. return err
  210. }
  211. if bufferLen == 0 {
  212. return nil
  213. }
  214. buffer16 := make([]uint16, bufferLen/uint32(unsafe.Sizeof(uint16(0))))
  215. if _, err := _ImmGetCompositionStringW(hIMC, _GCS_COMPSTR, unsafe.Pointer(&buffer16[0]), bufferLen); err != nil {
  216. return err
  217. }
  218. attrLen, err := _ImmGetCompositionStringW(hIMC, _GCS_COMPATTR, nil, 0)
  219. if err != nil {
  220. return err
  221. }
  222. attr := make([]byte, attrLen)
  223. if _, err := _ImmGetCompositionStringW(hIMC, _GCS_COMPATTR, unsafe.Pointer(&attr[0]), attrLen); err != nil {
  224. return err
  225. }
  226. clauseLen, err := _ImmGetCompositionStringW(hIMC, _GCS_COMPCLAUSE, nil, 0)
  227. if err != nil {
  228. return err
  229. }
  230. clause := make([]uint32, clauseLen/uint32(unsafe.Sizeof(uint32(0))))
  231. if _, err := _ImmGetCompositionStringW(hIMC, _GCS_COMPCLAUSE, unsafe.Pointer(&clause[0]), clauseLen); err != nil {
  232. return err
  233. }
  234. var start16 int
  235. var end16 int
  236. if len(clause) > 0 {
  237. for i, c := range clause[:len(clause)-1] {
  238. if int(c) == len(attr) {
  239. break
  240. }
  241. if attr[c] == _ATTR_TARGET_CONVERTED || attr[c] == _ATTR_TARGET_NOTCONVERTED {
  242. start16 = int(c)
  243. end16 = int(clause[i+1])
  244. break
  245. }
  246. }
  247. }
  248. text := windows.UTF16ToString(buffer16)
  249. t.send(text, convertUTF16CountToByteCount(text, start16), convertUTF16CountToByteCount(text, end16), false)
  250. return nil
  251. }
  252. // commit must be called from the main thread.
  253. func (t *textInput) commit() (ferr error) {
  254. if t.err != nil {
  255. return t.err
  256. }
  257. hIMC := _ImmGetContext(t.window)
  258. defer func() {
  259. if err := _ImmReleaseContext(t.window, hIMC); err != nil && ferr != nil {
  260. ferr = err
  261. }
  262. }()
  263. bufferLen, err := _ImmGetCompositionStringW(hIMC, _GCS_RESULTSTR, nil, 0)
  264. if err != nil {
  265. return err
  266. }
  267. if bufferLen == 0 {
  268. return nil
  269. }
  270. buffer16 := make([]uint16, bufferLen/uint32(unsafe.Sizeof(uint16(0))))
  271. if _, err := _ImmGetCompositionStringW(hIMC, _GCS_RESULTSTR, unsafe.Pointer(&buffer16[0]), bufferLen); err != nil {
  272. return err
  273. }
  274. text := windows.UTF16ToString(buffer16)
  275. t.send(text, 0, len(text), true)
  276. return nil
  277. }