diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 1371aedb..f841e20c 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -75,6 +75,7 @@ video tutorials.
- Jason Francis
- Gerald Franz
- Mário Freitas
+ - Daijiro Fukuda
- GeO4d
- Marcus Geelnard
- Gegy
@@ -104,6 +105,7 @@ video tutorials.
- Charles Huber
- Brent Huisman
- Florian Hülsmann
+ - Ryo Ichinose
- illustris
- InKryption
- IntellectualKitty
@@ -122,6 +124,7 @@ video tutorials.
- Cameron King
- Peter Knut
- Christoph Kubisch
+ - Yasutaka Kumei
- Yuri Kunde Schlesner
- Rokas Kupstys
- Konstantin Käfer
@@ -285,6 +288,7 @@ video tutorials.
- Andy Williams
- Joel Winarske
- Richard A. Wilkes
+ - xfangfang
- Tatsuya Yatagawa
- Ryogo Yoshimura
- Lukas Zanner
diff --git a/README.md b/README.md
index 10044faf..ff71ea6d 100644
--- a/README.md
+++ b/README.md
@@ -132,6 +132,28 @@ information on what to include when reporting a bug.
- [Null] Added EGL context creation on Mesa via `EGL_MESA_platform_surfaceless`
- [EGL] Allowed native access on Wayland with `GLFW_CONTEXT_CREATION_API` set to
`GLFW_NATIVE_CONTEXT_API` (#2518)
+ - Added `glfwSetPreeditCallback` function and `GLFWpreeditfun` type for
+ preedit of input method (#2130)
+ - Added `glfwSetIMEStatusCallback` function and `GLFWimestatusfun` type for
+ status of input method (#2130)
+ - Added `glfwSetPreeditCursorRectangle` function to set the preedit cursor
+ area that is used to decide the position of the candidate window of input
+ method (#2130)
+ - Added `glfwGetPreeditCursorRectangle` function to get the preedit cursor
+ area (#2130)
+ - Added `glfwResetPreeditText` function to reset preedit of input method
+ (#2130)
+ - Added `glfwSetPreeditCandidateCallback` function and
+ `GLFWpreeditcandidatefun` type for preedit candidates (#2130)
+ - Added `glfwGetPreeditCandidate` function to get a preeidt candidate text
+ (#2130)
+ - Added `GLFW_IME` input mode for `glfwGetInputMode` and `glfwSetInputMode`
+ (#2130)
+ - Added `GLFW_X11_ONTHESPOT` init hint for using on-the-spot input method
+ style on X11 (#2130)
+ - Added `GLFW_MANAGE_PREEDIT_CANDIDATE` init hint for displaying preedit
+ candidates on the application side (supported only on Windows currently)
+ (#2130)
## Contact
diff --git a/deps/wayland/text-input-unstable-v1.xml b/deps/wayland/text-input-unstable-v1.xml
new file mode 100644
index 00000000..6ee26652
--- /dev/null
+++ b/deps/wayland/text-input-unstable-v1.xml
@@ -0,0 +1,385 @@
+
+
+
+
+ Copyright © 2012, 2013 Intel Corporation
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+
+
+
+
+ An object used for text input. Adds support for text input and input
+ methods to applications. A text_input object is created from a
+ wl_text_input_manager and corresponds typically to a text entry in an
+ application.
+
+ Requests are used to activate/deactivate the text_input object and set
+ state information like surrounding and selected text or the content type.
+ The information about entered text is sent to the text_input object via
+ the pre-edit and commit events. Using this interface removes the need
+ for applications to directly process hardware key events and compose text
+ out of them.
+
+ Text is generally UTF-8 encoded, indices and lengths are in bytes.
+
+ Serials are used to synchronize the state between the text input and
+ an input method. New serials are sent by the text input in the
+ commit_state request and are used by the input method to indicate
+ the known text input state in events like preedit_string, commit_string,
+ and keysym. The text input can then ignore events from the input method
+ which are based on an outdated state (for example after a reset).
+
+ Warning! The protocol described in this file is experimental and
+ backward incompatible changes may be made. Backward compatible changes
+ may be added together with the corresponding interface version bump.
+ Backward incompatible changes are done by bumping the version number in
+ the protocol and interface names and resetting the interface version.
+ Once the protocol is to be declared stable, the 'z' prefix and the
+ version number in the protocol and interface names are removed and the
+ interface version number is reset.
+
+
+
+
+ Requests the text_input object to be activated (typically when the
+ text entry gets focus).
+
+ The seat argument is a wl_seat which maintains the focus for this
+ activation. The surface argument is a wl_surface assigned to the
+ text_input object and tracked for focus lost. The enter event
+ is emitted on successful activation.
+
+
+
+
+
+
+
+ Requests the text_input object to be deactivated (typically when the
+ text entry lost focus). The seat argument is a wl_seat which was used
+ for activation.
+
+
+
+
+
+
+ Requests input panels (virtual keyboard) to show.
+
+
+
+
+
+ Requests input panels (virtual keyboard) to hide.
+
+
+
+
+
+ Should be called by an editor widget when the input state should be
+ reset, for example after the text was changed outside of the normal
+ input method flow.
+
+
+
+
+
+ Sets the plain surrounding text around the input position. Text is
+ UTF-8 encoded. Cursor is the byte offset within the
+ surrounding text. Anchor is the byte offset of the
+ selection anchor within the surrounding text. If there is no selected
+ text anchor, then it is the same as cursor.
+
+
+
+
+
+
+
+
+ Content hint is a bitmask to allow to modify the behavior of the text
+ input.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The content purpose allows to specify the primary purpose of a text
+ input.
+
+ This allows an input method to show special purpose input panels with
+ extra characters or to disallow some characters.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sets the content purpose and content hint. While the purpose is the
+ basic purpose of an input field, the hint flags allow to modify some
+ of the behavior.
+
+ When no content type is explicitly set, a normal content purpose with
+ default hints (auto completion, auto correction, auto capitalization)
+ should be assumed.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sets a specific language. This allows for example a virtual keyboard to
+ show a language specific layout. The "language" argument is an RFC-3066
+ format language tag.
+
+ It could be used for example in a word processor to indicate the
+ language of the currently edited document or in an instant message
+ application which tracks languages of contacts.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Notify the text_input object when it received focus. Typically in
+ response to an activate request.
+
+
+
+
+
+
+ Notify the text_input object when it lost focus. Either in response
+ to a deactivate request or when the assigned surface lost focus or was
+ destroyed.
+
+
+
+
+
+ Transfer an array of 0-terminated modifier names. The position in
+ the array is the index of the modifier as used in the modifiers
+ bitmask in the keysym event.
+
+
+
+
+
+
+ Notify when the visibility state of the input panel changed.
+
+
+
+
+
+
+ Notify when a new composing text (pre-edit) should be set around the
+ current cursor position. Any previously set composing text should
+ be removed.
+
+ The commit text can be used to replace the preedit text on reset
+ (for example on unfocus).
+
+ The text input should also handle all preedit_style and preedit_cursor
+ events occurring directly before preedit_string.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sets styling information on composing text. The style is applied for
+ length bytes from index relative to the beginning of the composing
+ text (as byte offset). Multiple styles can
+ be applied to a composing text by sending multiple preedit_styling
+ events.
+
+ This event is handled as part of a following preedit_string event.
+
+
+
+
+
+
+
+
+ Sets the cursor position inside the composing text (as byte
+ offset) relative to the start of the composing text. When index is a
+ negative number no cursor is shown.
+
+ This event is handled as part of a following preedit_string event.
+
+
+
+
+
+
+ Notify when text should be inserted into the editor widget. The text to
+ commit could be either just a single character after a key press or the
+ result of some composing (pre-edit). It could also be an empty text
+ when some text should be removed (see delete_surrounding_text) or when
+ the input cursor should be moved (see cursor_position).
+
+ Any previously set composing text should be removed.
+
+
+
+
+
+
+
+ Notify when the cursor or anchor position should be modified.
+
+ This event should be handled as part of a following commit_string
+ event.
+
+
+
+
+
+
+
+ Notify when the text around the current cursor position should be
+ deleted.
+
+ Index is relative to the current cursor (in bytes).
+ Length is the length of deleted text (in bytes).
+
+ This event should be handled as part of a following commit_string
+ event.
+
+
+
+
+
+
+
+ Notify when a key event was sent. Key events should not be used
+ for normal text input operations, which should be done with
+ commit_string, delete_surrounding_text, etc. The key event follows
+ the wl_keyboard key event convention. Sym is an XKB keysym, state a
+ wl_keyboard key_state. Modifiers are a mask for effective modifiers
+ (where the modifier indices are set by the modifiers_map event)
+
+
+
+
+
+
+
+
+
+
+ Sets the language of the input text. The "language" argument is an
+ RFC-3066 format language tag.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sets the text direction of input text.
+
+ It is mainly needed for showing an input cursor on the correct side of
+ the editor when there is no input done yet and making sure neutral
+ direction text is laid out properly.
+
+
+
+
+
+
+
+
+ A factory for text_input objects. This object is a global singleton.
+
+
+
+
+ Creates a new text_input object.
+
+
+
+
+
+
diff --git a/deps/wayland/text-input-unstable-v3.xml b/deps/wayland/text-input-unstable-v3.xml
new file mode 100644
index 00000000..1fae54d7
--- /dev/null
+++ b/deps/wayland/text-input-unstable-v3.xml
@@ -0,0 +1,457 @@
+
+
+
+
+ Copyright © 2012, 2013 Intel Corporation
+ Copyright © 2015, 2016 Jan Arne Petersen
+ Copyright © 2017, 2018 Red Hat, Inc.
+ Copyright © 2018 Purism SPC
+
+ Permission to use, copy, modify, distribute, and sell this
+ software and its documentation for any purpose is hereby granted
+ without fee, provided that the above copyright notice appear in
+ all copies and that both that copyright notice and this permission
+ notice appear in supporting documentation, and that the name of
+ the copyright holders not be used in advertising or publicity
+ pertaining to distribution of the software without specific,
+ written prior permission. The copyright holders make no
+ representations about the suitability of this software for any
+ purpose. It is provided "as is" without express or implied
+ warranty.
+
+ THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ THIS SOFTWARE.
+
+
+
+ This protocol allows compositors to act as input methods and to send text
+ to applications. A text input object is used to manage state of what are
+ typically text entry fields in the application.
+
+ This document adheres to the RFC 2119 when using words like "must",
+ "should", "may", etc.
+
+ Warning! The protocol described in this file is experimental and
+ backward incompatible changes may be made. Backward compatible changes
+ may be added together with the corresponding interface version bump.
+ Backward incompatible changes are done by bumping the version number in
+ the protocol and interface names and resetting the interface version.
+ Once the protocol is to be declared stable, the 'z' prefix and the
+ version number in the protocol and interface names are removed and the
+ interface version number is reset.
+
+
+
+
+ The zwp_text_input_v3 interface represents text input and input methods
+ associated with a seat. It provides enter/leave events to follow the
+ text input focus for a seat.
+
+ Requests are used to enable/disable the text-input object and set
+ state information like surrounding and selected text or the content type.
+ The information about the entered text is sent to the text-input object
+ via the preedit_string and commit_string events.
+
+ Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices
+ must not point to middle bytes inside a code point: they must either
+ point to the first byte of a code point or to the end of the buffer.
+ Lengths must be measured between two valid indices.
+
+ Focus moving throughout surfaces will result in the emission of
+ zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused
+ surface must commit zwp_text_input_v3.enable and
+ zwp_text_input_v3.disable requests as the keyboard focus moves across
+ editable and non-editable elements of the UI. Those two requests are not
+ expected to be paired with each other, the compositor must be able to
+ handle consecutive series of the same request.
+
+ State is sent by the state requests (set_surrounding_text,
+ set_content_type and set_cursor_rectangle) and a commit request. After an
+ enter event or disable request all state information is invalidated and
+ needs to be resent by the client.
+
+
+
+
+ Destroy the wp_text_input object. Also disables all surfaces enabled
+ through this wp_text_input object.
+
+
+
+
+
+ Requests text input on the surface previously obtained from the enter
+ event.
+
+ This request must be issued every time the active text input changes
+ to a new one, including within the current surface. Use
+ zwp_text_input_v3.disable when there is no longer any input focus on
+ the current surface.
+
+ Clients must not enable more than one text input on the single seat
+ and should disable the current text input before enabling the new one.
+ At most one instance of text input may be in enabled state per instance,
+ Requests to enable the another text input when some text input is active
+ must be ignored by compositor.
+
+ This request resets all state associated with previous enable, disable,
+ set_surrounding_text, set_text_change_cause, set_content_type, and
+ set_cursor_rectangle requests, as well as the state associated with
+ preedit_string, commit_string, and delete_surrounding_text events.
+
+ The set_surrounding_text, set_content_type and set_cursor_rectangle
+ requests must follow if the text input supports the necessary
+ functionality.
+
+ State set with this request is double-buffered. It will get applied on
+ the next zwp_text_input_v3.commit request, and stay valid until the
+ next committed enable or disable request.
+
+ The changes must be applied by the compositor after issuing a
+ zwp_text_input_v3.commit request.
+
+
+
+
+
+ Explicitly disable text input on the current surface (typically when
+ there is no focus on any text entry inside the surface).
+
+ State set with this request is double-buffered. It will get applied on
+ the next zwp_text_input_v3.commit request.
+
+
+
+
+
+ Sets the surrounding plain text around the input, excluding the preedit
+ text.
+
+ The client should notify the compositor of any changes in any of the
+ values carried with this request, including changes caused by handling
+ incoming text-input events as well as changes caused by other
+ mechanisms like keyboard typing.
+
+ If the client is unaware of the text around the cursor, it should not
+ issue this request, to signify lack of support to the compositor.
+
+ Text is UTF-8 encoded, and should include the cursor position, the
+ complete selection and additional characters before and after them.
+ There is a maximum length of wayland messages, so text can not be
+ longer than 4000 bytes.
+
+ Cursor is the byte offset of the cursor within text buffer.
+
+ Anchor is the byte offset of the selection anchor within text buffer.
+ If there is no selected text, anchor is the same as cursor.
+
+ If any preedit text is present, it is replaced with a cursor for the
+ purpose of this event.
+
+ Values set with this request are double-buffered. They will get applied
+ on the next zwp_text_input_v3.commit request, and stay valid until the
+ next committed enable or disable request.
+
+ The initial state for affected fields is empty, meaning that the text
+ input does not support sending surrounding text. If the empty values
+ get applied, subsequent attempts to change them may have no effect.
+
+
+
+
+
+
+
+
+ Reason for the change of surrounding text or cursor posision.
+
+
+
+
+
+
+
+ Tells the compositor why the text surrounding the cursor changed.
+
+ Whenever the client detects an external change in text, cursor, or
+ anchor posision, it must issue this request to the compositor. This
+ request is intended to give the input method a chance to update the
+ preedit text in an appropriate way, e.g. by removing it when the user
+ starts typing with a keyboard.
+
+ cause describes the source of the change.
+
+ The value set with this request is double-buffered. It must be applied
+ and reset to initial at the next zwp_text_input_v3.commit request.
+
+ The initial value of cause is input_method.
+
+
+
+
+
+
+ Content hint is a bitmask to allow to modify the behavior of the text
+ input.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The content purpose allows to specify the primary purpose of a text
+ input.
+
+ This allows an input method to show special purpose input panels with
+ extra characters or to disallow some characters.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sets the content purpose and content hint. While the purpose is the
+ basic purpose of an input field, the hint flags allow to modify some of
+ the behavior.
+
+ Values set with this request are double-buffered. They will get applied
+ on the next zwp_text_input_v3.commit request.
+ Subsequent attempts to update them may have no effect. The values
+ remain valid until the next committed enable or disable request.
+
+ The initial value for hint is none, and the initial value for purpose
+ is normal.
+
+
+
+
+
+
+
+ Marks an area around the cursor as a x, y, width, height rectangle in
+ surface local coordinates.
+
+ Allows the compositor to put a window with word suggestions near the
+ cursor, without obstructing the text being input.
+
+ If the client is unaware of the position of edited text, it should not
+ issue this request, to signify lack of support to the compositor.
+
+ Values set with this request are double-buffered. They will get applied
+ on the next zwp_text_input_v3.commit request, and stay valid until the
+ next committed enable or disable request.
+
+ The initial values describing a cursor rectangle are empty. That means
+ the text input does not support describing the cursor area. If the
+ empty values get applied, subsequent attempts to change them may have
+ no effect.
+
+
+
+
+
+
+
+
+
+ Atomically applies state changes recently sent to the compositor.
+
+ The commit request establishes and updates the state of the client, and
+ must be issued after any changes to apply them.
+
+ Text input state (enabled status, content purpose, content hint,
+ surrounding text and change cause, cursor rectangle) is conceptually
+ double-buffered within the context of a text input, i.e. between a
+ committed enable request and the following committed enable or disable
+ request.
+
+ Protocol requests modify the pending state, as opposed to the current
+ state in use by the input method. A commit request atomically applies
+ all pending state, replacing the current state. After commit, the new
+ pending state is as documented for each related request.
+
+ Requests are applied in the order of arrival.
+
+ Neither current nor pending state are modified unless noted otherwise.
+
+ The compositor must count the number of commit requests coming from
+ each zwp_text_input_v3 object and use the count as the serial in done
+ events.
+
+
+
+
+
+ Notification that this seat's text-input focus is on a certain surface.
+
+ If client has created multiple text input objects, compositor must send
+ this event to all of them.
+
+ When the seat has the keyboard capability the text-input focus follows
+ the keyboard focus. This event sets the current surface for the
+ text-input object.
+
+
+
+
+
+
+ Notification that this seat's text-input focus is no longer on a
+ certain surface. The client should reset any preedit string previously
+ set.
+
+ The leave notification clears the current surface. It is sent before
+ the enter notification for the new focus. After leave event, compositor
+ must ignore requests from any text input instances until next enter
+ event.
+
+ When the seat has the keyboard capability the text-input focus follows
+ the keyboard focus.
+
+
+
+
+
+
+ Notify when a new composing text (pre-edit) should be set at the
+ current cursor position. Any previously set composing text must be
+ removed. Any previously existing selected text must be removed.
+
+ The argument text contains the pre-edit string buffer.
+
+ The parameters cursor_begin and cursor_end are counted in bytes
+ relative to the beginning of the submitted text buffer. Cursor should
+ be hidden when both are equal to -1.
+
+ They could be represented by the client as a line if both values are
+ the same, or as a text highlight otherwise.
+
+ Values set with this event are double-buffered. They must be applied
+ and reset to initial on the next zwp_text_input_v3.done event.
+
+ The initial value of text is an empty string, and cursor_begin,
+ cursor_end and cursor_hidden are all 0.
+
+
+
+
+
+
+
+
+ Notify when text should be inserted into the editor widget. The text to
+ commit could be either just a single character after a key press or the
+ result of some composing (pre-edit).
+
+ Values set with this event are double-buffered. They must be applied
+ and reset to initial on the next zwp_text_input_v3.done event.
+
+ The initial value of text is an empty string.
+
+
+
+
+
+
+ Notify when the text around the current cursor position should be
+ deleted.
+
+ Before_length and after_length are the number of bytes before and after
+ the current cursor index (excluding the selection) to delete.
+
+ If a preedit text is present, in effect before_length is counted from
+ the beginning of it, and after_length from its end (see done event
+ sequence).
+
+ Values set with this event are double-buffered. They must be applied
+ and reset to initial on the next zwp_text_input_v3.done event.
+
+ The initial values of both before_length and after_length are 0.
+
+
+
+
+
+
+
+ Instruct the application to apply changes to state requested by the
+ preedit_string, commit_string and delete_surrounding_text events. The
+ state relating to these events is double-buffered, and each one
+ modifies the pending state. This event replaces the current state with
+ the pending state.
+
+ The application must proceed by evaluating the changes in the following
+ order:
+
+ 1. Replace existing preedit string with the cursor.
+ 2. Delete requested surrounding text.
+ 3. Insert commit string with the cursor at its end.
+ 4. Calculate surrounding text to send.
+ 5. Insert new preedit text in cursor position.
+ 6. Place cursor inside preedit text.
+
+ The serial number reflects the last state of the zwp_text_input_v3
+ object known to the compositor. The value of the serial argument must
+ be equal to the number of commit requests already issued on that object.
+
+ When the client receives a done event with a serial different than the
+ number of past commit requests, it must proceed with evaluating and
+ applying the changes as normal, except it should not change the current
+ state of the zwp_text_input_v3 object. All pending state requests
+ (set_surrounding_text, set_content_type and set_cursor_rectangle) on
+ the zwp_text_input_v3 object should be sent and committed after
+ receiving a zwp_text_input_v3.done event with a matching serial.
+
+
+
+
+
+
+
+ A factory for text-input objects. This object is a global singleton.
+
+
+
+
+ Destroy the wp_text_input_manager object.
+
+
+
+
+
+ Creates a new text-input object for a given seat.
+
+
+
+
+
+
diff --git a/docs/input.md b/docs/input.md
index 3ef1aebe..a502ffa9 100644
--- a/docs/input.md
+++ b/docs/input.md
@@ -246,6 +246,248 @@ ignored. This matches the behavior of the key callback, meaning the callback
arguments can always be passed unmodified to this function.
+@section ime_support IME support
+
+IME (Input Method Editor/Engine) is used to input characters not mapped with
+physical keys. It is popular among East Asian people.
+
+
+@subsection ime_style IME styles
+
+GLFW supports the following two styles of IME.
+
+ - On-the-spot
+ - Over-the-spot
+
+On-the-spot style is supported on Windows, macOS and Wayland. On these platforms,
+applications need to draw preedit text directly in their UI by using the preedit
+callback (See [Preedit input](@ref input_preedit)).
+
+Over-the-spot style is supported on X11. On this platform, the IME displays preedit
+text, and applications don't need to draw it. So the preedit callback doesn't work
+on X11.
+
+In both styles, applications should manage the position of the candidate window.
+See [Candidate window](@ref candidate_window) for details.
+
+@note
+@x11 You can use on-the-spot style also on X11 by using @ref GLFW_X11_ONTHESPOT_hint.
+In this case, the preedit callback also works on X11. However, on-the-spot style on
+X11 is unstable, so it is not recommended.
+
+
+@subsection input_preedit Preedit input
+
+When inputting text with IME, the text is temporarily inputted, then conversion
+and other processing are performed and finally committed. The committed text is
+inputted in the same way as input without IME (See [Text input](@ref input_char)).
+
+This temporary input is called "preedit" or "pre-edit".
+
+On Windows, macOS and Wayland, that use on-the-spot sytle, applications need to
+take preedit information and draw it in their UI.
+
+You can register the preedit callback as follows.
+
+@code
+glfwSetPreeditCallback(window, preedit_callback);
+@endcode
+
+The callback receives the following information.
+
+@code
+void preedit_callback(GLFWwindow* window,
+ int preedit_count,
+ unsigned int* preedit_string,
+ int block_count,
+ int* block_sizes,
+ int focused_block,
+ int caret)
+{
+}
+@endcode
+
+"preedit_count" and "preedit_string" parameter represent the whole preedit text.
+Each character of the preedit string is a native endian UTF-32 like @ref input_char.
+
+If you want to type the text "寿司(sushi)", Usually the callback is called several
+times like the following sequence:
+
+-# key event: s
+-# preedit: [preedit_string: "s", block_sizes: [1], focused_block: 0]
+-# key event: u
+-# preedit: [preedit_string: "す", block_sizes: [1], focused_block: 0]
+-# key event: s
+-# preedit: [preedit_string: "すs", block_sizes: [2], focused_block: 0]
+-# key event: h
+-# preedit: [preedit_string: "すsh", block_sizes: [3], focused_block: 0]
+-# key event: i
+-# preedit: [preedit_string: "すし", block_sizes: [2], focused_block: 0]
+-# key event: ' '
+-# preedit: [preedit_string: "寿司", block_sizes: [2], focused_block: 0]
+-# char: '寿'
+-# char: '司'
+-# preedit: [preedit_string: "", block_sizes: [], focused_block: 0]
+
+If preedit text includes several semantic blocks, the callback returns several blocks:
+
+-# preedit: [preedit_string: "わたしはすしをたべます", block_sizes: [11], focused_block: 0]
+-# preedit: [preedit_string: "私は寿司を食べます", block_sizes: [2, 7], focused_block: 1]
+
+"block_sizes" is a list of the sizes of each block. The above case, it contains the following
+blocks and the second block is focused.
+
+- 私は
+- [寿司を食べます]
+
+The application side should draw a focused block and unfocused blocks
+in different styles.
+
+You can use the "caret" parameter to draw the caret of the preedit text.
+The specification of this parameter depends on the specification of the input method.
+The following is an example on Win32.
+
+- "あいうえお|" (caret: 5)
+- key event: arrow-left
+- "あいうえ|お" (caret: 4)
+- ...
+- "|あいうえお" (caret: 0)
+
+
+@subsection candidate_window Candidate window
+
+The application has to manage the position of the candidate window that shows
+the preedit candidate list. To do this, the application has to manage the area
+of the preedit text cursor by the following functions. The IME displays the
+candidate window in the appropriate position based on the area of the preedit
+text cursor.
+
+@code
+glfwSetPreeditCursorRectangle(window, x, y, w, h);
+glfwGetPreeditCursorRectangle(window, &x, &y, &w, &h);
+@endcode
+
+
+@subsection ime_status IME status
+
+Sometimes, IME task needs to be interrupted by a user or an application. There
+are several functions to support these situations.
+
+@note
+@x11 @wayland This feature is not supported.
+
+You can receive notification about IME status change(on/off) by using the following
+function:
+
+@code
+glfwSetIMEStatusCallback(window, imestatus_callback);
+@endcode
+
+The callback has a simple signature like this:
+
+@code
+void imestatus_callback(GLFWwindow* window)
+{
+}
+@endcode
+
+@anchor GLFW_IME
+You can get the current IME status by the following function:
+
+@code
+glfwGetInputMode(window, GLFW_IME);
+@endcode
+
+If you get GLFW_TRUE, it means the IME is on, and GLFW_FALSE means the IME is off.
+
+You can also change the IME status by the following function:
+
+@code
+glfwSetInputMode(window, GLFW_IME, GLFW_TRUE);
+glfwSetInputMode(window, GLFW_IME, GLFW_FALSE);
+@endcode
+
+You can use the following function to clear the current preedit.
+
+@code
+glfwResetPreeditText(window);
+@endcode
+
+
+@subsection manage_preedit_candidate Manage preedit candidate
+
+By default, the IME manages the drawing of the preedit candidates, but
+sometimes you need to do that on the application side for some reason. In such
+a case, you can use
+[GLFW_MANAGE_PREEDIT_CANDIDATE](@ref GLFW_MANAGE_PREEDIT_CANDIDATE_hint) init hint.
+By setting this to `GLFW_TRUE`, the IME stops managing the drawing of the
+candidates and the application needs to manage it by using the following
+functions.
+
+@note
+@win32 Only the OS currently supports this hint.
+
+You can register the candidate callback as follows.
+
+@code
+glfwSetPreeditCandidateCallback(window, candidate_callback);
+@endcode
+
+The callback receives the following information.
+
+@code
+void candidate_callback(GLFWwindow* window,
+ int candidates_count,
+ int selected_index,
+ int page_start,
+ int page_size)
+{
+}
+@endcode
+
+`candidates_count` is the number of total candidates. `selected_index` is the
+index of the currently selected candidate. Normally all candidates should not
+be displayed at once, but divided into pages. You can use `page_start` and
+`page_size` to manage the pages. `page_start` is the index of the first
+candidate on the current page. `page_size` is the number of the candidates on
+the current page.
+
+You can get the text of the candidate on the specific index as follows. Each
+character of the returned text is a native endian UTF-32.
+
+@code
+int text_count;
+unsigned int* text = glfwGetPreeditCandidate(window, index, &text_count);
+@endcode
+
+A sample code to get all candidate texts on the current page is as follows.
+
+@code
+void candidate_callback(GLFWwindow* window, int candidates_count,
+ int selected_index, int page_start, int page_size)
+{
+ int i, j;
+ for (i = 0; i < page_size; ++i)
+ {
+ int index = i + page_start;
+ int text_count;
+ unsigned int* text = glfwGetPreeditCandidate(window, index, &text_count);
+ if (index == selected_index)
+ printf("> ");
+ for (j = 0; j < text_count; ++j)
+ {
+ char encoded[5] = "";
+ encode_utf8(encoded, text[j]); // Some kind of encoding process
+ printf("%s", encoded);
+ }
+ printf("\n");
+ }
+}
+
+glfwSetPreeditCandidateCallback(window, candidate_callback);
+@endcode
+
+
## Mouse input {#input_mouse}
Mouse input comes in many forms, including mouse motion, button presses and
diff --git a/docs/intro.md b/docs/intro.md
index 7aa75e31..9004460e 100644
--- a/docs/intro.md
+++ b/docs/intro.md
@@ -118,6 +118,16 @@ The ANGLE platform type is specified via the `EGL_ANGLE_platform_angle`
extension. This extension is not used if this hint is
`GLFW_ANGLE_PLATFORM_TYPE_NONE`, which is the default value.
+@anchor GLFW_MANAGE_PREEDIT_CANDIDATE_hint
+__GLFW_MANAGE_PREEDIT_CANDIDATE__ specifies whether to manage the preedit
+candidates on the application side. Possible values are `GLFW_TRUE` and
+`GLFW_FALSE`. The default is `GLFW_FALSE` and there is no need to manage
+the candidates on the application side. When you need to do that on the
+application side for some reason, you can enable this hint. Please see
+@ref ime_support for more information about IME support.
+
+@win32 Only the OS currently supports this hint.
+
#### macOS specific init hints {#init_hints_osx}
@@ -152,18 +162,29 @@ __GLFW_X11_XCB_VULKAN_SURFACE__ specifies whether to prefer the
the `VK_KHR_xlib_surface` extension. Possible values are `GLFW_TRUE` and
`GLFW_FALSE`. This is ignored on other platforms.
+@anchor GLFW_X11_ONTHESPOT_hint
+__GLFW_X11_ONTHESPOT__ specifies whether to use on-the-spot input method style.
+On X11 platform, over-the-spot style is used if this hint is `GLFW_FALSE`,
+which is the default value. You can set `GLFW_TRUE` to use on-the-spot style
+as with other platforms. However, on-the-spot style on X11 is unstable, so
+it is recommended not to use this hint in normal cases. Possible values are
+`GLFW_TRUE` and `GLFW_FALSE`. This is ignored on other platforms. Please see
+@ref ime_support for more information about IME support.
+
#### Supported and default values {#init_hints_values}
-Initialization hint | Default value | Supported values
--------------------------------- | ------------------------------- | ----------------
-@ref GLFW_PLATFORM | `GLFW_ANY_PLATFORM` | `GLFW_ANY_PLATFORM`, `GLFW_PLATFORM_WIN32`, `GLFW_PLATFORM_COCOA`, `GLFW_PLATFORM_WAYLAND`, `GLFW_PLATFORM_X11` or `GLFW_PLATFORM_NULL`
-@ref GLFW_JOYSTICK_HAT_BUTTONS | `GLFW_TRUE` | `GLFW_TRUE` or `GLFW_FALSE`
-@ref GLFW_ANGLE_PLATFORM_TYPE | `GLFW_ANGLE_PLATFORM_TYPE_NONE` | `GLFW_ANGLE_PLATFORM_TYPE_NONE`, `GLFW_ANGLE_PLATFORM_TYPE_OPENGL`, `GLFW_ANGLE_PLATFORM_TYPE_OPENGLES`, `GLFW_ANGLE_PLATFORM_TYPE_D3D9`, `GLFW_ANGLE_PLATFORM_TYPE_D3D11`, `GLFW_ANGLE_PLATFORM_TYPE_VULKAN` or `GLFW_ANGLE_PLATFORM_TYPE_METAL`
-@ref GLFW_COCOA_CHDIR_RESOURCES | `GLFW_TRUE` | `GLFW_TRUE` or `GLFW_FALSE`
-@ref GLFW_COCOA_MENUBAR | `GLFW_TRUE` | `GLFW_TRUE` or `GLFW_FALSE`
-@ref GLFW_WAYLAND_LIBDECOR | `GLFW_WAYLAND_PREFER_LIBDECOR` | `GLFW_WAYLAND_PREFER_LIBDECOR` or `GLFW_WAYLAND_DISABLE_LIBDECOR`
-@ref GLFW_X11_XCB_VULKAN_SURFACE | `GLFW_TRUE` | `GLFW_TRUE` or `GLFW_FALSE`
+Initialization hint | Default value | Supported values
+---------------------------------- | ------------------------------- | ----------------
+@ref GLFW_PLATFORM | `GLFW_ANY_PLATFORM` | `GLFW_ANY_PLATFORM`, `GLFW_PLATFORM_WIN32`, `GLFW_PLATFORM_COCOA`, `GLFW_PLATFORM_WAYLAND`, `GLFW_PLATFORM_X11` or `GLFW_PLATFORM_NULL`
+@ref GLFW_JOYSTICK_HAT_BUTTONS | `GLFW_TRUE` | `GLFW_TRUE` or `GLFW_FALSE`
+@ref GLFW_ANGLE_PLATFORM_TYPE | `GLFW_ANGLE_PLATFORM_TYPE_NONE` | `GLFW_ANGLE_PLATFORM_TYPE_NONE`, `GLFW_ANGLE_PLATFORM_TYPE_OPENGL`, `GLFW_ANGLE_PLATFORM_TYPE_OPENGLES`, `GLFW_ANGLE_PLATFORM_TYPE_D3D9`, `GLFW_ANGLE_PLATFORM_TYPE_D3D11`, `GLFW_ANGLE_PLATFORM_TYPE_VULKAN` or `GLFW_ANGLE_PLATFORM_TYPE_METAL`
+@ref GLFW_MANAGE_PREEDIT_CANDIDATE | `GLFW_FALSE` | `GLFW_TRUE` or `GLFW_FALSE`
+@ref GLFW_COCOA_CHDIR_RESOURCES | `GLFW_TRUE` | `GLFW_TRUE` or `GLFW_FALSE`
+@ref GLFW_COCOA_MENUBAR | `GLFW_TRUE` | `GLFW_TRUE` or `GLFW_FALSE`
+@ref GLFW_WAYLAND_LIBDECOR | `GLFW_WAYLAND_PREFER_LIBDECOR` | `GLFW_WAYLAND_PREFER_LIBDECOR` or `GLFW_WAYLAND_DISABLE_LIBDECOR`
+@ref GLFW_X11_XCB_VULKAN_SURFACE | `GLFW_TRUE` | `GLFW_TRUE` or `GLFW_FALSE`
+@ref GLFW_X11_ONTHESPOT | `GLFW_FALSE` | `GLFW_TRUE` or `GLFW_FALSE`
### Runtime platform selection {#platform}
diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h
index 79b06288..e14c07bc 100644
--- a/include/GLFW/glfw3.h
+++ b/include/GLFW/glfw3.h
@@ -1155,6 +1155,7 @@ extern "C" {
#define GLFW_LOCK_KEY_MODS 0x00033004
#define GLFW_RAW_MOUSE_MOTION 0x00033005
#define GLFW_UNLIMITED_MOUSE_BUTTONS 0x00033006
+#define GLFW_IME 0x00033007
#define GLFW_CURSOR_NORMAL 0x00034001
#define GLFW_CURSOR_HIDDEN 0x00034002
@@ -1308,6 +1309,11 @@ extern "C" {
* Platform selection [init hint](@ref GLFW_PLATFORM).
*/
#define GLFW_PLATFORM 0x00050003
+/*! @brief Preedit candidate init hint.
+ *
+ * Preedit candidate [init hint](@ref GLFW_MANAGE_PREEDIT_CANDIDATE_hint).
+ */
+#define GLFW_MANAGE_PREEDIT_CANDIDATE 0x00050004
/*! @brief macOS specific init hint.
*
* macOS specific [init hint](@ref GLFW_COCOA_CHDIR_RESOURCES_hint).
@@ -1323,6 +1329,11 @@ extern "C" {
* X11 specific [init hint](@ref GLFW_X11_XCB_VULKAN_SURFACE_hint).
*/
#define GLFW_X11_XCB_VULKAN_SURFACE 0x00052001
+/*! @brief X11 specific init hint.
+ *
+ * X11 specific [init hint](@ref GLFW_X11_ONTHESPOT_hint).
+ */
+#define GLFW_X11_ONTHESPOT 0x00052002
/*! @brief Wayland specific init hint.
*
* Wayland specific [init hint](@ref GLFW_WAYLAND_LIBDECOR_hint).
@@ -1945,6 +1956,67 @@ typedef void (* GLFWcharfun)(GLFWwindow* window, unsigned int codepoint);
*/
typedef void (* GLFWcharmodsfun)(GLFWwindow* window, unsigned int codepoint, int mods);
+/*! @brief The function pointer type for preedit callbacks.
+ *
+ * This is the function pointer type for preedit callback functions.
+ *
+ * @param[in] window The window that received the event.
+ * @param[in] preedit_count Preedit string count.
+ * @param[in] preedit_string Preedit string.
+ * @param[in] block_count Attributed block count.
+ * @param[in] block_sizes List of attributed block size.
+ * @param[in] focused_block Focused block index.
+ * @param[in] caret Caret position.
+ *
+ * @sa @ref ime_support
+ * @sa glfwSetPreeditCallback
+ *
+ * @ingroup input
+ */
+typedef void (* GLFWpreeditfun)(GLFWwindow* window,
+ int preedit_count,
+ unsigned int* preedit_string,
+ int block_count,
+ int* block_sizes,
+ int focused_block,
+ int caret);
+
+/*! @brief The function pointer type for IME status change callbacks.
+ *
+ * This is the function pointer type for IME status change callback functions.
+ *
+ * @param[in] window The window that received the event.
+ *
+ * @sa @ref ime_support
+ * @sa glfwSetIMEStatusCallback
+ *
+ * @ingroup monitor
+ */
+typedef void (* GLFWimestatusfun)(GLFWwindow* window);
+
+/*! @brief The function pointer type for preedit candidate callbacks.
+ *
+ * This is the function pointer type for preedit candidate callback functions.
+ * Use @ref glfwGetPreeditCandidate to get the candidate text for a specific index.
+ *
+ * @param[in] window The window that received the event.
+ * @param[in] candidates_count Candidates count.
+ * @param[in] selected_index.Index of selected candidate.
+ * @param[in] page_start Start index of candidate currently displayed.
+ * @param[in] page_size Count of candidates currently displayed.
+ *
+ * @sa @ref ime_support
+ * @sa @ref glfwSetPreeditCandidateCallback
+ * @sa @ref glfwGetPreeditCandidate
+ *
+ * @ingroup input
+ */
+typedef void (* GLFWpreeditcandidatefun)(GLFWwindow* window,
+ int candidates_count,
+ int selected_index,
+ int page_start,
+ int page_size);
+
/*! @brief The function pointer type for path drop callbacks.
*
* This is the function pointer type for path drop callbacks. A path drop
@@ -4652,13 +4724,13 @@ GLFWAPI void glfwPostEmptyEvent(void);
*
* This function returns the value of an input option for the specified window.
* The mode must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS,
- * @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS or
- * @ref GLFW_RAW_MOUSE_MOTION.
+ * @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS,
+ * @ref GLFW_RAW_MOUSE_MOTION or @ref GLFW_IME.
*
* @param[in] window The window to query.
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
- * `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS` or
- * `GLFW_RAW_MOUSE_MOTION`.
+ * `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS`, `GLFW_RAW_MOUSE_MOTION`,
+ * or `GLFW_IME`.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_INVALID_ENUM.
@@ -4677,8 +4749,9 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
*
* This function sets an input mode option for the specified window. The mode
* must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS,
- * @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS
- * @ref GLFW_RAW_MOUSE_MOTION, or @ref GLFW_UNLIMITED_MOUSE_BUTTONS.
+ * @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS,
+ * @ref GLFW_RAW_MOUSE_MOTION, @ref GLFW_UNLIMITED_MOUSE_BUTTONS,
+ * @ref GLFW_IME.
*
* If the mode is `GLFW_CURSOR`, the value must be one of the following cursor
* modes:
@@ -4723,10 +4796,13 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
* callback, or `GLFW_FALSE` to limit the mouse buttons sent to the callback
* to the mouse button token values up to `GLFW_MOUSE_BUTTON_LAST`.
*
+ * If the mode is `GLFW_IME`, the value must be either `GLFW_TRUE` to turn on
+ * IME, or `GLFW_FALSE` to turn off it.
+ *
* @param[in] window The window whose input mode to set.
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
- * `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS` or
- * `GLFW_RAW_MOUSE_MOTION`.
+ * `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS`,
+ * `GLFW_RAW_MOUSE_MOTION` or `GLFW_IME`.
* @param[in] value The new value of the specified input mode.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
@@ -5156,6 +5232,100 @@ GLFWAPI void glfwDestroyCursor(GLFWcursor* cursor);
*/
GLFWAPI void glfwSetCursor(GLFWwindow* window, GLFWcursor* cursor);
+/*! @brief Retrieves the area of the preedit text cursor.
+ *
+ * This area is used to decide the position of the candidate window.
+ * The cursor position is relative to the window.
+ *
+ * @param[in] window The window to set the preedit text cursor for.
+ * @param[out] x The preedit text cursor x position (relative position from window coordinates).
+ * @param[out] y The preedit text cursor y position (relative position from window coordinates).
+ * @param[out] w The preedit text cursor width.
+ * @param[out] h The preedit text cursor height.
+ *
+ * @par Thread Safety
+ * This function may only be called from the main thread.
+ *
+ * @sa @ref ime_support
+ *
+ * @since Added in GLFW 3.X.
+ *
+ * @ingroup input
+ */
+GLFWAPI void glfwGetPreeditCursorRectangle(GLFWwindow* window, int* x, int* y, int* w, int* h);
+
+/*! @brief Sets the area of the preedit text cursor.
+ *
+ * This area is used to decide the position of the candidate window.
+ * The cursor position is relative to the window.
+ *
+ * @param[in] window The window to set the text cursor for.
+ * @param[in] x The preedit text cursor x position (relative position from window coordinates).
+ * @param[in] y The preedit text cursor y position (relative position from window coordinates).
+ * @param[in] w The preedit text cursor width.
+ * @param[in] h The preedit text cursor height.
+ *
+ * @par Thread Safety
+ * This function may only be called from the main thread.
+ *
+ * @sa @ref ime_support
+ *
+ * @since Added in GLFW 3.X.
+ *
+ * @ingroup input
+ */
+GLFWAPI void glfwSetPreeditCursorRectangle(GLFWwindow* window, int x, int y, int w, int h);
+
+/*! @brief Resets IME input status.
+ *
+ * This function resets IME's preedit text.
+ *
+ * @param[in] window The window.
+ *
+ * @remark @x11 Since over-the-spot style is used by default, you don't need
+ * to use this function.
+ *
+ * @remark @wayland This function is currently not supported.
+ *
+ * @par Thread Safety
+ * This function may only be called from the main thread.
+ *
+ * @sa @ref ime_support
+ *
+ * @since Added in GLFW 3.X.
+ *
+ * @ingroup input
+ */
+GLFWAPI void glfwResetPreeditText(GLFWwindow* window);
+
+/*! @brief Returns the preedit candidate.
+ *
+ * This function returns the text and the text-count of the preedit candidate.
+ *
+ * By default, the IME manages the preedit candidates, so there is no need to
+ * use this function. See @ref glfwSetPreeditCandidateCallback and
+ * [GLFW_MANAGE_PREEDIT_CANDIDATE](@ref GLFW_MANAGE_PREEDIT_CANDIDATE_hint) for details.
+ *
+ * @param[in] window The window.
+ * @param[in] index The index of the candidate.
+ * @param[out] textCount The text-count of the candidate.
+ * @return The text of the candidate as Unicode code points.
+ *
+ * @remark @macos @x11 @wayland Don't support this function.
+ *
+ * @par Thread Safety
+ * This function may only be called from the main thread.
+ *
+ * @sa @ref ime_support
+ * @sa @ref glfwSetPreeditCandidateCallback
+ * @sa [GLFW_MANAGE_PREEDIT_CANDIDATE](@ref GLFW_MANAGE_PREEDIT_CANDIDATE_hint)
+ *
+ * @since Added in GLFW 3.X.
+ *
+ * @ingroup input
+ */
+GLFWAPI unsigned int* glfwGetPreeditCandidate(GLFWwindow* window, int index, int* textCount);
+
/*! @brief Sets the key callback.
*
* This function sets the key callback of the specified window, which is called
@@ -5291,6 +5461,124 @@ GLFWAPI GLFWcharfun glfwSetCharCallback(GLFWwindow* window, GLFWcharfun callback
*/
GLFWAPI GLFWcharmodsfun glfwSetCharModsCallback(GLFWwindow* window, GLFWcharmodsfun callback);
+/*! @brief Sets the preedit callback.
+ *
+ * This function sets the preedit callback of the specified
+ * window, which is called when an IME is processing text before committed.
+ *
+ * Callback receives relative position of input cursor inside preedit text and
+ * attributed text blocks. This callback is used for on-the-spot text editing
+ * with IME.
+ *
+ * @param[in] window The window whose callback to set.
+ * @param[in] cbfun The new callback, or `NULL` to remove the currently set
+ * callback.
+ * @return The previously set callback, or `NULL` if no callback was set or an
+ * error occurred.
+ *
+ * @callback_signature
+ * @code
+ * void function_name(GLFWwindow* window,
+ int preedit_count,
+ unsigned int* preedit_string,
+ int block_count,
+ int* block_sizes,
+ int focused_block,
+ int caret)
+ * @endcode
+ * For more information about the callback parameters, see the
+ * [function pointer type](@ref GLFWpreeditfun).
+ *
+ * @remark @x11 Since over-the-spot style is used by default, you don't need
+ * to use this function.
+ *
+ * @par Thread Safety
+ * This function may only be called from the main thread.
+ *
+ * @sa @ref ime_support
+ *
+ * @since Added in GLFW 3.X
+ *
+ * @ingroup input
+ */
+GLFWAPI GLFWpreeditfun glfwSetPreeditCallback(GLFWwindow* window, GLFWpreeditfun cbfun);
+
+/*! @brief Sets the IME status change callback.
+ *
+ * This function sets the IME status callback of the specified
+ * window, which is called when an IME is switched on and off.
+ *
+ * @param[in] window The window whose callback to set.
+ * @param[in] cbfun The new callback, or `NULL` to remove the currently set
+ * callback.
+ * @return The previously set callback, or `NULL` if no callback was set or an
+ * error occurred.
+ *
+ * @callback_signature
+ * @code
+ * void function_name(GLFWwindow* window)
+ * @endcode
+ * For more information about the callback parameters, see the
+ * [function pointer type](@ref GLFWimestatusfun).
+ *
+ * @remark @x11 @wayland Don't support this function. The callback is not called.
+ *
+ * @par Thread Safety
+ * This function may only be called from the main thread.
+ *
+ * @sa @ref ime_support
+ *
+ * @since Added in GLFW 3.X
+ *
+ * @ingroup input
+ */
+GLFWAPI GLFWimestatusfun glfwSetIMEStatusCallback(GLFWwindow* window, GLFWimestatusfun cbfun);
+
+/*! @brief Sets the preedit candidate change callback.
+ *
+ * This function sets the preedit candidate callback of the specified
+ * window, which is called when the candidates are updated and can be used
+ * to display them by the application side.
+ *
+ * By default, this callback is not called because the IME displays the
+ * candidates and there is nothing to do on the application side. Only when
+ * the application side needs to use this to manage the displaying of
+ * IME candidates, you can set
+ * [GLFW_MANAGE_PREEDIT_CANDIDATE](@ref GLFW_MANAGE_PREEDIT_CANDIDATE_hint) init hint
+ * and stop the IME from managing it.
+ *
+ * @param[in] window The window whose callback to set.
+ * @param[in] cbfun The new callback, or `NULL` to remove the currently set
+ * callback.
+ * @return The previously set callback, or `NULL` if no callback was set or an
+ * error occurred.
+ *
+ * @callback_signature
+ * @code
+ * void function_name(GLFWwindow* window,
+ int candidates_count,
+ int selected_index,
+ int page_start,
+ int page_size)
+ * @endcode
+ * For more information about the callback parameters, see the
+ * [function pointer type](@ref GLFWpreeditcandidatefun).
+ *
+ * @remark @macos @x11 @wayland Don't support this function. The callback is
+ * not called.
+ *
+ * @par Thread Safety
+ * This function may only be called from the main thread.
+ *
+ * @sa @ref ime_support
+ * @sa [GLFW_MANAGE_PREEDIT_CANDIDATE](@ref GLFW_MANAGE_PREEDIT_CANDIDATE_hint)
+ *
+ * @since Added in GLFW 3.X
+ *
+ * @ingroup input
+ */
+GLFWAPI GLFWpreeditcandidatefun glfwSetPreeditCandidateCallback(GLFWwindow* window, GLFWpreeditcandidatefun cbfun);
+
/*! @brief Sets the mouse button callback.
*
* This function sets the mouse button callback of the specified window, which
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 463b898d..6b5f14b4 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -104,6 +104,8 @@ if (GLFW_BUILD_WAYLAND)
generate_wayland_protocol("fractional-scale-v1.xml")
generate_wayland_protocol("xdg-activation-v1.xml")
generate_wayland_protocol("xdg-decoration-unstable-v1.xml")
+ generate_wayland_protocol("text-input-unstable-v1.xml")
+ generate_wayland_protocol("text-input-unstable-v3.xml")
endif()
if (WIN32 AND GLFW_BUILD_SHARED_LIBRARY)
diff --git a/src/cocoa_init.m b/src/cocoa_init.m
index 15dc4ec4..869b4246 100644
--- a/src/cocoa_init.m
+++ b/src/cocoa_init.m
@@ -349,22 +349,70 @@ static GLFWbool initializeTIS(void)
return GLFW_FALSE;
}
+ CFStringRef* kCategoryKeyboardInputSource =
+ CFBundleGetDataPointerForName(_glfw.ns.tis.bundle,
+ CFSTR("kTISCategoryKeyboardInputSource"));
+ CFStringRef* kPropertyInputSourceCategory =
+ CFBundleGetDataPointerForName(_glfw.ns.tis.bundle,
+ CFSTR("kTISPropertyInputSourceCategory"));
+ CFStringRef* kPropertyInputSourceID =
+ CFBundleGetDataPointerForName(_glfw.ns.tis.bundle,
+ CFSTR("kTISPropertyInputSourceID"));
+ CFStringRef* kPropertyInputSourceIsSelectCapable =
+ CFBundleGetDataPointerForName(_glfw.ns.tis.bundle,
+ CFSTR("kTISPropertyInputSourceIsSelectCapable"));
+ CFStringRef* kPropertyInputSourceType =
+ CFBundleGetDataPointerForName(_glfw.ns.tis.bundle,
+ CFSTR("kTISPropertyInputSourceType"));
CFStringRef* kPropertyUnicodeKeyLayoutData =
CFBundleGetDataPointerForName(_glfw.ns.tis.bundle,
CFSTR("kTISPropertyUnicodeKeyLayoutData"));
+ CFStringRef* kTypeKeyboardInputMethodModeEnabled =
+ CFBundleGetDataPointerForName(_glfw.ns.tis.bundle,
+ CFSTR("kTISTypeKeyboardInputMethodModeEnabled"));
+ _glfw.ns.tis.CopyCurrentASCIICapableKeyboardInputSource =
+ CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle,
+ CFSTR("TISCopyCurrentASCIICapableKeyboardInputSource"));
+ _glfw.ns.tis.CopyCurrentKeyboardInputSource =
+ CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle,
+ CFSTR("TISCopyCurrentKeyboardInputSource"));
_glfw.ns.tis.CopyCurrentKeyboardLayoutInputSource =
CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle,
CFSTR("TISCopyCurrentKeyboardLayoutInputSource"));
+ _glfw.ns.tis.CopyInputSourceForLanguage =
+ CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle,
+ CFSTR("TISCopyInputSourceForLanguage"));
+ _glfw.ns.tis.CreateASCIICapableInputSourceList =
+ CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle,
+ CFSTR("TISCreateASCIICapableInputSourceList"));
+ _glfw.ns.tis.CreateInputSourceList =
+ CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle,
+ CFSTR("TISCreateInputSourceList"));
_glfw.ns.tis.GetInputSourceProperty =
CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle,
CFSTR("TISGetInputSourceProperty"));
+ _glfw.ns.tis.SelectInputSource =
+ CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle,
+ CFSTR("TISSelectInputSource"));
_glfw.ns.tis.GetKbdType =
CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle,
CFSTR("LMGetKbdType"));
- if (!kPropertyUnicodeKeyLayoutData ||
+ if (!kCategoryKeyboardInputSource||
+ !kPropertyInputSourceCategory ||
+ !kPropertyInputSourceID ||
+ !kPropertyInputSourceIsSelectCapable||
+ !kPropertyInputSourceType||
+ !kPropertyUnicodeKeyLayoutData ||
+ !kTypeKeyboardInputMethodModeEnabled ||
+ !TISCopyCurrentASCIICapableKeyboardInputSource ||
+ !TISCopyCurrentKeyboardInputSource ||
!TISCopyCurrentKeyboardLayoutInputSource ||
+ !TISCopyInputSourceForLanguage ||
+ !TISCreateASCIICapableInputSourceList ||
+ !TISCreateInputSourceList ||
!TISGetInputSourceProperty ||
+ !TISSelectInputSource ||
!LMGetKbdType)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
@@ -372,8 +420,20 @@ static GLFWbool initializeTIS(void)
return GLFW_FALSE;
}
+ _glfw.ns.tis.kCategoryKeyboardInputSource =
+ *kCategoryKeyboardInputSource;
+ _glfw.ns.tis.kPropertyInputSourceCategory =
+ *kPropertyInputSourceCategory;
+ _glfw.ns.tis.kPropertyInputSourceID =
+ *kPropertyInputSourceID;
+ _glfw.ns.tis.kPropertyInputSourceIsSelectCapable =
+ *kPropertyInputSourceIsSelectCapable;
+ _glfw.ns.tis.kPropertyInputSourceType =
+ *kPropertyInputSourceType;
_glfw.ns.tis.kPropertyUnicodeKeyLayoutData =
*kPropertyUnicodeKeyLayoutData;
+ _glfw.ns.tis.kTypeKeyboardInputMethodModeEnabled =
+ *kTypeKeyboardInputMethodModeEnabled;
return updateUnicodeData();
}
@@ -509,6 +569,10 @@ GLFWbool _glfwConnectCocoa(int platformID, _GLFWplatform* platform)
.getKeyScancode = _glfwGetKeyScancodeCocoa,
.setClipboardString = _glfwSetClipboardStringCocoa,
.getClipboardString = _glfwGetClipboardStringCocoa,
+ .updatePreeditCursorRectangle = _glfwUpdatePreeditCursorRectangleCocoa,
+ .resetPreeditText = _glfwResetPreeditTextCocoa,
+ .setIMEStatus = _glfwSetIMEStatusCocoa,
+ .getIMEStatus = _glfwGetIMEStatusCocoa,
.initJoysticks = _glfwInitJoysticksCocoa,
.terminateJoysticks = _glfwTerminateJoysticksCocoa,
.pollJoystick = _glfwPollJoystickCocoa,
diff --git a/src/cocoa_platform.h b/src/cocoa_platform.h
index 4d1d66ae..b2207324 100644
--- a/src/cocoa_platform.h
+++ b/src/cocoa_platform.h
@@ -109,11 +109,29 @@ typedef VkResult (APIENTRY *PFN_vkCreateMetalSurfaceEXT)(VkInstance,const VkMeta
#define GLFW_NSGL_LIBRARY_CONTEXT_STATE _GLFWlibraryNSGL nsgl;
// HIToolbox.framework pointer typedefs
+#define kTISCategoryKeyboardInputSource _glfw.ns.tis.kCategoryKeyboardInputSource
+#define kTISPropertyInputSourceCategory _glfw.ns.tis.kPropertyInputSourceCategory
+#define kTISPropertyInputSourceID _glfw.ns.tis.kPropertyInputSourceID
+#define kTISPropertyInputSourceIsSelectCapable _glfw.ns.tis.kPropertyInputSourceIsSelectCapable
+#define kTISPropertyInputSourceType _glfw.ns.tis.kPropertyInputSourceType
#define kTISPropertyUnicodeKeyLayoutData _glfw.ns.tis.kPropertyUnicodeKeyLayoutData
+#define kTISTypeKeyboardInputMethodModeEnabled _glfw.ns.tis.kTypeKeyboardInputMethodModeEnabled
+typedef TISInputSourceRef (*PFN_TISCopyCurrentASCIICapableKeyboardInputSource)(void);
+#define TISCopyCurrentASCIICapableKeyboardInputSource _glfw.ns.tis.CopyCurrentASCIICapableKeyboardInputSource
+typedef TISInputSourceRef (*PFN_TISCopyCurrentKeyboardInputSource)(void);
+#define TISCopyCurrentKeyboardInputSource _glfw.ns.tis.CopyCurrentKeyboardInputSource
typedef TISInputSourceRef (*PFN_TISCopyCurrentKeyboardLayoutInputSource)(void);
#define TISCopyCurrentKeyboardLayoutInputSource _glfw.ns.tis.CopyCurrentKeyboardLayoutInputSource
+typedef TISInputSourceRef (*PFN_TISCopyInputSourceForLanguage)(CFStringRef);
+#define TISCopyInputSourceForLanguage _glfw.ns.tis.CopyInputSourceForLanguage
+typedef CFArrayRef (*PFN_TISCreateASCIICapableInputSourceList)(void);
+#define TISCreateASCIICapableInputSourceList _glfw.ns.tis.CreateASCIICapableInputSourceList
+typedef CFArrayRef (*PEN_TISCreateInputSourceList)(CFDictionaryRef,Boolean);
+#define TISCreateInputSourceList _glfw.ns.tis.CreateInputSourceList
typedef void* (*PFN_TISGetInputSourceProperty)(TISInputSourceRef,CFStringRef);
#define TISGetInputSourceProperty _glfw.ns.tis.GetInputSourceProperty
+typedef OSStatus (*PFN_TISSelectInputSource)(TISInputSourceRef);
+#define TISSelectInputSource _glfw.ns.tis.SelectInputSource
typedef UInt8 (*PFN_LMGetKbdType)(void);
#define LMGetKbdType _glfw.ns.tis.GetKbdType
@@ -184,10 +202,22 @@ typedef struct _GLFWlibraryNS
struct {
CFBundleRef bundle;
+ PFN_TISCopyCurrentASCIICapableKeyboardInputSource CopyCurrentASCIICapableKeyboardInputSource;
+ PFN_TISCopyCurrentKeyboardInputSource CopyCurrentKeyboardInputSource;
PFN_TISCopyCurrentKeyboardLayoutInputSource CopyCurrentKeyboardLayoutInputSource;
+ PFN_TISCopyInputSourceForLanguage CopyInputSourceForLanguage;
+ PFN_TISCreateASCIICapableInputSourceList CreateASCIICapableInputSourceList;
+ PEN_TISCreateInputSourceList CreateInputSourceList;
PFN_TISGetInputSourceProperty GetInputSourceProperty;
+ PFN_TISSelectInputSource SelectInputSource;
PFN_LMGetKbdType GetKbdType;
+ CFStringRef kCategoryKeyboardInputSource;
+ CFStringRef kPropertyInputSourceCategory;
+ CFStringRef kPropertyInputSourceID;
+ CFStringRef kPropertyInputSourceIsSelectCapable;
+ CFStringRef kPropertyInputSourceType;
CFStringRef kPropertyUnicodeKeyLayoutData;
+ CFStringRef kTypeKeyboardInputMethodModeEnabled;
} tis;
} _GLFWlibraryNS;
@@ -268,6 +298,11 @@ void _glfwSetCursorCocoa(_GLFWwindow* window, _GLFWcursor* cursor);
void _glfwSetClipboardStringCocoa(const char* string);
const char* _glfwGetClipboardStringCocoa(void);
+void _glfwUpdatePreeditCursorRectangleCocoa(_GLFWwindow* window);
+void _glfwResetPreeditTextCocoa(_GLFWwindow* window);
+void _glfwSetIMEStatusCocoa(_GLFWwindow* window, int active);
+int _glfwGetIMEStatusCocoa(_GLFWwindow* window);
+
EGLenum _glfwGetEGLPlatformCocoa(EGLint** attribs);
EGLNativeDisplayType _glfwGetEGLNativeDisplayCocoa(void);
EGLNativeWindowType _glfwGetEGLNativeWindowCocoa(_GLFWwindow* window);
diff --git a/src/cocoa_window.m b/src/cocoa_window.m
index e69b5fe0..5bd2aae1 100644
--- a/src/cocoa_window.m
+++ b/src/cocoa_window.m
@@ -321,6 +321,11 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
}
}
+- (void)imeStatusChangeNotified:(NSNotification *)notification
+{
+ _glfwInputIMEStatus(window);
+}
+
@end
@@ -565,7 +570,8 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
const int key = translateKey([event keyCode]);
const int mods = translateFlags([event modifierFlags]);
- _glfwInputKey(window, key, [event keyCode], GLFW_PRESS, mods);
+ if (![self hasMarkedText])
+ _glfwInputKey(window, key, [event keyCode], GLFW_PRESS, mods);
[self interpretKeyEvents:@[event]];
}
@@ -658,7 +664,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
- (NSRange)markedRange
{
if ([markedText length] > 0)
- return NSMakeRange(0, [markedText length] - 1);
+ return NSMakeRange(0, [markedText length]);
else
return kEmptyRange;
}
@@ -677,11 +683,95 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string];
else
markedText = [[NSMutableAttributedString alloc] initWithString:string];
+
+ NSString* markedTextString = markedText.string;
+
+ NSUInteger textLen = [markedTextString length];
+ _GLFWpreedit* preedit = &window->preedit;
+ int textBufferCount = preedit->textBufferCount;
+ while (textBufferCount < textLen + 1)
+ textBufferCount = textBufferCount == 0 ? 1 : textBufferCount * 2;
+ if (textBufferCount != preedit->textBufferCount)
+ {
+ unsigned int* preeditText = _glfw_realloc(preedit->text,
+ sizeof(unsigned int) * textBufferCount);
+ if (preeditText == NULL)
+ return;
+ preedit->text = preeditText;
+ preedit->textBufferCount = textBufferCount;
+ }
+
+ // NSString handles text data in UTF16 by default, so we have to convert them
+ // to UTF32. Not only the encoding, but also the number of characters and
+ // the position of each block.
+ int currentBlockIndex = 0;
+ int currentBlockLength = 0;
+ int currentBlockLocation = 0;
+ int focusedBlockIndex = 0;
+ NSInteger preeditTextLength = 0;
+ NSRange range = NSMakeRange(0, textLen);
+ while (range.length)
+ {
+ uint32_t codepoint = 0;
+ NSRange currentBlockRange;
+ [markedText attributesAtIndex:range.location
+ effectiveRange:¤tBlockRange];
+
+ if (preedit->blockSizesBufferCount < 1 + currentBlockIndex)
+ {
+ int blockBufferCount = (preedit->blockSizesBufferCount == 0)
+ ? 1 : preedit->blockSizesBufferCount * 2;
+ int* blocks = _glfw_realloc(preedit->blockSizes,
+ sizeof(int) * blockBufferCount);
+ if (blocks == NULL)
+ return;
+ preedit->blockSizes = blocks;
+ preedit->blockSizesBufferCount = blockBufferCount;
+ }
+
+ if (currentBlockLocation != currentBlockRange.location)
+ {
+ currentBlockLocation = currentBlockRange.location;
+ preedit->blockSizes[currentBlockIndex++] = currentBlockLength;
+ currentBlockLength = 0;
+ if (selectedRange.location == currentBlockRange.location)
+ focusedBlockIndex = currentBlockIndex;
+ }
+
+ if ([markedTextString getBytes:&codepoint
+ maxLength:sizeof(codepoint)
+ usedLength:NULL
+ encoding:NSUTF32StringEncoding
+ options:0
+ range:range
+ remainingRange:&range])
+ {
+ if (codepoint >= 0xf700 && codepoint <= 0xf7ff)
+ continue;
+
+ preedit->text[preeditTextLength++] = codepoint;
+ currentBlockLength++;
+ }
+ }
+ preedit->blockSizes[currentBlockIndex] = currentBlockLength;
+ preedit->blockSizesCount = 1 + currentBlockIndex;
+ preedit->textCount = preeditTextLength;
+ preedit->text[preeditTextLength] = 0;
+ preedit->focusedBlockIndex = focusedBlockIndex;
+ // The caret is always at the last of preedit in macOS.
+ preedit->caretIndex = preeditTextLength;
+
+ _glfwInputPreedit(window);
}
- (void)unmarkText
{
[[markedText mutableString] setString:@""];
+ window->preedit.blockSizesCount = 0;
+ window->preedit.textCount = 0;
+ window->preedit.focusedBlockIndex = 0;
+ window->preedit.caretIndex = 0;
+ _glfwInputPreedit(window);
}
- (NSArray*)validAttributesForMarkedText
@@ -703,8 +793,19 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
- (NSRect)firstRectForCharacterRange:(NSRange)range
actualRange:(NSRangePointer)actualRange
{
- const NSRect frame = [window->ns.view frame];
- return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0);
+ int x = window->preedit.cursorPosX;
+ int y = window->preedit.cursorPosY;
+ int w = window->preedit.cursorWidth;
+ int h = window->preedit.cursorHeight;
+
+ const NSRect frame =
+ [window->ns.object contentRectForFrameRect:[window->ns.object frame]];
+
+ return NSMakeRect(frame.origin.x + x,
+ // The y-axis is upward on macOS, so this conversion is needed.
+ frame.origin.y + frame.size.height - y - h,
+ w,
+ h);
}
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
@@ -738,6 +839,8 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
_glfwInputChar(window, codepoint, mods, plain);
}
}
+
+ [self unmarkText];
}
- (void)doCommandBySelector:(SEL)selector
@@ -982,6 +1085,12 @@ GLFWbool _glfwCreateWindowCocoa(_GLFWwindow* window,
}
}
+ [[NSNotificationCenter defaultCenter]
+ addObserver:window->ns.delegate
+ selector:@selector(imeStatusChangeNotified:)
+ name:NSTextInputContextKeyboardSelectionDidChangeNotification
+ object:nil];
+
return GLFW_TRUE;
} // autoreleasepool
@@ -994,6 +1103,8 @@ void _glfwDestroyWindowCocoa(_GLFWwindow* window)
if (_glfw.ns.disabledCursorWindow == window)
_glfw.ns.disabledCursorWindow = NULL;
+ [[NSNotificationCenter defaultCenter] removeObserver:window->ns.delegate];
+
[window->ns.object orderOut:nil];
if (window->monitor)
@@ -1881,6 +1992,118 @@ const char* _glfwGetClipboardStringCocoa(void)
} // autoreleasepool
}
+void _glfwUpdatePreeditCursorRectangleCocoa(_GLFWwindow* window)
+{
+ // Do nothing. Instead, implement `firstRectForCharacterRange` callback
+ // to update the position.
+}
+
+void _glfwResetPreeditTextCocoa(_GLFWwindow* window)
+{
+ @autoreleasepool {
+
+ NSTextInputContext* context = [NSTextInputContext currentInputContext];
+ [context discardMarkedText];
+ [window->ns.view unmarkText];
+
+ } // autoreleasepool
+}
+
+void _glfwSetIMEStatusCocoa(_GLFWwindow* window, int active)
+{
+ @autoreleasepool {
+
+ if (active)
+ {
+ NSArray* locales = CFBridgingRelease(CFLocaleCopyPreferredLanguages());
+ // Select the most preferred locale.
+ CFStringRef locale = (__bridge CFStringRef) [locales firstObject];
+ if (locale)
+ {
+ TISInputSourceRef source = TISCopyInputSourceForLanguage(locale);
+ if (source)
+ {
+ CFStringRef sourceType = TISGetInputSourceProperty(source,
+ kTISPropertyInputSourceType);
+
+ if (sourceType != kTISTypeKeyboardInputMethodModeEnabled)
+ TISSelectInputSource(source);
+ else
+ {
+ // Some IMEs return a input-method that has input-method-modes for `TISCopyInputSourceForLanguage()`.
+ // We can't select these input-methods directly, but need to find
+ // a input-method-mode of the input-method.
+ // Example:
+ // - Input Method: com.apple.inputmethod.SCIM
+ // - Input Mode: com.apple.inputmethod.SCIM.ITABC
+ NSString* sourceID =
+ (__bridge NSString *) TISGetInputSourceProperty(source, kTISPropertyInputSourceID);
+ NSDictionary* properties = @{
+ (__bridge NSString *) kTISPropertyInputSourceCategory: (__bridge NSString *) kTISCategoryKeyboardInputSource,
+ (__bridge NSString *) kTISPropertyInputSourceIsSelectCapable: @YES,
+ };
+ NSArray* selectableSources =
+ CFBridgingRelease(TISCreateInputSourceList((__bridge CFDictionaryRef) properties, NO));
+ for (id sourceCandidate in selectableSources)
+ {
+ TISInputSourceRef sourceCandidateRef = (__bridge TISInputSourceRef) sourceCandidate;
+ NSString* sourceCandidateID =
+ (__bridge NSString *) TISGetInputSourceProperty(sourceCandidateRef, kTISPropertyInputSourceID);
+ if ([sourceCandidateID hasPrefix:sourceID])
+ {
+ TISSelectInputSource(sourceCandidateRef);
+ break;
+ }
+ }
+ }
+
+ CFRelease(source);
+ }
+ }
+ }
+ else
+ {
+ TISInputSourceRef source = TISCopyCurrentASCIICapableKeyboardInputSource();
+ TISSelectInputSource(source);
+ CFRelease(source);
+ }
+
+ // `NSTextInputContextKeyboardSelectionDidChangeNotification` is sometimes
+ // not called immediately after this, so call the callback here.
+ _glfwInputIMEStatus(window);
+
+ } // autoreleasepool
+}
+
+int _glfwGetIMEStatusCocoa(_GLFWwindow* window)
+{
+ @autoreleasepool {
+
+ NSArray* asciiInputSources =
+ CFBridgingRelease(TISCreateASCIICapableInputSourceList());
+
+ TISInputSourceRef currentSource = TISCopyCurrentKeyboardInputSource();
+ NSString* currentSourceID =
+ (__bridge NSString *) TISGetInputSourceProperty(currentSource,
+ kTISPropertyInputSourceID);
+ CFRelease(currentSource);
+
+ for (int i = 0; i < [asciiInputSources count]; i++)
+ {
+ TISInputSourceRef asciiSource =
+ (__bridge TISInputSourceRef) [asciiInputSources objectAtIndex:i];
+ NSString* asciiSourceID =
+ (__bridge NSString *) TISGetInputSourceProperty(asciiSource,
+ kTISPropertyInputSourceID);
+ if ([asciiSourceID compare:currentSourceID] == NSOrderedSame)
+ return GLFW_FALSE;
+ }
+
+ return GLFW_TRUE;
+
+ } // autoreleasepool
+}
+
EGLenum _glfwGetEGLPlatformCocoa(EGLint** attribs)
{
if (_glfw.egl.ANGLE_platform_angle)
diff --git a/src/init.c b/src/init.c
index dbd5a900..372f71a3 100644
--- a/src/init.c
+++ b/src/init.c
@@ -52,6 +52,7 @@ static _GLFWinitconfig _glfwInitHints =
.hatButtons = GLFW_TRUE,
.angleType = GLFW_ANGLE_PLATFORM_TYPE_NONE,
.platformID = GLFW_ANY_PLATFORM,
+ .managePreeditCandidate = GLFW_FALSE,
.vulkanLoader = NULL,
.ns =
{
@@ -61,6 +62,7 @@ static _GLFWinitconfig _glfwInitHints =
.x11 =
{
.xcbVulkanSurface = GLFW_TRUE,
+ .onTheSpotIMStyle = GLFW_FALSE
},
.wl =
{
@@ -175,6 +177,29 @@ size_t _glfwEncodeUTF8(char* s, uint32_t codepoint)
return count;
}
+// Decode a Unicode code point from a UTF-8 stream
+// Based on cutef8 by Jeff Bezanson (Public Domain)
+//
+uint32_t _glfwDecodeUTF8(const char** s)
+{
+ uint32_t codepoint = 0, count = 0;
+ static const uint32_t offsets[] =
+ {
+ 0x00000000u, 0x00003080u, 0x000e2080u,
+ 0x03c82080u, 0xfa082080u, 0x82082080u
+ };
+
+ do
+ {
+ codepoint = (codepoint << 6) + (unsigned char) **s;
+ (*s)++;
+ count++;
+ } while ((**s & 0xc0) == 0x80);
+
+ assert(count <= 6);
+ return codepoint - offsets[count - 1];
+}
+
// Splits and translates a text/uri-list into separate file paths
// NOTE: This function destroys the provided string
//
@@ -450,6 +475,9 @@ GLFWAPI void glfwInitHint(int hint, int value)
case GLFW_PLATFORM:
_glfwInitHints.platformID = value;
return;
+ case GLFW_MANAGE_PREEDIT_CANDIDATE:
+ _glfwInitHints.managePreeditCandidate = value;
+ return;
case GLFW_COCOA_CHDIR_RESOURCES:
_glfwInitHints.ns.chdir = value;
return;
@@ -459,6 +487,9 @@ GLFWAPI void glfwInitHint(int hint, int value)
case GLFW_X11_XCB_VULKAN_SURFACE:
_glfwInitHints.x11.xcbVulkanSurface = value;
return;
+ case GLFW_X11_ONTHESPOT:
+ _glfwInitHints.x11.onTheSpotIMStyle = value;
+ return;
case GLFW_WAYLAND_LIBDECOR:
_glfwInitHints.wl.libdecorMode = value;
return;
diff --git a/src/input.c b/src/input.c
index c619eefc..cd9d0da2 100644
--- a/src/input.c
+++ b/src/input.c
@@ -328,6 +328,48 @@ void _glfwInputChar(_GLFWwindow* window, uint32_t codepoint, int mods, GLFWbool
}
}
+// Notifies shared code of a preedit event
+//
+void _glfwInputPreedit(_GLFWwindow* window)
+{
+ if (window->callbacks.preedit)
+ {
+ _GLFWpreedit *preedit = &window->preedit;
+ window->callbacks.preedit((GLFWwindow*) window,
+ preedit->textCount,
+ preedit->text,
+ preedit->blockSizesCount,
+ preedit->blockSizes,
+ preedit->focusedBlockIndex,
+ preedit->caretIndex);
+ }
+}
+
+// Notifies shared code of a IME status event
+//
+void _glfwInputIMEStatus(_GLFWwindow* window)
+{
+ if (window->callbacks.imestatus)
+ {
+ window->callbacks.imestatus((GLFWwindow*) window);
+ }
+}
+
+// Notifies shared code of a preedit candidate event
+//
+void _glfwInputPreeditCandidate(_GLFWwindow* window)
+{
+ if (window->callbacks.preeditCandidate)
+ {
+ _GLFWpreedit* preedit = &window->preedit;
+ window->callbacks.preeditCandidate((GLFWwindow*) window,
+ preedit->candidateCount,
+ preedit->candidateSelection,
+ preedit->candidatePageStart,
+ preedit->candidatePageSize);
+ }
+}
+
// Notifies shared code of a scroll event
//
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset)
@@ -580,6 +622,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* handle, int mode)
return window->rawMouseMotion;
case GLFW_UNLIMITED_MOUSE_BUTTONS:
return window->disableMouseButtonLimit;
+ case GLFW_IME:
+ return _glfw.platform.getIMEStatus(window);
}
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode);
@@ -693,6 +737,12 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
window->disableMouseButtonLimit = value ? GLFW_TRUE : GLFW_FALSE;
return;
}
+
+ case GLFW_IME:
+ {
+ _glfw.platform.setIMEStatus(window, value ? GLFW_TRUE : GLFW_FALSE);
+ return;
+ }
}
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode);
@@ -953,6 +1003,62 @@ GLFWAPI void glfwSetCursor(GLFWwindow* windowHandle, GLFWcursor* cursorHandle)
_glfw.platform.setCursor(window, cursor);
}
+GLFWAPI void glfwGetPreeditCursorRectangle(GLFWwindow* handle, int* x, int* y, int* w, int* h)
+{
+ _GLFWwindow* window = (_GLFWwindow*) handle;
+ _GLFWpreedit* preedit = &window->preedit;
+ if (x)
+ *x = preedit->cursorPosX;
+ if (y)
+ *y = preedit->cursorPosY;
+ if (w)
+ *w = preedit->cursorWidth;
+ if (h)
+ *h = preedit->cursorHeight;
+}
+
+GLFWAPI void glfwSetPreeditCursorRectangle(GLFWwindow* handle, int x, int y, int w, int h)
+{
+ _GLFWwindow* window = (_GLFWwindow*) handle;
+ _GLFWpreedit* preedit = &window->preedit;
+
+ if (x == preedit->cursorPosX &&
+ y == preedit->cursorPosY &&
+ w == preedit->cursorWidth &&
+ h == preedit->cursorHeight)
+ {
+ return;
+ }
+
+ preedit->cursorPosX = x;
+ preedit->cursorPosY = y;
+ preedit->cursorWidth = w;
+ preedit->cursorHeight = h;
+
+ _glfw.platform.updatePreeditCursorRectangle(window);
+}
+
+GLFWAPI void glfwResetPreeditText(GLFWwindow* handle)
+{
+ _GLFWwindow* window = (_GLFWwindow*) handle;
+ _glfw.platform.resetPreeditText(window);
+}
+
+GLFWAPI unsigned int* glfwGetPreeditCandidate(GLFWwindow* handle, int index, int* textCount)
+{
+ _GLFWwindow* window = (_GLFWwindow*) handle;
+ _GLFWpreedit* preedit = &window->preedit;
+
+ if (preedit->candidateCount <= index)
+ return NULL;
+
+ if (textCount)
+ *textCount = preedit->candidates[index].textCount;
+
+
+ return preedit->candidates[index].text;
+}
+
GLFWAPI GLFWkeyfun glfwSetKeyCallback(GLFWwindow* handle, GLFWkeyfun cbfun)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
@@ -986,6 +1092,31 @@ GLFWAPI GLFWcharmodsfun glfwSetCharModsCallback(GLFWwindow* handle, GLFWcharmods
return cbfun;
}
+GLFWAPI GLFWpreeditfun glfwSetPreeditCallback(GLFWwindow* handle, GLFWpreeditfun cbfun)
+{
+ _GLFWwindow* window = (_GLFWwindow*) handle;
+ _GLFW_REQUIRE_INIT_OR_RETURN(NULL);
+ _GLFW_SWAP(GLFWpreeditfun, window->callbacks.preedit, cbfun);
+ return cbfun;
+}
+
+GLFWAPI GLFWimestatusfun glfwSetIMEStatusCallback(GLFWwindow* handle, GLFWimestatusfun cbfun)
+{
+ _GLFWwindow* window = (_GLFWwindow*) handle;
+ _GLFW_REQUIRE_INIT_OR_RETURN(NULL);
+ _GLFW_SWAP(GLFWimestatusfun, window->callbacks.imestatus, cbfun);
+ return cbfun;
+}
+
+GLFWAPI GLFWpreeditcandidatefun glfwSetPreeditCandidateCallback(GLFWwindow* handle,
+ GLFWpreeditcandidatefun cbfun)
+{
+ _GLFWwindow* window = (_GLFWwindow*) handle;
+ _GLFW_REQUIRE_INIT_OR_RETURN(NULL);
+ _GLFW_SWAP(GLFWpreeditcandidatefun, window->callbacks.preeditCandidate, cbfun);
+ return cbfun;
+}
+
GLFWAPI GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* handle,
GLFWmousebuttonfun cbfun)
{
diff --git a/src/internal.h b/src/internal.h
index 4f097aa8..80db7916 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -63,22 +63,24 @@
typedef int GLFWbool;
typedef void (*GLFWproc)(void);
-typedef struct _GLFWerror _GLFWerror;
-typedef struct _GLFWinitconfig _GLFWinitconfig;
-typedef struct _GLFWwndconfig _GLFWwndconfig;
-typedef struct _GLFWctxconfig _GLFWctxconfig;
-typedef struct _GLFWfbconfig _GLFWfbconfig;
-typedef struct _GLFWcontext _GLFWcontext;
-typedef struct _GLFWwindow _GLFWwindow;
-typedef struct _GLFWplatform _GLFWplatform;
-typedef struct _GLFWlibrary _GLFWlibrary;
-typedef struct _GLFWmonitor _GLFWmonitor;
-typedef struct _GLFWcursor _GLFWcursor;
-typedef struct _GLFWmapelement _GLFWmapelement;
-typedef struct _GLFWmapping _GLFWmapping;
-typedef struct _GLFWjoystick _GLFWjoystick;
-typedef struct _GLFWtls _GLFWtls;
-typedef struct _GLFWmutex _GLFWmutex;
+typedef struct _GLFWerror _GLFWerror;
+typedef struct _GLFWinitconfig _GLFWinitconfig;
+typedef struct _GLFWwndconfig _GLFWwndconfig;
+typedef struct _GLFWctxconfig _GLFWctxconfig;
+typedef struct _GLFWfbconfig _GLFWfbconfig;
+typedef struct _GLFWcontext _GLFWcontext;
+typedef struct _GLFWpreedit _GLFWpreedit;
+typedef struct _GLFWpreeditcandidate _GLFWpreeditcandidate;
+typedef struct _GLFWwindow _GLFWwindow;
+typedef struct _GLFWplatform _GLFWplatform;
+typedef struct _GLFWlibrary _GLFWlibrary;
+typedef struct _GLFWmonitor _GLFWmonitor;
+typedef struct _GLFWcursor _GLFWcursor;
+typedef struct _GLFWmapelement _GLFWmapelement;
+typedef struct _GLFWmapping _GLFWmapping;
+typedef struct _GLFWjoystick _GLFWjoystick;
+typedef struct _GLFWtls _GLFWtls;
+typedef struct _GLFWmutex _GLFWmutex;
#define GL_VERSION 0x1f02
#define GL_NONE 0
@@ -377,6 +379,7 @@ struct _GLFWinitconfig
GLFWbool hatButtons;
int angleType;
int platformID;
+ GLFWbool managePreeditCandidate;
PFN_vkGetInstanceProcAddr vulkanLoader;
struct {
GLFWbool menubar;
@@ -384,6 +387,7 @@ struct _GLFWinitconfig
} ns;
struct {
GLFWbool xcbVulkanSurface;
+ GLFWbool onTheSpotIMStyle;
} x11;
struct {
int libdecorMode;
@@ -525,6 +529,39 @@ struct _GLFWcontext
GLFW_PLATFORM_CONTEXT_STATE
};
+// Preedit structure for Input Method Editor/Engine
+//
+struct _GLFWpreedit
+{
+ unsigned int* text;
+ int textCount;
+ int textBufferCount;
+ int* blockSizes;
+ int blockSizesCount;
+ int blockSizesBufferCount;
+ int focusedBlockIndex;
+ int caretIndex;
+ int cursorPosX, cursorPosY, cursorWidth, cursorHeight;
+
+ // Used only when apps display candidates by themselves.
+ // Usually, OS displays them, so apps don't need to do it.
+ _GLFWpreeditcandidate* candidates;
+ int candidateCount;
+ int candidateBufferCount;
+ int candidateSelection;
+ int candidatePageStart;
+ int candidatePageSize;
+};
+
+// Preedit candidate structure
+//
+struct _GLFWpreeditcandidate
+{
+ unsigned int* text;
+ int textCount;
+ int textBufferCount;
+};
+
// Window and context structure
//
struct _GLFWwindow
@@ -563,6 +600,8 @@ struct _GLFWwindow
_GLFWcontext context;
+ _GLFWpreedit preedit;
+
struct {
GLFWwindowposfun pos;
GLFWwindowsizefun size;
@@ -580,6 +619,9 @@ struct _GLFWwindow
GLFWkeyfun key;
GLFWcharfun character;
GLFWcharmodsfun charmods;
+ GLFWpreeditfun preedit;
+ GLFWimestatusfun imestatus;
+ GLFWpreeditcandidatefun preeditCandidate;
GLFWdropfun drop;
} callbacks;
@@ -699,6 +741,10 @@ struct _GLFWplatform
int (*getKeyScancode)(int);
void (*setClipboardString)(const char*);
const char* (*getClipboardString)(void);
+ void (*updatePreeditCursorRectangle)(_GLFWwindow*);
+ void (*resetPreeditText)(_GLFWwindow*);
+ void (*setIMEStatus)(_GLFWwindow*,int);
+ int (*getIMEStatus)(_GLFWwindow*);
GLFWbool (*initJoysticks)(void);
void (*terminateJoysticks)(void);
GLFWbool (*pollJoystick)(_GLFWjoystick*,int);
@@ -934,6 +980,9 @@ void _glfwInputKey(_GLFWwindow* window,
int key, int scancode, int action, int mods);
void _glfwInputChar(_GLFWwindow* window,
uint32_t codepoint, int mods, GLFWbool plain);
+void _glfwInputPreedit(_GLFWwindow* window);
+void _glfwInputIMEStatus(_GLFWwindow* window);
+void _glfwInputPreeditCandidate(_GLFWwindow* window);
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset);
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
@@ -1010,6 +1059,7 @@ void _glfwTerminateVulkan(void);
const char* _glfwGetVulkanResultString(VkResult result);
size_t _glfwEncodeUTF8(char* s, uint32_t codepoint);
+uint32_t _glfwDecodeUTF8(const char** s);
char** _glfwParseUriList(char* text, int* count);
char* _glfw_strdup(const char* source);
diff --git a/src/null_init.c b/src/null_init.c
index 8c10f5e6..f4250603 100644
--- a/src/null_init.c
+++ b/src/null_init.c
@@ -55,6 +55,10 @@ GLFWbool _glfwConnectNull(int platformID, _GLFWplatform* platform)
.getKeyScancode = _glfwGetKeyScancodeNull,
.setClipboardString = _glfwSetClipboardStringNull,
.getClipboardString = _glfwGetClipboardStringNull,
+ .updatePreeditCursorRectangle = _glfwUpdatePreeditCursorRectangleNull,
+ .resetPreeditText = _glfwResetPreeditTextNull,
+ .setIMEStatus = _glfwSetIMEStatusNull,
+ .getIMEStatus = _glfwGetIMEStatusNull,
.initJoysticks = _glfwInitJoysticksNull,
.terminateJoysticks = _glfwTerminateJoysticksNull,
.pollJoystick = _glfwPollJoystickNull,
diff --git a/src/null_platform.h b/src/null_platform.h
index dbcb835b..bbd3ee42 100644
--- a/src/null_platform.h
+++ b/src/null_platform.h
@@ -270,6 +270,11 @@ const char* _glfwGetClipboardStringNull(void);
const char* _glfwGetScancodeNameNull(int scancode);
int _glfwGetKeyScancodeNull(int key);
+void _glfwUpdatePreeditCursorRectangleNull(_GLFWwindow* window);
+void _glfwResetPreeditTextNull(_GLFWwindow* window);
+void _glfwSetIMEStatusNull(_GLFWwindow* window, int active);
+int _glfwGetIMEStatusNull(_GLFWwindow* window);
+
EGLenum _glfwGetEGLPlatformNull(EGLint** attribs);
EGLNativeDisplayType _glfwGetEGLNativeDisplayNull(void);
EGLNativeWindowType _glfwGetEGLNativeWindowNull(_GLFWwindow* window);
diff --git a/src/null_window.c b/src/null_window.c
index f0e1dcc9..efadbe18 100644
--- a/src/null_window.c
+++ b/src/null_window.c
@@ -551,6 +551,23 @@ const char* _glfwGetClipboardStringNull(void)
return _glfw.null.clipboardString;
}
+void _glfwUpdatePreeditCursorRectangleNull(_GLFWwindow* window)
+{
+}
+
+void _glfwResetPreeditTextNull(_GLFWwindow* window)
+{
+}
+
+void _glfwSetIMEStatusNull(_GLFWwindow* window, int active)
+{
+}
+
+int _glfwGetIMEStatusNull(_GLFWwindow* window)
+{
+ return GLFW_FALSE;
+}
+
EGLenum _glfwGetEGLPlatformNull(EGLint** attribs)
{
if (_glfw.egl.EXT_platform_base && _glfw.egl.MESA_platform_surfaceless)
diff --git a/src/win32_init.c b/src/win32_init.c
index 77ab56ba..8d0eb65e 100644
--- a/src/win32_init.c
+++ b/src/win32_init.c
@@ -167,6 +167,31 @@ static GLFWbool loadLibraries(void)
_glfwPlatformGetModuleSymbol(_glfw.win32.ntdll.instance, "RtlVerifyVersionInfo");
}
+ _glfw.win32.imm32.instance = _glfwPlatformLoadModule("imm32.dll");
+ if (_glfw.win32.imm32.instance)
+ {
+ _glfw.win32.imm32.ImmGetCandidateListW_ = (PFN_ImmGetCandidateListW)
+ _glfwPlatformGetModuleSymbol(_glfw.win32.imm32.instance, "ImmGetCandidateListW");
+ _glfw.win32.imm32.ImmGetCompositionStringW_ = (PFN_ImmGetCompositionStringW)
+ _glfwPlatformGetModuleSymbol(_glfw.win32.imm32.instance, "ImmGetCompositionStringW");
+ _glfw.win32.imm32.ImmGetContext_ = (PFN_ImmGetContext)
+ _glfwPlatformGetModuleSymbol(_glfw.win32.imm32.instance, "ImmGetContext");
+ _glfw.win32.imm32.ImmGetConversionStatus_ = (PFN_ImmGetConversionStatus)
+ _glfwPlatformGetModuleSymbol(_glfw.win32.imm32.instance, "ImmGetConversionStatus");
+ _glfw.win32.imm32.ImmGetDescriptionW_ = (PFN_ImmGetDescriptionW)
+ _glfwPlatformGetModuleSymbol(_glfw.win32.imm32.instance, "ImmGetDescriptionW");
+ _glfw.win32.imm32.ImmGetOpenStatus_ = (PFN_ImmGetOpenStatus)
+ _glfwPlatformGetModuleSymbol(_glfw.win32.imm32.instance, "ImmGetOpenStatus");
+ _glfw.win32.imm32.ImmNotifyIME_ = (PFN_ImmNotifyIME)
+ _glfwPlatformGetModuleSymbol(_glfw.win32.imm32.instance, "ImmNotifyIME");
+ _glfw.win32.imm32.ImmReleaseContext_ = (PFN_ImmReleaseContext)
+ _glfwPlatformGetModuleSymbol(_glfw.win32.imm32.instance, "ImmReleaseContext");
+ _glfw.win32.imm32.ImmSetCandidateWindow_ = (PFN_ImmSetCandidateWindow)
+ _glfwPlatformGetModuleSymbol(_glfw.win32.imm32.instance, "ImmSetCandidateWindow");
+ _glfw.win32.imm32.ImmSetOpenStatus_ = (PFN_ImmSetOpenStatus)
+ _glfwPlatformGetModuleSymbol(_glfw.win32.imm32.instance, "ImmSetOpenStatus");
+ }
+
return GLFW_TRUE;
}
@@ -191,6 +216,9 @@ static void freeLibraries(void)
if (_glfw.win32.ntdll.instance)
_glfwPlatformFreeModule(_glfw.win32.ntdll.instance);
+
+ if (_glfw.win32.imm32.instance)
+ _glfwPlatformFreeModule(_glfw.win32.imm32.instance);
}
// Create key code translation tables
@@ -618,6 +646,10 @@ GLFWbool _glfwConnectWin32(int platformID, _GLFWplatform* platform)
.getKeyScancode = _glfwGetKeyScancodeWin32,
.setClipboardString = _glfwSetClipboardStringWin32,
.getClipboardString = _glfwGetClipboardStringWin32,
+ .updatePreeditCursorRectangle = _glfwUpdatePreeditCursorRectangleWin32,
+ .resetPreeditText = _glfwResetPreeditTextWin32,
+ .setIMEStatus = _glfwSetIMEStatusWin32,
+ .getIMEStatus = _glfwGetIMEStatusWin32,
.initJoysticks = _glfwInitJoysticksWin32,
.terminateJoysticks = _glfwTerminateJoysticksWin32,
.pollJoystick = _glfwPollJoystickWin32,
diff --git a/src/win32_platform.h b/src/win32_platform.h
index a2f86852..eae33340 100644
--- a/src/win32_platform.h
+++ b/src/win32_platform.h
@@ -69,6 +69,7 @@
#include
#include
#include
+#include
// HACK: Define macros that some windows.h variants don't
#ifndef WM_MOUSEHWHEEL
@@ -316,6 +317,28 @@ typedef HRESULT (WINAPI * PFN_GetDpiForMonitor)(HMONITOR,MONITOR_DPI_TYPE,UINT*,
typedef LONG (WINAPI * PFN_RtlVerifyVersionInfo)(OSVERSIONINFOEXW*,ULONG,ULONGLONG);
#define RtlVerifyVersionInfo _glfw.win32.ntdll.RtlVerifyVersionInfo_
+// imm32 function pointer typedefs
+typedef DWORD (WINAPI * PFN_ImmGetCandidateListW)(HIMC,DWORD,LPCANDIDATELIST,DWORD);
+typedef LONG (WINAPI * PFN_ImmGetCompositionStringW)(HIMC,DWORD,LPVOID,DWORD);
+typedef HIMC (WINAPI * PFN_ImmGetContext)(HWND);
+typedef BOOL (WINAPI * PFN_ImmGetConversionStatus)(HIMC,LPDWORD,LPDWORD);
+typedef UINT (WINAPI * PFN_ImmGetDescriptionW)(HKL,LPWSTR,UINT);
+typedef BOOL (WINAPI * PFN_ImmGetOpenStatus)(HIMC);
+typedef BOOL (WINAPI * PFN_ImmNotifyIME)(HIMC,DWORD,DWORD,DWORD);
+typedef BOOL (WINAPI * PFN_ImmReleaseContext)(HWND,HIMC);
+typedef BOOL (WINAPI * PFN_ImmSetCandidateWindow)(HIMC,LPCANDIDATEFORM);
+typedef BOOL (WINAPI * PFN_ImmSetOpenStatus)(HIMC,BOOL);
+#define ImmGetCandidateListW _glfw.win32.imm32.ImmGetCandidateListW_
+#define ImmGetCompositionStringW _glfw.win32.imm32.ImmGetCompositionStringW_
+#define ImmGetContext _glfw.win32.imm32.ImmGetContext_
+#define ImmGetConversionStatus _glfw.win32.imm32.ImmGetConversionStatus_
+#define ImmGetDescriptionW _glfw.win32.imm32.ImmGetDescriptionW_
+#define ImmGetOpenStatus _glfw.win32.imm32.ImmGetOpenStatus_
+#define ImmNotifyIME _glfw.win32.imm32.ImmNotifyIME_
+#define ImmReleaseContext _glfw.win32.imm32.ImmReleaseContext_
+#define ImmSetCandidateWindow _glfw.win32.imm32.ImmSetCandidateWindow_
+#define ImmSetOpenStatus _glfw.win32.imm32.ImmSetOpenStatus_
+
// WGL extension pointer typedefs
typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC)(int);
typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(HDC,int,int,UINT,const int*,int*);
@@ -502,6 +525,20 @@ typedef struct _GLFWlibraryWin32
HINSTANCE instance;
PFN_RtlVerifyVersionInfo RtlVerifyVersionInfo_;
} ntdll;
+
+ struct {
+ HINSTANCE instance;
+ PFN_ImmGetCandidateListW ImmGetCandidateListW_;
+ PFN_ImmGetCompositionStringW ImmGetCompositionStringW_;
+ PFN_ImmGetContext ImmGetContext_;
+ PFN_ImmGetConversionStatus ImmGetConversionStatus_;
+ PFN_ImmGetDescriptionW ImmGetDescriptionW_;
+ PFN_ImmGetOpenStatus ImmGetOpenStatus_;
+ PFN_ImmNotifyIME ImmNotifyIME_;
+ PFN_ImmReleaseContext ImmReleaseContext_;
+ PFN_ImmSetCandidateWindow ImmSetCandidateWindow_;
+ PFN_ImmSetOpenStatus ImmSetOpenStatus_;
+ } imm32;
} _GLFWlibraryWin32;
// Win32-specific per-monitor data
@@ -596,6 +633,11 @@ void _glfwSetCursorWin32(_GLFWwindow* window, _GLFWcursor* cursor);
void _glfwSetClipboardStringWin32(const char* string);
const char* _glfwGetClipboardStringWin32(void);
+void _glfwUpdatePreeditCursorRectangleWin32(_GLFWwindow* window);
+void _glfwResetPreeditTextWin32(_GLFWwindow* window);
+void _glfwSetIMEStatusWin32(_GLFWwindow* window, int active);
+int _glfwGetIMEStatusWin32(_GLFWwindow* window);
+
EGLenum _glfwGetEGLPlatformWin32(EGLint** attribs);
EGLNativeDisplayType _glfwGetEGLNativeDisplayWin32(void);
EGLNativeWindowType _glfwGetEGLNativeWindowWin32(_GLFWwindow* window);
diff --git a/src/win32_window.c b/src/win32_window.c
index d014944b..396c1771 100644
--- a/src/win32_window.c
+++ b/src/win32_window.c
@@ -35,6 +35,42 @@
#include
#include
#include
+#include
+
+// Converts utf16 units to Unicode code points (UTF32).
+// Returns GLFW_TRUE when the converting completes and the result is assigned to
+// the argument `codepoint`.
+// Returns GLFW_FALSE when the converting is not yet completed (for
+// Surrogate-pair processing) and the unit is assigned to the argument
+// `highsurrogate`. It will be used in the next unit's processing.
+//
+static GLFWbool convertToUTF32FromUTF16(WCHAR utf16_unit,
+ WCHAR* highsurrogate,
+ uint32_t* codepoint)
+{
+ *codepoint = 0;
+
+ if (utf16_unit >= 0xd800 && utf16_unit <= 0xdbff)
+ {
+ *highsurrogate = (WCHAR) utf16_unit;
+ return GLFW_FALSE;
+ }
+
+ if (utf16_unit >= 0xdc00 && utf16_unit <= 0xdfff)
+ {
+ if (*highsurrogate)
+ {
+ *codepoint += (*highsurrogate - 0xd800) << 10;
+ *codepoint += (WCHAR) utf16_unit - 0xdc00;
+ *codepoint += 0x10000;
+ }
+ }
+ else
+ *codepoint = (WCHAR) utf16_unit;
+
+ *highsurrogate = 0;
+ return GLFW_TRUE;
+}
// Returns the window style for the specified window
//
@@ -529,6 +565,318 @@ static void maximizeWindowManually(_GLFWwindow* window)
SWP_NOACTIVATE | SWP_NOZORDER | SWP_FRAMECHANGED);
}
+// Store candidate text from the buffer data
+//
+static void setCandidate(_GLFWpreeditcandidate* candidate, LPWSTR buffer)
+{
+ size_t bufferCount = wcslen(buffer);
+ int textBufferCount = candidate->textBufferCount;
+ uint32_t codepoint;
+ WCHAR highSurrogate = 0;
+ int convertedLength = 0;
+ int i;
+
+ while ((size_t) textBufferCount < bufferCount + 1)
+ textBufferCount = (textBufferCount == 0) ? 1 : textBufferCount * 2;
+ if (textBufferCount != candidate->textBufferCount)
+ {
+ unsigned int* text =
+ _glfw_realloc(candidate->text,
+ sizeof(unsigned int) * textBufferCount);
+ if (text == NULL)
+ return;
+ candidate->text = text;
+ candidate->textBufferCount = textBufferCount;
+ }
+
+ for (i = 0; (size_t) i < bufferCount; ++i)
+ {
+ if (convertToUTF32FromUTF16(buffer[i],
+ &highSurrogate,
+ &codepoint))
+ candidate->text[convertedLength++] = codepoint;
+ }
+
+ candidate->textCount = convertedLength;
+}
+
+// Get preedit candidates of Imm32 and pass them to candidate-callback
+//
+static void getImmCandidates(_GLFWwindow* window)
+{
+ _GLFWpreedit* preedit = &window->preedit;
+ HIMC hIMC = ImmGetContext(window->win32.handle);
+ DWORD candidateListBytes = ImmGetCandidateListW(hIMC, 0, NULL, 0);
+
+ if (candidateListBytes == 0)
+ {
+ ImmReleaseContext(window->win32.handle, hIMC);
+ return;
+ }
+
+ {
+ int i;
+ int bufferCount = preedit->candidateBufferCount;
+ LPCANDIDATELIST candidateList = _glfw_calloc(candidateListBytes, 1);
+ if (candidateList == NULL)
+ {
+ ImmReleaseContext(window->win32.handle, hIMC);
+ return;
+ }
+ ImmGetCandidateListW(hIMC, 0, candidateList, candidateListBytes);
+ ImmReleaseContext(window->win32.handle, hIMC);
+
+ while ((DWORD) bufferCount < candidateList->dwCount + 1)
+ bufferCount = (bufferCount == 0) ? 1 : bufferCount * 2;
+ if (bufferCount != preedit->candidateBufferCount)
+ {
+ _GLFWpreeditcandidate* candidates =
+ _glfw_realloc(preedit->candidates,
+ sizeof(_GLFWpreeditcandidate) * bufferCount);
+ if (candidates == NULL)
+ {
+ _glfw_free(candidateList);
+ return;
+ }
+ // `realloc` does not initialize the increased area with 0.
+ // This logic should be moved to a more appropriate place to share
+ // when other platforms support this feature.
+ for (i = preedit->candidateBufferCount; i < bufferCount; ++i)
+ {
+ candidates[i].text = NULL;
+ candidates[i].textCount = 0;
+ candidates[i].textBufferCount = 0;
+ }
+ preedit->candidates = candidates;
+ preedit->candidateBufferCount = bufferCount;
+ }
+
+ for (i = 0; (DWORD) i < candidateList->dwCount; ++i)
+ setCandidate(&preedit->candidates[i],
+ (LPWSTR)((char*) candidateList + candidateList->dwOffset[i]));
+
+ preedit->candidateCount = candidateList->dwCount;
+ preedit->candidateSelection = candidateList->dwSelection;
+ preedit->candidatePageStart = candidateList->dwPageStart;
+ preedit->candidatePageSize = candidateList->dwPageSize;
+
+ _glfw_free(candidateList);
+ }
+
+ _glfwInputPreeditCandidate(window);
+}
+
+// Clear preedit candidates
+static void clearImmCandidate(_GLFWwindow* window)
+{
+ window->preedit.candidateCount = 0;
+ window->preedit.candidateSelection = 0;
+ window->preedit.candidatePageStart = 0;
+ window->preedit.candidatePageSize = 0;
+ _glfwInputPreeditCandidate(window);
+}
+
+// Get preedit texts of Imm32 and pass them to preedit-callback
+//
+static GLFWbool getImmPreedit(_GLFWwindow* window)
+{
+ _GLFWpreedit* preedit = &window->preedit;
+ HIMC hIMC = ImmGetContext(window->win32.handle);
+ // get preedit data sizes
+ LONG preeditBytes = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, NULL, 0);
+ LONG attrBytes = ImmGetCompositionStringW(hIMC, GCS_COMPATTR, NULL, 0);
+ LONG clauseBytes = ImmGetCompositionStringW(hIMC, GCS_COMPCLAUSE, NULL, 0);
+ LONG cursorPos = ImmGetCompositionStringW(hIMC, GCS_CURSORPOS, NULL, 0);
+
+ if (preeditBytes > 0)
+ {
+ int textBufferCount = preedit->textBufferCount;
+ int blockBufferCount = preedit->blockSizesBufferCount;
+ int textLen = preeditBytes / sizeof(WCHAR);
+ LPWSTR buffer = _glfw_calloc(preeditBytes, 1);
+ LPSTR attributes = _glfw_calloc(attrBytes, 1);
+ DWORD* clauses = _glfw_calloc(clauseBytes, 1);
+
+ if (!buffer || (attrBytes > 0 && !attributes) || (clauseBytes > 0 && !clauses))
+ {
+ _glfw_free(buffer);
+ _glfw_free(attributes);
+ _glfw_free(clauses);
+ ImmReleaseContext(window->win32.handle, hIMC);
+ return GLFW_FALSE;
+ }
+
+ // get preedit data
+ ImmGetCompositionStringW(hIMC, GCS_COMPSTR, buffer, preeditBytes);
+ if (attributes)
+ ImmGetCompositionStringW(hIMC, GCS_COMPATTR, attributes, attrBytes);
+ if (clauses)
+ ImmGetCompositionStringW(hIMC, GCS_COMPCLAUSE, clauses, clauseBytes);
+
+ // realloc preedit text
+ while (textBufferCount < textLen + 1)
+ textBufferCount = (textBufferCount == 0) ? 1 : textBufferCount * 2;
+ if (textBufferCount != preedit->textBufferCount)
+ {
+ size_t bufsize = sizeof(unsigned int) * textBufferCount;
+ unsigned int* preeditText = _glfw_realloc(preedit->text,
+ bufsize);
+
+ if (preeditText == NULL)
+ {
+ _glfw_free(buffer);
+ _glfw_free(attributes);
+ _glfw_free(clauses);
+ ImmReleaseContext(window->win32.handle, hIMC);
+ return GLFW_FALSE;
+ }
+ preedit->text = preeditText;
+ preedit->textBufferCount = textBufferCount;
+ }
+
+ // realloc blocks
+ preedit->blockSizesCount = clauses ? clauseBytes / sizeof(DWORD) - 1 : 1;
+ while (blockBufferCount < preedit->blockSizesCount)
+ blockBufferCount = (blockBufferCount == 0) ? 1 : blockBufferCount * 2;
+ if (blockBufferCount != preedit->blockSizesBufferCount)
+ {
+ size_t bufsize = sizeof(int) * blockBufferCount;
+ int* blocks = _glfw_realloc(preedit->blockSizes,
+ bufsize);
+
+ if (blocks == NULL)
+ {
+ _glfw_free(buffer);
+ _glfw_free(attributes);
+ _glfw_free(clauses);
+ ImmReleaseContext(window->win32.handle, hIMC);
+ return GLFW_FALSE;
+ }
+ preedit->blockSizes = blocks;
+ preedit->blockSizesBufferCount = blockBufferCount;
+ }
+
+ // store preedit text & block sizes
+ {
+ // Win32 API handles text data in UTF16, so we have to convert them
+ // to UTF32. Not only the encoding, but also the number of characters,
+ // the position of each block and the cursor.
+ int i;
+ uint32_t codepoint;
+ WCHAR highSurrogate = 0;
+ int convertedLength = 0;
+ int blockIndex = 0;
+ int currentBlockLength = 0;
+
+ // The last element of clauses is a block count, but
+ // text length is convenient.
+ if (clauses)
+ clauses[preedit->blockSizesCount] = textLen;
+
+ for (i = 0; i < textLen; i++)
+ {
+ if (clauses && clauses[blockIndex + 1] <= (DWORD) i)
+ {
+ preedit->blockSizes[blockIndex++] = currentBlockLength;
+ currentBlockLength = 0;
+ }
+
+ if (convertToUTF32FromUTF16(buffer[i],
+ &highSurrogate,
+ &codepoint))
+ {
+ preedit->text[convertedLength++] = codepoint;
+ currentBlockLength++;
+ }
+ else if ((LONG) i < cursorPos)
+ {
+ // A high surrogate appears before cursorPos, so needs to
+ // fix cursorPos on UTF16 for UTF32
+ cursorPos--;
+ }
+ }
+ preedit->blockSizes[blockIndex] = currentBlockLength;
+ preedit->textCount = convertedLength;
+ preedit->text[convertedLength] = 0;
+ preedit->caretIndex = cursorPos;
+
+ preedit->focusedBlockIndex = 0;
+ if (attributes && clauses)
+ {
+ for (i = 0; i < preedit->blockSizesCount; i++)
+ {
+ if (attributes[clauses[i]] == ATTR_TARGET_CONVERTED ||
+ attributes[clauses[i]] == ATTR_TARGET_NOTCONVERTED)
+ {
+ preedit->focusedBlockIndex = i;
+ break;
+ }
+ }
+ }
+ }
+
+ _glfw_free(buffer);
+ _glfw_free(attributes);
+ _glfw_free(clauses);
+
+ _glfwInputPreedit(window);
+ }
+
+ ImmReleaseContext(window->win32.handle, hIMC);
+ return GLFW_TRUE;
+}
+
+// Clear peedit data
+//
+static void clearImmPreedit(_GLFWwindow* window)
+{
+ window->preedit.blockSizesCount = 0;
+ window->preedit.textCount = 0;
+ window->preedit.focusedBlockIndex = 0;
+ window->preedit.caretIndex = 0;
+ _glfwInputPreedit(window);
+}
+
+// Commit the result texts of Imm32 to character-callback
+//
+static GLFWbool commitImmResultStr(_GLFWwindow* window)
+{
+ HIMC hIMC;
+ LONG bytes;
+ uint32_t codepoint;
+ WCHAR highSurrogate = 0;
+
+ if (!window->callbacks.character)
+ return GLFW_FALSE;
+
+ hIMC = ImmGetContext(window->win32.handle);
+ // get preedit data sizes
+ bytes = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
+
+ if (bytes > 0)
+ {
+ int i;
+ int length = bytes / sizeof(WCHAR);
+ LPWSTR buffer = _glfw_calloc(bytes, 1);
+
+ // get preedit data
+ ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buffer, bytes);
+
+ for (i = 0; i < length; i++)
+ {
+ if (convertToUTF32FromUTF16(buffer[i],
+ &highSurrogate,
+ &codepoint))
+ window->callbacks.character((GLFWwindow*) window, codepoint);
+ }
+
+ _glfw_free(buffer);
+ }
+
+ ImmReleaseContext(window->win32.handle, hIMC);
+ return GLFW_TRUE;
+}
+
// Window procedure for user-created windows
//
static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
@@ -557,6 +905,20 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
switch (uMsg)
{
+ case WM_IME_SETCONTEXT:
+ {
+ // To draw preedit text by an application side
+ if (lParam & ISC_SHOWUICOMPOSITIONWINDOW)
+ lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
+
+ if (_glfw.hints.init.managePreeditCandidate &&
+ (lParam & ISC_SHOWUICANDIDATEWINDOW))
+ {
+ lParam &= ~ISC_SHOWUICANDIDATEWINDOW;
+ }
+ break;
+ }
+
case WM_MOUSEACTIVATE:
{
// HACK: Postpone cursor disabling when the window was activated by
@@ -662,27 +1024,11 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
case WM_CHAR:
case WM_SYSCHAR:
{
- if (wParam >= 0xd800 && wParam <= 0xdbff)
- window->win32.highSurrogate = (WCHAR) wParam;
- else
- {
- uint32_t codepoint = 0;
-
- if (wParam >= 0xdc00 && wParam <= 0xdfff)
- {
- if (window->win32.highSurrogate)
- {
- codepoint += (window->win32.highSurrogate - 0xd800) << 10;
- codepoint += (WCHAR) wParam - 0xdc00;
- codepoint += 0x10000;
- }
- }
- else
- codepoint = (WCHAR) wParam;
-
- window->win32.highSurrogate = 0;
+ uint32_t codepoint;
+ if (convertToUTF32FromUTF16((WCHAR) wParam,
+ &window->win32.highSurrogate,
+ &codepoint))
_glfwInputChar(window, codepoint, getKeyMods(), uMsg != WM_SYSCHAR);
- }
if (uMsg == WM_SYSCHAR && window->win32.keymenu)
break;
@@ -800,6 +1146,54 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
break;
}
+ case WM_IME_COMPOSITION:
+ {
+ if (lParam & (GCS_RESULTSTR | GCS_COMPSTR))
+ {
+ if (lParam & GCS_RESULTSTR)
+ commitImmResultStr(window);
+ if (lParam & GCS_COMPSTR)
+ getImmPreedit(window);
+ return TRUE;
+ }
+ break;
+ }
+
+ case WM_IME_ENDCOMPOSITION:
+ {
+ clearImmPreedit(window);
+ // Usually clearing candidates in IMN_CLOSECANDIDATE is sufficient.
+ // However, some IME need it here, e.g. Google Japanese Input.
+ clearImmCandidate(window);
+ return TRUE;
+ }
+
+ case WM_IME_NOTIFY:
+ {
+ switch (wParam)
+ {
+ case IMN_SETOPENSTATUS:
+ {
+ _glfwInputIMEStatus(window);
+ return TRUE;
+ }
+
+ case IMN_OPENCANDIDATE:
+ case IMN_CHANGECANDIDATE:
+ {
+ getImmCandidates(window);
+ return TRUE;
+ }
+
+ case IMN_CLOSECANDIDATE:
+ {
+ clearImmCandidate(window);
+ return TRUE;
+ }
+ }
+ break;
+ }
+
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
@@ -2463,6 +2857,48 @@ const char* _glfwGetClipboardStringWin32(void)
return _glfw.win32.clipboardString;
}
+void _glfwUpdatePreeditCursorRectangleWin32(_GLFWwindow* window)
+{
+ _GLFWpreedit* preedit = &window->preedit;
+ HWND hWnd = window->win32.handle;
+ HIMC hIMC = ImmGetContext(hWnd);
+
+ int x = preedit->cursorPosX;
+ int y = preedit->cursorPosY;
+ int w = preedit->cursorWidth;
+ int h = preedit->cursorHeight;
+ CANDIDATEFORM excludeRect = { 0, CFS_EXCLUDE, { x, y }, { x, y, x + w, y + h } };
+
+ ImmSetCandidateWindow(hIMC, &excludeRect);
+
+ ImmReleaseContext(hWnd, hIMC);
+}
+
+void _glfwResetPreeditTextWin32(_GLFWwindow* window)
+{
+ HWND hWnd = window->win32.handle;
+ HIMC hIMC = ImmGetContext(hWnd);
+ ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
+ ImmReleaseContext(hWnd, hIMC);
+}
+
+void _glfwSetIMEStatusWin32(_GLFWwindow* window, int active)
+{
+ HWND hWnd = window->win32.handle;
+ HIMC hIMC = ImmGetContext(hWnd);
+ ImmSetOpenStatus(hIMC, active ? TRUE : FALSE);
+ ImmReleaseContext(hWnd, hIMC);
+}
+
+int _glfwGetIMEStatusWin32(_GLFWwindow* window)
+{
+ HWND hWnd = window->win32.handle;
+ HIMC hIMC = ImmGetContext(hWnd);
+ BOOL result = ImmGetOpenStatus(hIMC);
+ ImmReleaseContext(hWnd, hIMC);
+ return result ? GLFW_TRUE : GLFW_FALSE;
+}
+
EGLenum _glfwGetEGLPlatformWin32(EGLint** attribs)
{
if (_glfw.egl.ANGLE_platform_angle)
diff --git a/src/window.c b/src/window.c
index e03121a4..8d15bf96 100644
--- a/src/window.c
+++ b/src/window.c
@@ -244,6 +244,11 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
window->denom = GLFW_DONT_CARE;
window->title = _glfw_strdup(title);
+ window->preedit.cursorPosX = 0;
+ window->preedit.cursorPosY = height;
+ window->preedit.cursorWidth = 0;
+ window->preedit.cursorHeight = 0;
+
if (!_glfw.platform.createWindow(window, &wndconfig, &ctxconfig, &fbconfig))
{
glfwDestroyWindow((GLFWwindow*) window);
@@ -495,6 +500,11 @@ GLFWAPI void glfwDestroyWindow(GLFWwindow* handle)
*prev = window->next;
}
+ // Clear memory for preedit text
+ if (window->preedit.text)
+ _glfw_free(window->preedit.text);
+ if (window->preedit.blockSizes)
+ _glfw_free(window->preedit.blockSizes);
_glfw_free(window->title);
_glfw_free(window);
}
diff --git a/src/wl_init.c b/src/wl_init.c
index 76054bc6..5edb36a2 100644
--- a/src/wl_init.c
+++ b/src/wl_init.c
@@ -49,6 +49,8 @@
#include "fractional-scale-v1-client-protocol.h"
#include "xdg-activation-v1-client-protocol.h"
#include "idle-inhibit-unstable-v1-client-protocol.h"
+#include "text-input-unstable-v1-client-protocol.h"
+#include "text-input-unstable-v3-client-protocol.h"
// NOTE: Versions of wayland-scanner prior to 1.17.91 named every global array of
// wl_interface pointers 'types', making it impossible to combine several unmodified
@@ -91,6 +93,14 @@
#include "idle-inhibit-unstable-v1-client-protocol-code.h"
#undef types
+#define types _glfw_text_input_v1_types
+#include "text-input-unstable-v1-client-protocol-code.h"
+#undef types
+
+#define types _glfw_text_input_v3_types
+#include "text-input-unstable-v3-client-protocol-code.h"
+#undef types
+
static void wmBaseHandlePing(void* userData,
struct xdg_wm_base* wmBase,
uint32_t serial)
@@ -208,6 +218,20 @@ static void registryHandleGlobal(void* userData,
&wp_fractional_scale_manager_v1_interface,
1);
}
+ else if (strcmp(interface, "zwp_text_input_manager_v1") == 0)
+ {
+ _glfw.wl.textInputManagerV1 =
+ wl_registry_bind(registry, name,
+ &zwp_text_input_manager_v1_interface,
+ 1);
+ }
+ else if (strcmp(interface, "zwp_text_input_manager_v3") == 0)
+ {
+ _glfw.wl.textInputManagerV3 =
+ wl_registry_bind(registry, name,
+ &zwp_text_input_manager_v3_interface,
+ 1);
+ }
}
static void registryHandleGlobalRemove(void* userData,
@@ -452,6 +476,10 @@ GLFWbool _glfwConnectWayland(int platformID, _GLFWplatform* platform)
.getKeyScancode = _glfwGetKeyScancodeWayland,
.setClipboardString = _glfwSetClipboardStringWayland,
.getClipboardString = _glfwGetClipboardStringWayland,
+ .updatePreeditCursorRectangle = _glfwUpdatePreeditCursorRectangleWayland,
+ .resetPreeditText = _glfwResetPreeditTextWayland,
+ .setIMEStatus = _glfwSetIMEStatusWayland,
+ .getIMEStatus = _glfwGetIMEStatusWayland,
#if defined(GLFW_BUILD_LINUX_JOYSTICK)
.initJoysticks = _glfwInitJoysticksLinux,
.terminateJoysticks = _glfwTerminateJoysticksLinux,
@@ -984,6 +1012,10 @@ void _glfwTerminateWayland(void)
xdg_activation_v1_destroy(_glfw.wl.activationManager);
if (_glfw.wl.fractionalScaleManager)
wp_fractional_scale_manager_v1_destroy(_glfw.wl.fractionalScaleManager);
+ if (_glfw.wl.textInputManagerV1)
+ zwp_text_input_manager_v1_destroy(_glfw.wl.textInputManagerV1);
+ if (_glfw.wl.textInputManagerV3)
+ zwp_text_input_manager_v3_destroy(_glfw.wl.textInputManagerV3);
if (_glfw.wl.registry)
wl_registry_destroy(_glfw.wl.registry);
if (_glfw.wl.display)
diff --git a/src/wl_platform.h b/src/wl_platform.h
index f3e8cba2..4372ce8f 100644
--- a/src/wl_platform.h
+++ b/src/wl_platform.h
@@ -411,6 +411,13 @@ typedef struct _GLFWwindowWayland
_GLFWfallbackEdgeWayland top, left, right, bottom;
struct wl_surface* focus;
} fallback;
+
+ struct zwp_text_input_v1* textInputV1;
+ struct zwp_text_input_v3* textInputV3;
+ struct {
+ char* preeditText;
+ char* commitTextOnReset;
+ } textInputV1Context;
} _GLFWwindowWayland;
// Wayland-specific global data
@@ -435,6 +442,8 @@ typedef struct _GLFWlibraryWayland
struct zwp_idle_inhibit_manager_v1* idleInhibitManager;
struct xdg_activation_v1* activationManager;
struct wp_fractional_scale_manager_v1* fractionalScaleManager;
+ struct zwp_text_input_manager_v1* textInputManagerV1;
+ struct zwp_text_input_manager_v3* textInputManagerV3;
_GLFWofferWayland* offers;
unsigned int offerCount;
@@ -664,6 +673,11 @@ void _glfwSetCursorWayland(_GLFWwindow* window, _GLFWcursor* cursor);
void _glfwSetClipboardStringWayland(const char* string);
const char* _glfwGetClipboardStringWayland(void);
+void _glfwUpdatePreeditCursorRectangleWayland(_GLFWwindow* window);
+void _glfwResetPreeditTextWayland(_GLFWwindow* window);
+void _glfwSetIMEStatusWayland(_GLFWwindow* window, int active);
+int _glfwGetIMEStatusWayland(_GLFWwindow* window);
+
EGLenum _glfwGetEGLPlatformWayland(EGLint** attribs);
EGLNativeDisplayType _glfwGetEGLNativeDisplayWayland(void);
EGLNativeWindowType _glfwGetEGLNativeWindowWayland(_GLFWwindow* window);
diff --git a/src/wl_window.c b/src/wl_window.c
index 2e842aaa..7aea6a59 100644
--- a/src/wl_window.c
+++ b/src/wl_window.c
@@ -51,6 +51,8 @@
#include "xdg-activation-v1-client-protocol.h"
#include "idle-inhibit-unstable-v1-client-protocol.h"
#include "fractional-scale-v1-client-protocol.h"
+#include "text-input-unstable-v1-client-protocol.h"
+#include "text-input-unstable-v3-client-protocol.h"
#define GLFW_BORDER_SIZE 4
#define GLFW_CAPTION_HEIGHT 24
@@ -555,6 +557,22 @@ const struct wp_fractional_scale_v1_listener fractionalScaleListener =
fractionalScaleHandlePreferredScale,
};
+static void activateTextInputV1(_GLFWwindow* window)
+{
+ if (!window->wl.textInputV1)
+ return;
+ zwp_text_input_v1_show_input_panel(window->wl.textInputV1);
+ zwp_text_input_v1_activate(window->wl.textInputV1, _glfw.wl.seat, window->wl.surface);
+}
+
+static void deactivateTextInputV1(_GLFWwindow* window)
+{
+ if (!window->wl.textInputV1)
+ return;
+ zwp_text_input_v1_hide_input_panel(window->wl.textInputV1);
+ zwp_text_input_v1_deactivate(window->wl.textInputV1, _glfw.wl.seat);
+}
+
static void xdgToplevelHandleConfigure(void* userData,
struct xdg_toplevel* toplevel,
int32_t width,
@@ -582,6 +600,7 @@ static void xdgToplevelHandleConfigure(void* userData,
break;
case XDG_TOPLEVEL_STATE_ACTIVATED:
window->wl.pending.activated = GLFW_TRUE;
+ activateTextInputV1(window);
break;
}
}
@@ -1538,6 +1557,11 @@ static void pointerHandleButton(void* userData,
if (!window)
return;
+ // On weston, pressing the title bar will cause leave event and never emit
+ // enter event even though back to content area by pressing mouse button
+ // just after it. So activate it here explicitly.
+ activateTextInputV1(window);
+
if (window->wl.hovered)
{
_glfw.wl.serial = serial;
@@ -2120,6 +2144,379 @@ void _glfwAddDataDeviceListenerWayland(struct wl_data_device* device)
wl_data_device_add_listener(device, &dataDeviceListener, NULL);
}
+// Callbacks for text_input_unstable_v3 protocol.
+//
+// This protocol is widely supported by major desktop environments such as GNOME
+// or KDE.
+//
+static void textInputV3Enter(void* data,
+ struct zwp_text_input_v3* textInputV3,
+ struct wl_surface* surface)
+{
+ zwp_text_input_v3_enable(textInputV3);
+ zwp_text_input_v3_commit(textInputV3);
+}
+
+static void textInputV3Reset(_GLFWwindow* window)
+{
+ _GLFWpreedit* preedit = &window->preedit;
+
+ preedit->textCount = 0;
+ preedit->blockSizesCount = 0;
+ preedit->focusedBlockIndex = 0;
+ preedit->caretIndex = 0;
+
+ _glfwInputPreedit(window);
+}
+
+static void textInputV3Leave(void* data,
+ struct zwp_text_input_v3* textInputV3,
+ struct wl_surface* surface)
+{
+ _GLFWwindow* window = (_GLFWwindow*) data;
+ zwp_text_input_v3_disable(textInputV3);
+ zwp_text_input_v3_commit(textInputV3);
+
+ // Although this should be handled by IM via preedit callback, it seems that
+ // the behavior varies depending on implemention. It's cleared by IM on
+ // Ubuntu 22.04 but not cleared on Ubuntu 20.04.
+ textInputV3Reset(window);
+}
+
+static void textInputV3PreeditString(void* data,
+ struct zwp_text_input_v3* textInputV3,
+ const char* text,
+ int32_t cursorBegin,
+ int32_t cursorEnd)
+{
+ _GLFWwindow* window = (_GLFWwindow*) data;
+ _GLFWpreedit* preedit = &window->preedit;
+ const char* cur = text;
+ unsigned int cursorLength = 0;
+
+ preedit->textCount = 0;
+ preedit->blockSizesCount = 0;
+ preedit->focusedBlockIndex = 0;
+ preedit->caretIndex = 0;
+
+ // Store preedit text
+ while (cur && *cur)
+ {
+ uint32_t codepoint = _glfwDecodeUTF8(&cur);
+
+ ++preedit->textCount;
+
+ if (cur == text + cursorBegin)
+ preedit->caretIndex = preedit->textCount;
+ if (cursorBegin != cursorEnd && cur == text + cursorEnd)
+ cursorLength = preedit->textCount - cursorBegin;
+
+ if (preedit->textBufferCount < preedit->textCount + 1)
+ {
+ int bufSize = preedit->textBufferCount;
+
+ while (bufSize < preedit->textCount + 1)
+ bufSize = (bufSize == 0) ? 1 : bufSize * 2;
+ preedit->text = _glfw_realloc(preedit->text,
+ sizeof(unsigned int) * bufSize);
+ if (!preedit->text)
+ return;
+ preedit->textBufferCount = bufSize;
+ }
+ preedit->text[preedit->textCount - 1] = codepoint;
+ }
+ if (preedit->text)
+ preedit->text[preedit->textCount] = 0;
+
+ // Store preedit blocks
+ if (preedit->textCount)
+ {
+ int* blocks = preedit->blockSizes;
+ int blockCount = preedit->blockSizesCount;
+ int cursorPos = preedit->caretIndex;
+ int textCount = preedit->textCount;
+
+ if (!preedit->blockSizes)
+ {
+ int bufSize = 3;
+
+ preedit->blockSizesBufferCount = bufSize;
+ preedit->blockSizes = _glfw_calloc(sizeof(int), bufSize);
+ if (!preedit->blockSizes)
+ return;
+ blocks = preedit->blockSizes;
+ }
+
+ if (cursorLength && cursorPos)
+ blocks[blockCount++] = cursorPos;
+
+ preedit->focusedBlockIndex = blockCount;
+ blocks[blockCount++] = cursorLength ? cursorLength : textCount;
+
+ if (cursorLength && cursorPos + cursorLength != textCount)
+ blocks[blockCount++] = textCount - cursorPos - cursorLength;
+
+ preedit->blockSizesCount = blockCount;
+ }
+}
+
+static void textInputV3CommitString(void* data,
+ struct zwp_text_input_v3* textInputV3,
+ const char* text)
+{
+ _GLFWwindow* window = (_GLFWwindow*) data;
+ const char* cur = text;
+
+ if (!window->callbacks.character)
+ return;
+
+ while (cur && *cur)
+ {
+ uint32_t codepoint = _glfwDecodeUTF8(&cur);
+ window->callbacks.character((GLFWwindow*) window, codepoint);
+ }
+}
+
+static void textInputV3DeleteSurroundingText(void* data,
+ struct zwp_text_input_v3* textInputV3,
+ uint32_t beforeLength,
+ uint32_t afterLength)
+{
+}
+
+static void textInputV3Done(void* data,
+ struct zwp_text_input_v3* textInputV3,
+ uint32_t serial)
+{
+ _GLFWwindow* window = (_GLFWwindow*) data;
+ _glfwUpdatePreeditCursorRectangleWayland(window);
+ _glfwInputPreedit(window);
+}
+
+static const struct zwp_text_input_v3_listener textInputV3Listener =
+{
+ textInputV3Enter,
+ textInputV3Leave,
+ textInputV3PreeditString,
+ textInputV3CommitString,
+ textInputV3DeleteSurroundingText,
+ textInputV3Done
+};
+
+// Callbacks for text_input_unstable_v1 protocol
+//
+// This protocol isn't so popular but Weston which is the reference Wayland
+// implementation supports only this protocol and doesn't support
+// text_input_unstable_v3.
+//
+static void textInputV1Enter(void* data,
+ struct zwp_text_input_v1* textInputV1,
+ struct wl_surface* surface)
+{
+ _GLFWwindow* window = (_GLFWwindow*) data;
+ activateTextInputV1(window);
+}
+
+static void textInputV1Reset(_GLFWwindow* window)
+{
+ _GLFWpreedit* preedit = &window->preedit;
+
+ preedit->textCount = 0;
+ preedit->blockSizesCount = 0;
+ preedit->focusedBlockIndex = 0;
+ preedit->caretIndex = 0;
+
+ _glfw_free(window->wl.textInputV1Context.preeditText);
+ _glfw_free(window->wl.textInputV1Context.commitTextOnReset);
+ window->wl.textInputV1Context.preeditText = NULL;
+ window->wl.textInputV1Context.commitTextOnReset = NULL;
+
+ _glfwInputPreedit(window);
+}
+
+static void textInputV1Leave(void* data,
+ struct zwp_text_input_v1* textInputV1)
+{
+ _GLFWwindow* window = (_GLFWwindow*) data;
+ char* commitText = window->wl.textInputV1Context.commitTextOnReset;
+
+ textInputV3CommitString(data, NULL, commitText);
+ textInputV1Reset(window);
+ deactivateTextInputV1(window);
+}
+
+static void textInputV1ModifiersMap(void* data,
+ struct zwp_text_input_v1* textInputV1,
+ struct wl_array* map)
+{
+}
+
+static void textInputV1InputPanelState(void* data,
+ struct zwp_text_input_v1* textInputV1,
+ uint32_t state)
+{
+}
+
+static void textInputV1PreeditString(void* data,
+ struct zwp_text_input_v1* textInputV1,
+ uint32_t serial,
+ const char* text,
+ const char* commit)
+{
+ _GLFWwindow* window = (_GLFWwindow*) data;
+
+ _glfw_free(window->wl.textInputV1Context.preeditText);
+ _glfw_free(window->wl.textInputV1Context.commitTextOnReset);
+ window->wl.textInputV1Context.preeditText = strdup(text);
+ window->wl.textInputV1Context.commitTextOnReset = strdup(commit);
+
+ textInputV3PreeditString(data, NULL, text, 0, 0);
+ _glfwInputPreedit(window);
+}
+
+static void textInputV1PreeditStyling(void* data,
+ struct zwp_text_input_v1* textInputV1,
+ uint32_t index,
+ uint32_t length,
+ uint32_t style)
+{
+}
+
+static void textInputV1PreeditCursor(void* data,
+ struct zwp_text_input_v1* textInputV1,
+ int32_t index)
+{
+ _GLFWwindow* window = (_GLFWwindow*) data;
+ _GLFWpreedit* preedit = &window->preedit;
+ const char* text = window->wl.textInputV1Context.preeditText;
+ const char* cur = text;
+
+ preedit->caretIndex = 0;
+ if (index <= 0 || preedit->textCount == 0)
+ return;
+
+ while (cur && *cur)
+ {
+ _glfwDecodeUTF8(&cur);
+ ++preedit->caretIndex;
+ if (cur >= text + index)
+ break;
+ if (preedit->caretIndex > preedit->textCount)
+ break;
+ }
+}
+
+static void textInputV1CommitString(void* data,
+ struct zwp_text_input_v1* textInputV1,
+ uint32_t serial,
+ const char* text)
+{
+ _GLFWwindow* window = (_GLFWwindow*) data;
+
+ textInputV1Reset(window);
+ textInputV3CommitString(data, NULL, text);
+}
+
+static void textInputV1CursorPosition(void* data,
+ struct zwp_text_input_v1* textInputV1,
+ int32_t index,
+ int32_t anchor)
+{
+ // It's for surrounding text feature which isn't supported by GLFW.
+}
+
+static void textInputV1DeleteSurroundingText(void* data,
+ struct zwp_text_input_v1* textInputV1,
+ int32_t index,
+ uint32_t length)
+{
+}
+
+static void textInputV1Keysym(void* data,
+ struct zwp_text_input_v1* textInputV1,
+ uint32_t serial,
+ uint32_t time,
+ uint32_t sym,
+ uint32_t state,
+ uint32_t modifiers)
+{
+ uint32_t scancode;
+
+ // This code supports only weston-keyboard because we aren't aware
+ // of any other input methods that actually support this API.
+ // Supporting all keysyms is overkill for now.
+
+ switch (sym)
+ {
+ case XKB_KEY_Left:
+ scancode = KEY_LEFT;
+ break;
+ case XKB_KEY_Right:
+ scancode = KEY_RIGHT;
+ break;
+ case XKB_KEY_Up:
+ scancode = KEY_UP;
+ break;
+ case XKB_KEY_Down:
+ scancode = KEY_DOWN;
+ break;
+ case XKB_KEY_BackSpace:
+ scancode = KEY_BACKSPACE;
+ break;
+ case XKB_KEY_Tab:
+ scancode = KEY_TAB;
+ break;
+ case XKB_KEY_KP_Enter:
+ scancode = KEY_KPENTER;
+ break;
+ case XKB_KEY_Return:
+ scancode = KEY_ENTER;
+ break;
+ default:
+ return;
+ }
+
+ _glfw.wl.xkb.modifiers = modifiers;
+
+ keyboardHandleKey(data,
+ _glfw.wl.keyboard,
+ serial,
+ time,
+ scancode,
+ state);
+}
+
+static void textInputV1Language(void* data,
+ struct zwp_text_input_v1* textInputV1,
+ uint32_t serial,
+ const char* language)
+{
+}
+
+static void textInputV1TextDirection(void* data,
+ struct zwp_text_input_v1* textInputV1,
+ uint32_t serial,
+ uint32_t direction)
+{
+}
+
+static const struct zwp_text_input_v1_listener textInputV1Listener =
+{
+ textInputV1Enter,
+ textInputV1Leave,
+ textInputV1ModifiersMap,
+ textInputV1InputPanelState,
+ textInputV1PreeditString,
+ textInputV1PreeditStyling,
+ textInputV1PreeditCursor,
+ textInputV1CommitString,
+ textInputV1CursorPosition,
+ textInputV1DeleteSurroundingText,
+ textInputV1Keysym,
+ textInputV1Language,
+ textInputV1TextDirection
+};
+
//////////////////////////////////////////////////////////////////////////
////// GLFW platform API //////
@@ -2174,6 +2571,21 @@ GLFWbool _glfwCreateWindowWayland(_GLFWwindow* window,
return GLFW_FALSE;
}
+ if (_glfw.wl.textInputManagerV3)
+ {
+ window->wl.textInputV3 =
+ zwp_text_input_manager_v3_get_text_input(_glfw.wl.textInputManagerV3, _glfw.wl.seat);
+ zwp_text_input_v3_add_listener(window->wl.textInputV3,
+ &textInputV3Listener, window);
+ }
+ else if (_glfw.wl.textInputManagerV1)
+ {
+ window->wl.textInputV1 =
+ zwp_text_input_manager_v1_create_text_input(_glfw.wl.textInputManagerV1);
+ zwp_text_input_v1_add_listener(window->wl.textInputV1,
+ &textInputV1Listener, window);
+ }
+
return GLFW_TRUE;
}
@@ -2194,6 +2606,15 @@ void _glfwDestroyWindowWayland(_GLFWwindow* window)
if (window->wl.activationToken)
xdg_activation_token_v1_destroy(window->wl.activationToken);
+ if (window->wl.textInputV1) {
+ zwp_text_input_v1_destroy(window->wl.textInputV1);
+ _glfw_free(window->wl.textInputV1Context.preeditText);
+ _glfw_free(window->wl.textInputV1Context.commitTextOnReset);
+ }
+
+ if (window->wl.textInputV3)
+ zwp_text_input_v3_destroy(window->wl.textInputV3);
+
if (window->wl.idleInhibitor)
zwp_idle_inhibitor_v1_destroy(window->wl.idleInhibitor);
@@ -3197,6 +3618,36 @@ const char* _glfwGetClipboardStringWayland(void)
return _glfw.wl.clipboardString;
}
+void _glfwUpdatePreeditCursorRectangleWayland(_GLFWwindow* window)
+{
+ _GLFWpreedit* preedit = &window->preedit;
+ int x = preedit->cursorPosX;
+ int y = preedit->cursorPosY;
+ int w = preedit->cursorWidth;
+ int h = preedit->cursorHeight;
+
+ if (window->wl.textInputV3)
+ {
+ zwp_text_input_v3_set_cursor_rectangle(window->wl.textInputV3, x, y, w, h);
+ zwp_text_input_v3_commit(window->wl.textInputV3);
+ }
+ else if (window->wl.textInputV1)
+ zwp_text_input_v1_set_cursor_rectangle(window->wl.textInputV1, x, y, w, h);
+}
+
+void _glfwResetPreeditTextWayland(_GLFWwindow* window)
+{
+}
+
+void _glfwSetIMEStatusWayland(_GLFWwindow* window, int active)
+{
+}
+
+int _glfwGetIMEStatusWayland(_GLFWwindow* window)
+{
+ return GLFW_FALSE;
+}
+
EGLenum _glfwGetEGLPlatformWayland(EGLint** attribs)
{
if (_glfw.egl.EXT_platform_base && _glfw.egl.EXT_platform_wayland)
diff --git a/src/x11_init.c b/src/x11_init.c
index 982c526c..1268296f 100644
--- a/src/x11_init.c
+++ b/src/x11_init.c
@@ -445,9 +445,14 @@ static GLFWbool hasUsableInputMethodStyle(void)
if (XGetIMValues(_glfw.x11.im, XNQueryInputStyle, &styles, NULL) != NULL)
return GLFW_FALSE;
+ if (_glfw.hints.init.x11.onTheSpotIMStyle)
+ _glfw.x11.imStyle = STYLE_ONTHESPOT;
+ else
+ _glfw.x11.imStyle = STYLE_OVERTHESPOT;
+
for (unsigned int i = 0; i < styles->count_styles; i++)
{
- if (styles->supported_styles[i] == (XIMPreeditNothing | XIMStatusNothing))
+ if (styles->supported_styles[i] == _glfw.x11.imStyle)
{
found = GLFW_TRUE;
break;
@@ -1182,6 +1187,10 @@ GLFWbool _glfwConnectX11(int platformID, _GLFWplatform* platform)
.getKeyScancode = _glfwGetKeyScancodeX11,
.setClipboardString = _glfwSetClipboardStringX11,
.getClipboardString = _glfwGetClipboardStringX11,
+ .updatePreeditCursorRectangle = _glfwUpdatePreeditCursorRectangleX11,
+ .resetPreeditText = _glfwResetPreeditTextX11,
+ .setIMEStatus = _glfwSetIMEStatusX11,
+ .getIMEStatus = _glfwGetIMEStatusX11,
#if defined(GLFW_BUILD_LINUX_JOYSTICK)
.initJoysticks = _glfwInitJoysticksLinux,
.terminateJoysticks = _glfwTerminateJoysticksLinux,
@@ -1451,6 +1460,8 @@ int _glfwInitX11(void)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XSetErrorHandler");
_glfw.x11.xlib.SetICFocus = (PFN_XSetICFocus)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XSetICFocus");
+ _glfw.x11.xlib.SetICValues = (PFN_XSetICValues)
+ _glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XSetICValues");
_glfw.x11.xlib.SetIMValues = (PFN_XSetIMValues)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XSetIMValues");
_glfw.x11.xlib.SetInputFocus = (PFN_XSetInputFocus)
@@ -1481,6 +1492,8 @@ int _glfwInitX11(void)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XUnmapWindow");
_glfw.x11.xlib.UnsetICFocus = (PFN_XUnsetICFocus)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XUnsetICFocus");
+ _glfw.x11.xlib.VaCreateNestedList = (PFN_XVaCreateNestedList)
+ _glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XVaCreateNestedList");
_glfw.x11.xlib.VisualIDFromVisual = (PFN_XVisualIDFromVisual)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XVisualIDFromVisual");
_glfw.x11.xlib.WarpPointer = (PFN_XWarpPointer)
@@ -1513,6 +1526,8 @@ int _glfwInitX11(void)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XrmUniqueQuark");
_glfw.x11.xlib.UnregisterIMInstantiateCallback = (PFN_XUnregisterIMInstantiateCallback)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XUnregisterIMInstantiateCallback");
+ _glfw.x11.xlib.mbResetIC = (PFN_XmbResetIC)
+ _glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XmbResetIC");
_glfw.x11.xlib.utf8LookupString = (PFN_Xutf8LookupString)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "Xutf8LookupString");
_glfw.x11.xlib.utf8SetWMProperties = (PFN_Xutf8SetWMProperties)
diff --git a/src/x11_platform.h b/src/x11_platform.h
index 30326c5b..4764dbb3 100644
--- a/src/x11_platform.h
+++ b/src/x11_platform.h
@@ -91,6 +91,9 @@
#define GLX_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 0x2098
#define GLX_CONTEXT_OPENGL_NO_ERROR_ARB 0x31b3
+#define STYLE_OVERTHESPOT (XIMPreeditNothing | XIMStatusNothing)
+#define STYLE_ONTHESPOT (XIMPreeditCallbacks | XIMStatusCallbacks)
+
typedef XID GLXWindow;
typedef XID GLXDrawable;
typedef struct __GLXFBConfig* GLXFBConfig;
@@ -165,6 +168,7 @@ typedef Status (* PFN_XSendEvent)(Display*,Window,Bool,long,XEvent*);
typedef int (* PFN_XSetClassHint)(Display*,Window,XClassHint*);
typedef XErrorHandler (* PFN_XSetErrorHandler)(XErrorHandler);
typedef void (* PFN_XSetICFocus)(XIC);
+typedef char* (* PFN_XSetICValues)(XIC,...);
typedef char* (* PFN_XSetIMValues)(XIM,...);
typedef int (* PFN_XSetInputFocus)(Display*,Window,int,Time);
typedef char* (* PFN_XSetLocaleModifiers)(const char*);
@@ -180,6 +184,7 @@ typedef int (* PFN_XUndefineCursor)(Display*,Window);
typedef int (* PFN_XUngrabPointer)(Display*,Time);
typedef int (* PFN_XUnmapWindow)(Display*,Window);
typedef void (* PFN_XUnsetICFocus)(XIC);
+typedef XVaNestedList (* PFN_XVaCreateNestedList)(int,...);
typedef VisualID (* PFN_XVisualIDFromVisual)(Visual*);
typedef int (* PFN_XWarpPointer)(Display*,Window,Window,int,int,unsigned int,unsigned int,int,int);
typedef void (* PFN_XkbFreeKeyboard)(XkbDescPtr,unsigned int,Bool);
@@ -191,6 +196,7 @@ typedef KeySym (* PFN_XkbKeycodeToKeysym)(Display*,KeyCode,int,int);
typedef Bool (* PFN_XkbQueryExtension)(Display*,int*,int*,int*,int*,int*);
typedef Bool (* PFN_XkbSelectEventDetails)(Display*,unsigned int,unsigned int,unsigned long,unsigned long);
typedef Bool (* PFN_XkbSetDetectableAutoRepeat)(Display*,Bool,Bool*);
+typedef char* (* PFN_XmbResetIC)(XIC);
typedef void (* PFN_XrmDestroyDatabase)(XrmDatabase);
typedef Bool (* PFN_XrmGetResource)(XrmDatabase,const char*,const char*,char**,XrmValue*);
typedef XrmDatabase (* PFN_XrmGetStringDatabase)(const char*);
@@ -265,6 +271,7 @@ typedef void (* PFN_Xutf8SetWMProperties)(Display*,Window,const char*,const char
#define XSetClassHint _glfw.x11.xlib.SetClassHint
#define XSetErrorHandler _glfw.x11.xlib.SetErrorHandler
#define XSetICFocus _glfw.x11.xlib.SetICFocus
+#define XSetICValues _glfw.x11.xlib.SetICValues
#define XSetIMValues _glfw.x11.xlib.SetIMValues
#define XSetInputFocus _glfw.x11.xlib.SetInputFocus
#define XSetLocaleModifiers _glfw.x11.xlib.SetLocaleModifiers
@@ -280,6 +287,7 @@ typedef void (* PFN_Xutf8SetWMProperties)(Display*,Window,const char*,const char
#define XUngrabPointer _glfw.x11.xlib.UngrabPointer
#define XUnmapWindow _glfw.x11.xlib.UnmapWindow
#define XUnsetICFocus _glfw.x11.xlib.UnsetICFocus
+#define XVaCreateNestedList _glfw.x11.xlib.VaCreateNestedList
#define XVisualIDFromVisual _glfw.x11.xlib.VisualIDFromVisual
#define XWarpPointer _glfw.x11.xlib.WarpPointer
#define XkbFreeKeyboard _glfw.x11.xkb.FreeKeyboard
@@ -291,6 +299,7 @@ typedef void (* PFN_Xutf8SetWMProperties)(Display*,Window,const char*,const char
#define XkbQueryExtension _glfw.x11.xkb.QueryExtension
#define XkbSelectEventDetails _glfw.x11.xkb.SelectEventDetails
#define XkbSetDetectableAutoRepeat _glfw.x11.xkb.SetDetectableAutoRepeat
+#define XmbResetIC _glfw.x11.xlib.mbResetIC
#define XrmDestroyDatabase _glfw.x11.xrm.DestroyDatabase
#define XrmGetResource _glfw.x11.xrm.GetResource
#define XrmGetStringDatabase _glfw.x11.xrm.GetStringDatabase
@@ -546,6 +555,17 @@ typedef struct _GLFWwindowX11
// The time of the last KeyPress event per keycode, for discarding
// duplicate key events generated for some keys by ibus
Time keyPressTimes[256];
+
+ // Preedit callbacks
+ XIMCallback preeditStartCallback;
+ XIMCallback preeditDoneCallback;
+ XIMCallback preeditDrawCallback;
+ XIMCallback preeditCaretCallback;
+ XIMCallback statusStartCallback;
+ XIMCallback statusDoneCallback;
+ XIMCallback statusDrawCallback;
+
+ int imeFocus;
} _GLFWwindowX11;
// X11-specific global data
@@ -566,6 +586,8 @@ typedef struct _GLFWlibraryX11
XContext context;
// XIM input method
XIM im;
+ // XIM input method style
+ XIMStyle imStyle;
// The previous X error handler, to be restored later
XErrorHandler errorHandler;
// Most recent error code received by X error handler
@@ -711,6 +733,7 @@ typedef struct _GLFWlibraryX11
PFN_XSetClassHint SetClassHint;
PFN_XSetErrorHandler SetErrorHandler;
PFN_XSetICFocus SetICFocus;
+ PFN_XSetICValues SetICValues;
PFN_XSetIMValues SetIMValues;
PFN_XSetInputFocus SetInputFocus;
PFN_XSetLocaleModifiers SetLocaleModifiers;
@@ -726,9 +749,11 @@ typedef struct _GLFWlibraryX11
PFN_XUngrabPointer UngrabPointer;
PFN_XUnmapWindow UnmapWindow;
PFN_XUnsetICFocus UnsetICFocus;
+ PFN_XVaCreateNestedList VaCreateNestedList;
PFN_XVisualIDFromVisual VisualIDFromVisual;
PFN_XWarpPointer WarpPointer;
PFN_XUnregisterIMInstantiateCallback UnregisterIMInstantiateCallback;
+ PFN_XmbResetIC mbResetIC;
PFN_Xutf8LookupString utf8LookupString;
PFN_Xutf8SetWMProperties utf8SetWMProperties;
} xlib;
@@ -955,6 +980,11 @@ void _glfwSetCursorX11(_GLFWwindow* window, _GLFWcursor* cursor);
void _glfwSetClipboardStringX11(const char* string);
const char* _glfwGetClipboardStringX11(void);
+void _glfwUpdatePreeditCursorRectangleX11(_GLFWwindow* window);
+void _glfwResetPreeditTextX11(_GLFWwindow* window);
+void _glfwSetIMEStatusX11(_GLFWwindow* window, int active);
+int _glfwGetIMEStatusX11(_GLFWwindow* window);
+
EGLenum _glfwGetEGLPlatformX11(EGLint** attribs);
EGLNativeDisplayType _glfwGetEGLNativeDisplayX11(void);
EGLNativeWindowType _glfwGetEGLNativeWindowX11(_GLFWwindow* window);
diff --git a/src/x11_window.c b/src/x11_window.c
index 322349f0..32d836e6 100644
--- a/src/x11_window.c
+++ b/src/x11_window.c
@@ -410,29 +410,6 @@ static void updateWindowMode(_GLFWwindow* window)
}
}
-// Decode a Unicode code point from a UTF-8 stream
-// Based on cutef8 by Jeff Bezanson (Public Domain)
-//
-static uint32_t decodeUTF8(const char** s)
-{
- uint32_t codepoint = 0, count = 0;
- static const uint32_t offsets[] =
- {
- 0x00000000u, 0x00003080u, 0x000e2080u,
- 0x03c82080u, 0xfa082080u, 0x82082080u
- };
-
- do
- {
- codepoint = (codepoint << 6) + (unsigned char) **s;
- (*s)++;
- count++;
- } while ((**s & 0xc0) == 0x80);
-
- assert(count <= 6);
- return codepoint - offsets[count - 1];
-}
-
// Convert the specified Latin-1 string to UTF-8
//
static char* convertLatin1toUTF8(const char* source)
@@ -561,6 +538,235 @@ static void inputContextDestroyCallback(XIC ic, XPointer clientData, XPointer ca
window->x11.ic = NULL;
}
+// IME Start callback (do nothing)
+//
+static void _ximPreeditStartCallback(XIC xic, XPointer clientData, XPointer callData)
+{
+}
+
+// IME Done callback (do nothing)
+//
+static void _ximPreeditDoneCallback(XIC xic, XPointer clientData, XPointer callData)
+{
+}
+
+// IME Draw callback
+// When using the dafault style: STYLE_OVERTHESPOT, this is not used since applications
+// don't need to display preedit texts.
+//
+static void _ximPreeditDrawCallback(XIC xic, XPointer clientData, XIMPreeditDrawCallbackStruct* callData)
+{
+ _GLFWwindow* window = (_GLFWwindow*) clientData;
+ _GLFWpreedit* preedit = &window->preedit;
+
+ if (!callData->text)
+ {
+ // preedit text is empty
+ preedit->textCount = 0;
+ preedit->blockSizesCount = 0;
+ preedit->focusedBlockIndex = 0;
+ preedit->caretIndex = 0;
+ _glfwInputPreedit(window);
+ return;
+ }
+ else if (callData->text->encoding_is_wchar)
+ {
+ // wchar is not supported
+ return;
+ }
+ else
+ {
+ XIMText* text = callData->text;
+ int textLen = preedit->textCount + text->length - callData->chg_length;
+ int textBufferCount = preedit->textBufferCount;
+ int i, j, rstart, rend;
+ const char* src;
+
+ // realloc preedit text
+ while (textBufferCount < textLen + 1)
+ textBufferCount = (textBufferCount == 0) ? 1 : textBufferCount * 2;
+ if (textBufferCount != preedit->textBufferCount)
+ {
+ unsigned int* preeditText = _glfw_realloc(preedit->text,
+ sizeof(unsigned int) * textBufferCount);
+ if (preeditText == NULL)
+ return;
+
+ preedit->text = preeditText;
+ preedit->textBufferCount = textBufferCount;
+ }
+ preedit->textCount = textLen;
+ preedit->text[textLen] = 0;
+
+ // realloc block sizes
+ if (preedit->blockSizesBufferCount == 0)
+ {
+ preedit->blockSizes = _glfw_calloc(4, sizeof(int));
+ preedit->blockSizesBufferCount = 4;
+ }
+
+ // store preedit text
+ src = text->string.multi_byte;
+ rend = 0;
+ rstart = textLen;
+ for (i = 0, j = callData->chg_first; i < text->length; i++)
+ {
+ XIMFeedback f;
+
+ if (i < callData->chg_first || callData->chg_first + textLen < i)
+ continue;
+
+ preedit->text[j++] = _glfwDecodeUTF8(&src);
+ f = text->feedback[i];
+ if ((f & XIMReverse) || (f & XIMHighlight))
+ {
+ rend = i;
+ if (i < rstart)
+ rstart = i;
+ }
+ }
+
+ // store block sizes
+ // TODO: It doesn't care callData->chg_first != 0 case although it's quite rare.
+ if (rstart == textLen)
+ {
+ preedit->blockSizesCount = 1;
+ preedit->blockSizes[0] = textLen;
+ preedit->blockSizes[1] = 0;
+ preedit->focusedBlockIndex = 0;
+ preedit->caretIndex = callData->caret;
+ _glfwInputPreedit(window);
+ }
+ else if (rstart == 0)
+ {
+ if (rend == textLen -1)
+ {
+ preedit->blockSizesCount = 1;
+ preedit->blockSizes[0] = textLen;
+ preedit->blockSizes[1] = 0;
+ preedit->focusedBlockIndex = 0;
+ preedit->caretIndex = callData->caret;
+ _glfwInputPreedit(window);
+ }
+ else
+ {
+ preedit->blockSizesCount = 2;
+ preedit->blockSizes[0] = rend + 1;
+ preedit->blockSizes[1] = textLen - rend - 1;
+ preedit->blockSizes[2] = 0;
+ preedit->focusedBlockIndex = 0;
+ preedit->caretIndex = callData->caret;
+ _glfwInputPreedit(window);
+ }
+ }
+ else if (rend == textLen - 1)
+ {
+ preedit->blockSizesCount = 2;
+ preedit->blockSizes[0] = rstart;
+ preedit->blockSizes[1] = textLen - rstart;
+ preedit->blockSizes[2] = 0;
+ preedit->focusedBlockIndex = 1;
+ preedit->caretIndex = callData->caret;
+ _glfwInputPreedit(window);
+ }
+ else
+ {
+ preedit->blockSizesCount = 3;
+ preedit->blockSizes[0] = rstart;
+ preedit->blockSizes[1] = rend - rstart + 1;
+ preedit->blockSizes[2] = textLen - rend - 1;
+ preedit->blockSizes[3] = 0;
+ preedit->focusedBlockIndex = 1;
+ preedit->caretIndex = callData->caret;
+ _glfwInputPreedit(window);
+ }
+ }
+}
+
+// IME Caret callback (do nothing)
+//
+static void _ximPreeditCaretCallback(XIC xic, XPointer clientData, XPointer callData)
+{
+}
+
+// IME Status Start callback
+// When using the dafault style: STYLE_OVERTHESPOT, this is not used and the IME status
+// can not be taken.
+//
+static void _ximStatusStartCallback(XIC xic, XPointer clientData, XPointer callData)
+{
+ _GLFWwindow* window = (_GLFWwindow*) clientData;
+ window->x11.imeFocus = GLFW_TRUE;
+}
+
+// IME Status Done callback
+// When using the dafault style: STYLE_OVERTHESPOT, this is not used and the IME status
+// can not be taken.
+//
+static void _ximStatusDoneCallback(XIC xic, XPointer clientData, XPointer callData)
+{
+ _GLFWwindow* window = (_GLFWwindow*) clientData;
+ window->x11.imeFocus = GLFW_FALSE;
+}
+
+// IME Status Draw callback
+// When using the dafault style: STYLE_OVERTHESPOT, this is not used and the IME status
+// can not be taken.
+//
+static void _ximStatusDrawCallback(XIC xic, XPointer clientData, XIMStatusDrawCallbackStruct* callData)
+{
+ _GLFWwindow* window = (_GLFWwindow*) clientData;
+ _glfwInputIMEStatus(window);
+}
+
+// Create XIM Preedit callback
+// When using the dafault style: STYLE_OVERTHESPOT, this is not used since applications
+// don't need to display preedit texts.
+//
+static XVaNestedList _createXIMPreeditCallbacks(_GLFWwindow* window)
+{
+ window->x11.preeditStartCallback.client_data = (XPointer) window;
+ window->x11.preeditStartCallback.callback = (XIMProc) _ximPreeditStartCallback;
+ window->x11.preeditDoneCallback.client_data = (XPointer) window;
+ window->x11.preeditDoneCallback.callback = (XIMProc) _ximPreeditDoneCallback;
+ window->x11.preeditDrawCallback.client_data = (XPointer) window;
+ window->x11.preeditDrawCallback.callback = (XIMProc) _ximPreeditDrawCallback;
+ window->x11.preeditCaretCallback.client_data = (XPointer) window;
+ window->x11.preeditCaretCallback.callback = (XIMProc) _ximPreeditCaretCallback;
+ return XVaCreateNestedList(0,
+ XNPreeditStartCallback,
+ &window->x11.preeditStartCallback.client_data,
+ XNPreeditDoneCallback,
+ &window->x11.preeditDoneCallback.client_data,
+ XNPreeditDrawCallback,
+ &window->x11.preeditDrawCallback.client_data,
+ XNPreeditCaretCallback,
+ &window->x11.preeditCaretCallback.client_data,
+ NULL);
+}
+
+// Create XIM status callback
+// When using the dafault style: STYLE_OVERTHESPOT, this is not used and the IME status
+// can not be taken.
+//
+static XVaNestedList _createXIMStatusCallbacks(_GLFWwindow* window)
+{
+ window->x11.statusStartCallback.client_data = (XPointer) window;
+ window->x11.statusStartCallback.callback = (XIMProc) _ximStatusStartCallback;
+ window->x11.statusDoneCallback.client_data = (XPointer) window;
+ window->x11.statusDoneCallback.callback = (XIMProc) _ximStatusDoneCallback;
+ window->x11.statusDrawCallback.client_data = (XPointer) window;
+ window->x11.statusDrawCallback.callback = (XIMProc) _ximStatusDrawCallback;
+ return XVaCreateNestedList(0,
+ XNStatusStartCallback,
+ &window->x11.statusStartCallback.client_data,
+ XNStatusDoneCallback,
+ &window->x11.statusDoneCallback.client_data,
+ XNStatusDrawCallback,
+ &window->x11.statusDrawCallback.client_data,
+ NULL);
+}
+
// Create the X11 window (and its colormap)
//
static GLFWbool createNativeWindow(_GLFWwindow* window,
@@ -1290,7 +1496,7 @@ static void processEvent(XEvent *event)
const char* c = chars;
chars[count] = '\0';
while (c - chars < count)
- _glfwInputChar(window, decodeUTF8(&c), mods, plain);
+ _glfwInputChar(window, _glfwDecodeUTF8(&c), mods, plain);
}
if (chars != buffer)
@@ -1925,16 +2131,55 @@ void _glfwCreateInputContextX11(_GLFWwindow* window)
callback.callback = (XIMProc) inputContextDestroyCallback;
callback.client_data = (XPointer) window;
- window->x11.ic = XCreateIC(_glfw.x11.im,
- XNInputStyle,
- XIMPreeditNothing | XIMStatusNothing,
- XNClientWindow,
- window->x11.handle,
- XNFocusWindow,
- window->x11.handle,
- XNDestroyCallback,
- &callback,
- NULL);
+ window->x11.imeFocus = GLFW_FALSE;
+
+ if (_glfw.x11.imStyle == STYLE_ONTHESPOT)
+ {
+ // On X11, on-the-spot style is unstable.
+ // Status callbacks are not called and the preedit cursor position
+ // can not be changed.
+ XVaNestedList preeditList = _createXIMPreeditCallbacks(window);
+ XVaNestedList statusList = _createXIMStatusCallbacks(window);
+
+ window->x11.ic = XCreateIC(_glfw.x11.im,
+ XNInputStyle,
+ _glfw.x11.imStyle,
+ XNClientWindow,
+ window->x11.handle,
+ XNFocusWindow,
+ window->x11.handle,
+ XNPreeditAttributes,
+ preeditList,
+ XNStatusAttributes,
+ statusList,
+ XNDestroyCallback,
+ &callback,
+ NULL);
+
+ XFree(preeditList);
+ XFree(statusList);
+ }
+ else if (_glfw.x11.imStyle == STYLE_OVERTHESPOT)
+ {
+ window->x11.ic = XCreateIC(_glfw.x11.im,
+ XNInputStyle,
+ _glfw.x11.imStyle,
+ XNClientWindow,
+ window->x11.handle,
+ XNFocusWindow,
+ window->x11.handle,
+ XNDestroyCallback,
+ &callback,
+ NULL);
+ }
+ else
+ {
+ // (XIMPreeditNothing | XIMStatusNothing) is considered as STYLE_OVERTHESPOT.
+ // So this branch should not be used now.
+ _glfwInputError(GLFW_PLATFORM_ERROR,
+ "X11: Failed to create input context.");
+ return;
+ }
if (window->x11.ic)
{
@@ -3084,6 +3329,92 @@ const char* _glfwGetClipboardStringX11(void)
return getSelectionString(_glfw.x11.CLIPBOARD);
}
+// When using STYLE_ONTHESPOT, this doesn't work and the cursor position can't be updated
+//
+void _glfwUpdatePreeditCursorRectangleX11(_GLFWwindow* window)
+{
+ XVaNestedList preedit_attr;
+ XPoint spot;
+ _GLFWpreedit* preedit = &window->preedit;
+
+ if (!window->x11.ic)
+ return;
+
+ spot.x = preedit->cursorPosX + preedit->cursorWidth;
+ spot.y = preedit->cursorPosY + preedit->cursorHeight;
+ preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL);
+ XSetICValues(window->x11.ic, XNPreeditAttributes, preedit_attr, NULL);
+ XFree(preedit_attr);
+}
+
+void _glfwResetPreeditTextX11(_GLFWwindow* window)
+{
+ XIC ic = window->x11.ic;
+ _GLFWpreedit* preedit = &window->preedit;
+
+ /* restore conversion state after resetting ic later */
+ XIMPreeditState preedit_state = XIMPreeditUnKnown;
+ XVaNestedList preedit_attr;
+ char* result;
+
+ if (!ic)
+ return;
+
+ // Can not manage IME in the case of over-the-spot.
+ if (_glfw.x11.imStyle == STYLE_OVERTHESPOT)
+ return;
+
+ if (preedit->textCount == 0)
+ return;
+
+ preedit_attr = XVaCreateNestedList(0, XNPreeditState, &preedit_state, NULL);
+ XGetICValues(ic, XNPreeditAttributes, preedit_attr, NULL);
+ XFree(preedit_attr);
+
+ result = XmbResetIC(ic);
+
+ preedit_attr = XVaCreateNestedList(0, XNPreeditState, preedit_state, NULL);
+ XSetICValues(ic, XNPreeditAttributes, preedit_attr, NULL);
+ XFree(preedit_attr);
+
+ preedit->textCount = 0;
+ preedit->blockSizesCount = 0;
+ preedit->focusedBlockIndex = 0;
+ preedit->caretIndex = 0;
+ _glfwInputPreedit(window);
+
+ XFree (result);
+}
+
+void _glfwSetIMEStatusX11(_GLFWwindow* window, int active)
+{
+ XIC ic = window->x11.ic;
+
+ if (!ic)
+ return;
+
+ // Can not manage IME in the case of over-the-spot.
+ if (_glfw.x11.imStyle == STYLE_OVERTHESPOT)
+ return;
+
+ if (active)
+ XSetICFocus(ic);
+ else
+ XUnsetICFocus(ic);
+}
+
+int _glfwGetIMEStatusX11(_GLFWwindow* window)
+{
+ if (!window->x11.ic)
+ return GLFW_FALSE;
+
+ // Can not manage IME in the case of over-the-spot.
+ if (_glfw.x11.imStyle == STYLE_OVERTHESPOT)
+ return GLFW_FALSE;
+
+ return window->x11.imeFocus;
+}
+
EGLenum _glfwGetEGLPlatformX11(EGLint** attribs)
{
if (_glfw.egl.ANGLE_platform_angle)
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index f81cfeb9..f28a53b6 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -32,6 +32,7 @@ add_executable(cursor cursor.c ${GLAD_GL})
add_executable(empty WIN32 MACOSX_BUNDLE empty.c ${TINYCTHREAD} ${GLAD_GL})
add_executable(gamma WIN32 MACOSX_BUNDLE gamma.c ${GLAD_GL})
add_executable(icon WIN32 MACOSX_BUNDLE icon.c ${GLAD_GL})
+add_executable(input_text WIN32 MACOSX_BUNDLE input_text.c ${GETOPT} ${GLAD_GL})
add_executable(inputlag WIN32 MACOSX_BUNDLE inputlag.c ${GETOPT} ${GLAD_GL})
add_executable(joysticks WIN32 MACOSX_BUNDLE joysticks.c ${GLAD_GL})
add_executable(tearing WIN32 MACOSX_BUNDLE tearing.c ${GLAD_GL})
@@ -48,8 +49,16 @@ if (RT_LIBRARY)
target_link_libraries(threads "${RT_LIBRARY}")
endif()
-set(GUI_ONLY_BINARIES empty gamma icon inputlag joysticks tearing threads
- timeout title triangle-vulkan window)
+if (GLFW_BUILD_X11 OR GLFW_BUILD_WAYLAND)
+ find_package(Fontconfig)
+ if (FONTCONFIG_FOUND)
+ target_compile_definitions(input_text PRIVATE FONTCONFIG_ENABLED)
+ target_link_libraries(input_text fontconfig)
+ endif()
+endif()
+
+set(GUI_ONLY_BINARIES empty gamma icon input_text inputlag joysticks tearing
+ threads timeout title triangle-vulkan window)
set(CONSOLE_BINARIES allocator clipboard events msaa glfwinfo iconify monitors
reopen cursor)
@@ -70,6 +79,7 @@ endif()
if (APPLE)
set_target_properties(empty PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Empty Event")
set_target_properties(gamma PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Gamma")
+ set_target_properties(input_text PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Input Text")
set_target_properties(inputlag PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Input Lag")
set_target_properties(joysticks PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Joysticks")
set_target_properties(tearing PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Tearing")
diff --git a/tests/events.c b/tests/events.c
index ab3b99a7..0ea3a957 100644
--- a/tests/events.c
+++ b/tests/events.c
@@ -464,6 +464,59 @@ static void char_callback(GLFWwindow* window, unsigned int codepoint)
counter++, slot->number, glfwGetTime(), codepoint, string);
}
+static void preedit_callback(GLFWwindow* window, int preeditCount,
+ unsigned int* preeditString, int blockCount,
+ int* blockSizes, int focusedBlock, int caret)
+{
+ Slot* slot = glfwGetWindowUserPointer(window);
+ int i, blockIndex = -1, remainingBlockSize = 0;
+ int width, height;
+ char encoded[5] = "";
+ size_t encodedCount = 0;
+ printf("%08x to %i at %0.3f: Preedit text ",
+ counter++, slot->number, glfwGetTime());
+ if (preeditCount == 0 || blockCount == 0)
+ {
+ printf("(empty)\n");
+ }
+ else
+ {
+ for (i = 0; i < preeditCount; i++)
+ {
+ if (remainingBlockSize == 0)
+ {
+ if (blockIndex == focusedBlock)
+ printf("]");
+ blockIndex++;
+ remainingBlockSize = blockSizes[blockIndex];
+ printf("\n block %d: ", blockIndex);
+ if (blockIndex == focusedBlock)
+ printf("[");
+ }
+ if (i == caret)
+ printf("|");
+ encodedCount = encode_utf8(encoded, preeditString[i]);
+ encoded[encodedCount] = '\0';
+ printf("%s", encoded);
+ remainingBlockSize--;
+ }
+ if (blockIndex == focusedBlock)
+ printf("]");
+ if (caret == preeditCount)
+ printf("|");
+ printf("\n");
+ glfwGetWindowSize(window, &width, &height);
+ glfwSetPreeditCursorRectangle(window, width/2, height/2, 1, 20);
+ }
+}
+
+static void ime_callback(GLFWwindow* window)
+{
+ Slot* slot = glfwGetWindowUserPointer(window);
+ printf("%08x to %i at %0.3f: IME switched\n",
+ counter++, slot->number, glfwGetTime());
+}
+
static void drop_callback(GLFWwindow* window, int count, const char* paths[])
{
int i;
@@ -649,6 +702,8 @@ int main(int argc, char** argv)
glfwSetScrollCallback(slots[i].window, scroll_callback);
glfwSetKeyCallback(slots[i].window, key_callback);
glfwSetCharCallback(slots[i].window, char_callback);
+ glfwSetPreeditCallback(slots[i].window, preedit_callback);
+ glfwSetIMEStatusCallback(slots[i].window, ime_callback);
glfwSetDropCallback(slots[i].window, drop_callback);
glfwMakeContextCurrent(slots[i].window);
diff --git a/tests/input_text.c b/tests/input_text.c
new file mode 100644
index 00000000..b2030f80
--- /dev/null
+++ b/tests/input_text.c
@@ -0,0 +1,838 @@
+//========================================================================
+// Input Test
+// Copyright (c) Camilla Löwy
+// Copyright (c) Daijiro Fukuda
+// Copyright (c) Takuro Ashie
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would
+// be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not
+// be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source
+// distribution.
+//
+//========================================================================
+//
+// For font handiling, I reffered to https://github.com/Immediate-Mode-UI/Nuklear/wiki/Complete-font-guide.
+// For nuklear handling, I reffered to tests/window.c.
+//
+// Currently, it is made for Japanese input only.
+// You have to select the correct font to display Japanese texts.
+// To handle other languages, you need to add correct ranges to nk_font_config.
+//
+// On X11 or Wayland, you can choose a font by GUI if "fontconfig" libarary is enabled.
+//
+// On Win32, "Yu Mincho" is selected by default if it is installed. This font is
+// included in the FOD packages, so it will be installed automatically when you
+// enable Japanese input on your environment, or you can install it by
+// "Manage optional features" in "Apps & features".
+// Refer: https://learn.microsoft.com/en-us/typography/fonts/windows_10_font_list#japanese-supplemental-fonts
+//
+// On macOS, "Arial Unicode MS" is selected by default if it is installed.
+// I assume that this font is usually installed, but if it is not installed,
+// please install it manually.
+//
+// You can also specify a TTF filepath and use your own favorite font by setting
+// TTF_FONT_FILEPATH below.
+//
+//========================================================================
+
+// Please comment out and set font filepath here to change default font
+// #define TTF_FONT_FILEPATH ""
+
+#define GLAD_GL_IMPLEMENTATION
+#include
+#define GLFW_INCLUDE_NONE
+#include
+
+#include
+
+#define NK_IMPLEMENTATION
+#define NK_INCLUDE_STANDARD_IO
+#define NK_KEYSTATE_BASED_INPUT
+#define NK_INCLUDE_FIXED_TYPES
+#define NK_INCLUDE_FONT_BAKING
+#define NK_INCLUDE_DEFAULT_FONT
+#define NK_INCLUDE_DEFAULT_ALLOCATOR
+#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
+#define NK_INCLUDE_STANDARD_VARARGS
+#define NK_BUTTON_TRIGGER_ON_RELEASE
+
+// To increase the number of characters that can be entered at one time
+#define NK_INPUT_MAX 64
+
+#include
+
+#define NK_GLFW_GL2_IMPLEMENTATION
+#include
+
+#include
+#include
+#include
+#include
+
+#include "getopt.h"
+
+#if defined(FONTCONFIG_ENABLED)
+ #include
+#endif
+
+#define MAX_BUFFER_LEN 1024
+
+// https://github.com/Immediate-Mode-UI/Nuklear/wiki/Complete-font-guide
+// https://unicode-table.com
+// To handle other languages, you need to fix these ranges.
+static nk_rune rangesJapan[] = {
+ 0x0020, 0x007E, // Basic Latin
+ 0x2000, 0x206F, // General Punctuation
+ 0x3000, 0x303F, // CJK Symbols and Punctuation
+ 0x3041, 0x309F, // Hiragana
+ 0x30A0, 0x30FF, // Katakana
+ 0x4E00, 0x9FFF, // All Kanji
+ 0xFF01, 0xFFEF, // Halfwidth and Fullwidth Forms
+ 0
+};
+
+#define MAX_FONTS_LEN 512
+#define MAX_FONT_FAMILY_NAME_LEN 128
+#define MAX_FONT_FILEPATH_LEN 256
+
+static struct nk_font* currentFont;
+static char** fontFamilyNames;
+static char** fontFilePaths;
+static int fontNum = 0;
+static int currentFontIndex = 0;
+
+static int currentIMEStatus = GLFW_FALSE;
+#define MAX_PREEDIT_LEN 128
+static char preeditBuf[MAX_PREEDIT_LEN] = "";
+
+// Assuming that the page-size is 10 at most.
+static char candidateBuf[9][MAX_PREEDIT_LEN];
+static int candidatePageSize = 0;
+
+void usage(void)
+{
+ printf("Usage: input_text [-h] [-s] [-c]\n");
+ printf("Options:\n");
+ printf(" -s Use on-the-spot sytle on X11. This is ignored on other platforms.\n");
+ printf(" -c Use manage-preedit-candidate on Win32. This is ignored on other platforms.\n");
+ printf(" -h Show this help\n");
+}
+
+static size_t encode_utf8(char* s, unsigned int ch)
+{
+ size_t count = 0;
+
+ if (ch < 0x80)
+ s[count++] = (char) ch;
+ else if (ch < 0x800)
+ {
+ s[count++] = (ch >> 6) | 0xc0;
+ s[count++] = (ch & 0x3f) | 0x80;
+ }
+ else if (ch < 0x10000)
+ {
+ s[count++] = (ch >> 12) | 0xe0;
+ s[count++] = ((ch >> 6) & 0x3f) | 0x80;
+ s[count++] = (ch & 0x3f) | 0x80;
+ }
+ else if (ch < 0x110000)
+ {
+ s[count++] = (ch >> 18) | 0xf0;
+ s[count++] = ((ch >> 12) & 0x3f) | 0x80;
+ s[count++] = ((ch >> 6) & 0x3f) | 0x80;
+ s[count++] = (ch & 0x3f) | 0x80;
+ }
+
+ return count;
+}
+
+static int add_font(const char* familyName, const char* ttfFilePath, int checkExistence)
+{
+ if (MAX_FONTS_LEN <= fontNum)
+ return GLFW_FALSE;
+
+ if (MAX_FONT_FAMILY_NAME_LEN <= strlen(familyName) || MAX_FONT_FILEPATH_LEN <= strlen(ttfFilePath))
+ return GLFW_FALSE;
+
+ if (checkExistence)
+ {
+ FILE* fp = fopen(ttfFilePath, "rb");
+ if (!fp)
+ return GLFW_FALSE;
+ fclose(fp);
+ }
+
+ fontFamilyNames[fontNum] = (char*) malloc(1 + strlen(familyName));
+ assert(fontFamilyNames[fontNum]);
+ strcpy(fontFamilyNames[fontNum], familyName);
+
+ fontFilePaths[fontNum] = (char*) malloc(1 + strlen(ttfFilePath));
+ assert(fontFilePaths[fontNum]);
+ strcpy(fontFilePaths[fontNum], ttfFilePath);
+
+ fontNum++;
+
+ return GLFW_TRUE;
+}
+
+static int replace_font(int index, const char* familyName, const char* ttfFilePath, int checkExistence)
+{
+ if (index == 0 || fontNum <= index)
+ return GLFW_FALSE;
+ if (MAX_FONT_FAMILY_NAME_LEN <= strlen(familyName) || MAX_FONT_FILEPATH_LEN <= strlen(ttfFilePath))
+ return GLFW_FALSE;
+
+ if (checkExistence)
+ {
+ FILE* fp = fopen(ttfFilePath, "rb");
+ if (!fp)
+ return GLFW_FALSE;
+ fclose(fp);
+ }
+
+ free(fontFamilyNames[index]);
+ free(fontFilePaths[index]);
+
+ fontFamilyNames[index] = (char*) malloc(1 + strlen(familyName));
+ assert(fontFamilyNames[index]);
+ strcpy(fontFamilyNames[index], familyName);
+
+ fontFilePaths[index] = (char*) malloc(1 + strlen(ttfFilePath));
+ assert(fontFilePaths[index]);
+ strcpy(fontFilePaths[index], ttfFilePath);
+
+ return GLFW_TRUE;
+}
+
+#if defined(TTF_FONT_FILEPATH)
+static int load_custom_font()
+{
+ if (MAX_FONTS_LEN <= fontNum)
+ return GLFW_FALSE;
+ if (!(TTF_FONT_FILEPATH && *TTF_FONT_FILEPATH))
+ return GLFW_FALSE;
+
+ return add_font("Custom", TTF_FONT_FILEPATH, GLFW_TRUE);
+}
+#endif
+
+#if defined(FONTCONFIG_ENABLED)
+static void load_font_list_by_fontconfig()
+{
+ FcConfig* config = FcInitLoadConfigAndFonts();
+ FcFontSet* fontset = FcConfigGetFonts(config, FcSetSystem);
+
+ if (!fontset)
+ {
+ printf("load_font_list_by_fontconfig failed.\n");
+ FcConfigDestroy(config);
+ return;
+ }
+
+ for (int i = 0; i < fontset->nfont; i++)
+ {
+ FcValue fvalue, dvalue;
+ if (FcResultMatch == FcPatternGet(fontset->fonts[i], FC_FAMILY, 0, &fvalue))
+ {
+ if (FcResultMatch == FcPatternGet(fontset->fonts[i], FC_FILE, 0, &dvalue))
+ {
+ const char* familyName = (const char*) fvalue.u.s;
+ const char* filePath = (const char*) dvalue.u.s;
+ int existsFamily = GLFW_FALSE;
+ int existingIndex = 0;
+
+ if (!strstr(filePath, ".ttf"))
+ {
+ continue;
+ }
+
+ for (int j = 1; j < fontNum; ++j)
+ {
+ if (strcmp(fontFamilyNames[j], familyName) == 0)
+ {
+ existsFamily = GLFW_TRUE;
+ existingIndex = j;
+ break;
+ }
+ }
+
+ if (existsFamily)
+ {
+ // Prefer "regular" to the others.
+ if (strstr(filePath, "regular") || strstr(filePath, "Regular"))
+ replace_font(existingIndex, familyName, filePath, GLFW_FALSE);
+ }
+ else
+ add_font(familyName, filePath, GLFW_FALSE);
+
+ if (MAX_FONTS_LEN <= fontNum)
+ {
+ printf("MAX_FONTS_LEN reached. Could not load some fonts.\n");
+ break;
+ }
+ }
+ }
+ }
+
+ FcConfigDestroy(config);
+}
+#endif
+
+static void load_default_font_for_each_platform()
+{
+ int hasSucceeded = GLFW_FALSE;
+ if (MAX_FONTS_LEN <= fontNum)
+ return;
+
+ if (glfwGetPlatform() == GLFW_PLATFORM_COCOA)
+ hasSucceeded = add_font("Arial Unicode MS", "/Library/Fonts/Arial Unicode.ttf", GLFW_TRUE);
+ else if(glfwGetPlatform() == GLFW_PLATFORM_WIN32)
+ {
+ // Use "Yu Mincho" since it is the only TTF for Japanese in the FOD packages on Windows10 and Windows11.
+ // https://learn.microsoft.com/en-us/typography/fonts/windows_10_font_list#japanese-supplemental-fonts
+ char filepath[MAX_FONT_FILEPATH_LEN];
+ char* winDir = getenv("systemroot");
+ if (winDir)
+ snprintf(filepath, MAX_FONT_FILEPATH_LEN, "%s\\Fonts\\Yumin.ttf", winDir);
+ else
+ strcpy(filepath, "C:\\Windows\\Fonts\\Yumin.ttf");
+ hasSucceeded = add_font("Yu Mincho Regular", filepath, GLFW_TRUE);
+ }
+
+ if (hasSucceeded)
+ currentFontIndex = fontNum - 1;
+}
+
+static void init_font_list()
+{
+ int useCustomFont = GLFW_FALSE;
+ int customFontIndex = 0;
+
+ fontFamilyNames = (char**) malloc(sizeof(char*) * MAX_FONTS_LEN);
+ assert(fontFamilyNames);
+ fontFilePaths = (char**) malloc(sizeof(char*) * MAX_FONTS_LEN);
+ assert(fontFilePaths);
+
+ fontFamilyNames[0] = "GLFW default";
+ fontFilePaths[0] = "";
+ fontNum++;
+
+#if defined(TTF_FONT_FILEPATH)
+ useCustomFont = load_custom_font();
+ if (useCustomFont)
+ customFontIndex = fontNum - 1;
+#endif
+
+ load_default_font_for_each_platform();
+
+#if defined(FONTCONFIG_ENABLED)
+ load_font_list_by_fontconfig();
+#endif
+
+ if (useCustomFont)
+ currentFontIndex = customFontIndex;
+}
+
+static void deinit_font_list()
+{
+ for (int i = 1; i < fontNum; ++i)
+ {
+ free(fontFamilyNames[i]);
+ free(fontFilePaths[i]);
+ }
+
+ free(fontFamilyNames);
+ free(fontFilePaths);
+}
+
+// https://github.com/Immediate-Mode-UI/Nuklear/wiki/Complete-font-guide
+static void update_font(struct nk_context* nk, float height)
+{
+ struct nk_font_atlas* atlas;
+
+ nk_glfw3_font_stash_begin(&atlas);
+
+ if (currentFontIndex == 0)
+ {
+ currentFont = nk_font_atlas_add_default(atlas, height, 0);
+ }
+ else
+ {
+ struct nk_font* new_font;
+ struct nk_font_config cfg;
+ cfg = nk_font_config(0);
+ cfg.range = rangesJapan;
+ cfg.oversample_h = 1;
+ cfg.oversample_v = 1;
+ cfg.pixel_snap = true;
+
+ new_font = nk_font_atlas_add_from_file(atlas, fontFilePaths[currentFontIndex], height, &cfg);
+ if (new_font)
+ {
+ currentFont = new_font;
+ printf("Succeeded to load font file: %s\n", fontFilePaths[currentFontIndex]);
+ }
+ else
+ printf("Failed to load font file: %s\n", fontFilePaths[currentFontIndex]);
+ }
+
+ nk_glfw3_font_stash_end();
+ nk_style_set_font(nk, ¤tFont->handle);
+}
+
+static void set_menu_buttons(GLFWwindow* window, struct nk_context* nk, int height)
+{
+ static int windowedX, windowedY, windowedWidth, windowedHeight;
+
+ nk_layout_row_dynamic(nk, height, 2);
+ if (nk_button_label(nk, "Toggle Fullscreen"))
+ {
+ if (glfwGetWindowMonitor(window))
+ {
+ glfwSetWindowMonitor(window, NULL,
+ windowedX, windowedY,
+ windowedWidth, windowedHeight, 0);
+ }
+ else
+ {
+ GLFWmonitor* monitor = glfwGetPrimaryMonitor();
+ const GLFWvidmode* mode = glfwGetVideoMode(monitor);
+ glfwGetWindowPos(window, &windowedX, &windowedY);
+ glfwGetWindowSize(window, &windowedWidth, &windowedHeight);
+ glfwSetWindowMonitor(window, monitor,
+ 0, 0, mode->width, mode->height,
+ mode->refreshRate);
+ }
+ }
+
+ {
+ int auto_iconify = glfwGetWindowAttrib(window, GLFW_AUTO_ICONIFY);
+ if (nk_checkbox_label(nk, "Auto Iconify", &auto_iconify))
+ glfwSetWindowAttrib(window, GLFW_AUTO_ICONIFY, auto_iconify);
+ }
+}
+
+static int set_font_selecter(GLFWwindow* window, struct nk_context* nk, int height, int fontHeight)
+{
+ int newSelectedIndex;
+
+ nk_layout_row_begin(nk, NK_DYNAMIC, height, 2);
+
+ nk_layout_row_push(nk, 1.f / 3.f);
+ nk_label(nk, "Font", NK_TEXT_LEFT);
+
+ nk_layout_row_push(nk, 2.f / 3.f);
+ newSelectedIndex = nk_combo(nk, (const char**) fontFamilyNames, fontNum, currentFontIndex, fontHeight, nk_vec2(300, 400));
+
+ nk_layout_row_end(nk);
+
+ if (newSelectedIndex == currentFontIndex)
+ return GLFW_FALSE;
+
+ currentFontIndex = newSelectedIndex;
+ return GLFW_TRUE;
+}
+
+static void set_ime_buttons(GLFWwindow* window, struct nk_context* nk, int height)
+{
+ nk_layout_row_dynamic(nk, height, 2);
+
+ if (nk_button_label(nk, "Toggle IME status"))
+ {
+ glfwSetInputMode(window, GLFW_IME, !currentIMEStatus);
+ }
+
+ if (nk_button_label(nk, "Reset preedit text"))
+ {
+ glfwResetPreeditText(window);
+ }
+}
+
+static void set_preedit_cursor_edit(GLFWwindow* window, struct nk_context* nk, int height, int* isAutoUpdating)
+{
+ static int lastX = -1, lastY = -1, lastW = -1, lastH = -1;
+ static char xBuf[12] = "", yBuf[12] = "", wBuf[12] = "", hBuf[12] = "";
+
+ const nk_flags flags = NK_EDIT_FIELD |
+ NK_EDIT_SIG_ENTER |
+ NK_EDIT_GOTO_END_ON_ACTIVATE;
+ nk_flags events;
+ int x, y, w, h;
+
+ glfwGetPreeditCursorRectangle(window, &x, &y, &w, &h);
+
+ if (x != lastX)
+ sprintf(xBuf, "%i", x);
+ if (y != lastY)
+ sprintf(yBuf, "%i", y);
+ if (w != lastW)
+ sprintf(wBuf, "%i", w);
+ if (h != lastH)
+ sprintf(hBuf, "%i", h);
+
+ nk_layout_row_begin(nk, NK_DYNAMIC, height, 5);
+
+ nk_layout_row_push(nk, 4.f / 9.f);
+ nk_label(nk, "Preedit cursor (x,y,w,h)", NK_TEXT_LEFT);
+
+ nk_layout_row_push(nk, 1.f / 9.f);
+ events = nk_edit_string_zero_terminated(nk, flags, xBuf,
+ sizeof(xBuf),
+ nk_filter_decimal);
+ if (events & NK_EDIT_COMMITED)
+ {
+ x = atoi(xBuf);
+ *isAutoUpdating = GLFW_FALSE;
+ glfwSetPreeditCursorRectangle(window, x, y, w, h);
+ }
+ else if (events & NK_EDIT_DEACTIVATED)
+ sprintf(xBuf, "%i", x);
+
+ nk_layout_row_push(nk, 1.f / 9.f);
+ events = nk_edit_string_zero_terminated(nk, flags, yBuf,
+ sizeof(yBuf),
+ nk_filter_decimal);
+ if (events & NK_EDIT_COMMITED)
+ {
+ y = atoi(yBuf);
+ *isAutoUpdating = GLFW_FALSE;
+ glfwSetPreeditCursorRectangle(window, x, y, w, h);
+ }
+ else if (events & NK_EDIT_DEACTIVATED)
+ sprintf(yBuf, "%i", y);
+
+ nk_layout_row_push(nk, 1.f / 9.f);
+ events = nk_edit_string_zero_terminated(nk, flags, wBuf,
+ sizeof(wBuf),
+ nk_filter_decimal);
+ if (events & NK_EDIT_COMMITED)
+ {
+ w = atoi(wBuf);
+ *isAutoUpdating = GLFW_FALSE;
+ glfwSetPreeditCursorRectangle(window, x, y, w, h);
+ }
+ else if (events & NK_EDIT_DEACTIVATED)
+ sprintf(wBuf, "%i", w);
+
+ nk_layout_row_push(nk, 1.f / 9.f);
+ events = nk_edit_string_zero_terminated(nk, flags, hBuf,
+ sizeof(hBuf),
+ nk_filter_decimal);
+ if (events & NK_EDIT_COMMITED)
+ {
+ h = atoi(hBuf);
+ *isAutoUpdating = GLFW_FALSE;
+ glfwSetPreeditCursorRectangle(window, x, y, w, h);
+ }
+ else if (events & NK_EDIT_DEACTIVATED)
+ sprintf(hBuf, "%i", h);
+
+ nk_layout_row_push(nk, 1.f / 9.f);
+ nk_checkbox_label(nk, "Auto", isAutoUpdating);
+
+ nk_layout_row_end(nk);
+
+ lastX = x;
+ lastY = y;
+ lastW = w;
+ lastH = h;
+}
+
+static void set_ime_stauts_labels(GLFWwindow* window, struct nk_context* nk, int height)
+{
+ nk_layout_row_dynamic(nk, height, 1);
+ nk_value_bool(nk, "IME status", currentIMEStatus);
+}
+
+static void set_preedit_labels(GLFWwindow* window, struct nk_context* nk, int height)
+{
+ nk_layout_row_begin(nk, NK_DYNAMIC, height, 5);
+
+ nk_layout_row_push(nk, 1.f / 3.f);
+ nk_label(nk, "Preedit info:", NK_TEXT_LEFT);
+
+ nk_layout_row_push(nk, 2.f / 3.f);
+ nk_label(nk, (const char*) preeditBuf, NK_TEXT_LEFT);
+
+ nk_layout_row_end(nk);
+}
+
+static void set_candidate_labels(GLFWwindow* window, struct nk_context* nk)
+{
+ for (int i = 0; i < 5; ++i)
+ {
+ nk_layout_row_begin(nk, NK_DYNAMIC, 30, 3);
+ nk_layout_row_push(nk, 1.f / 3.f);
+ if (i == 0)
+ nk_label(nk, "Candidates:", NK_TEXT_LEFT);
+ else
+ nk_label(nk, "", NK_TEXT_LEFT);
+ nk_layout_row_push(nk, 1.f / 3.f);
+ if (candidatePageSize > i)
+ nk_label(nk, (const char*) candidateBuf[i], NK_TEXT_LEFT);
+ nk_layout_row_push(nk, 1.f / 3.f);
+ if (candidatePageSize > i + 5)
+ nk_label(nk, (const char*) candidateBuf[i + 5], NK_TEXT_LEFT);
+ nk_layout_row_end(nk);
+ }
+}
+
+// If it is possible to take the text-cursor position calculated in `nk_do_edit` function in `deps/nuklear.h`,
+// we can set preedit-cursor position more easily.
+// However, there doesn't seem to be a way to do that, so this does a simplified calculation only for the end
+// of the text. (Can not trace the cursor movement)
+static void update_cursor_pos(GLFWwindow* window, struct nk_context* nk, struct nk_user_font* f,
+ char* boxBuffer, int boxLen, int boxX, int boxY)
+{
+ float lineWidth = 0;
+ int totalLines = 1;
+
+ const char* text;
+ int textPos = 0;
+
+ struct nk_str nkString;
+ nk_str_init_fixed(&nkString, boxBuffer, (nk_size) MAX_BUFFER_LEN);
+ nkString.buffer.allocated = (nk_size) boxLen;
+ nkString.len = nk_utf_len(boxBuffer, boxLen);
+
+ text = nk_str_get_const(&nkString);
+
+ while (textPos < boxLen)
+ {
+ nk_rune unicode = 0;
+ int remainedBoxLen = boxLen - textPos;
+ int nextGlyphSize = nk_utf_decode(text + textPos, &unicode, remainedBoxLen);
+ if (!nextGlyphSize)
+ break;
+
+ if (unicode == '\n')
+ {
+ textPos++;
+ totalLines++;
+ lineWidth = 0;
+ continue;
+ }
+
+ textPos += nextGlyphSize;
+ lineWidth += f->width(f->userdata, f->height, text + textPos, nextGlyphSize);
+ }
+
+ {
+ int lineHeight = f->height + nk->style.edit.row_padding;
+
+ int cursorPosX = boxX + lineWidth;
+ int cursorPosY = boxY + lineHeight * (totalLines - 1);
+ int cursorHeight = lineHeight;
+ int cursorWidth;
+
+ // Keep the value of width since it doesn't need to be updated.
+ glfwGetPreeditCursorRectangle(window, NULL, NULL, &cursorWidth, NULL);
+
+ glfwSetPreeditCursorRectangle(window, cursorPosX, cursorPosY, cursorWidth, cursorHeight);
+ }
+}
+
+static void ime_callback(GLFWwindow* window)
+{
+ currentIMEStatus = glfwGetInputMode(window, GLFW_IME);
+ printf("IME switched: %s\n", currentIMEStatus ? "ON" : "OFF");
+}
+
+static void preedit_callback(GLFWwindow* window, int preeditCount,
+ unsigned int* preeditString, int blockCount,
+ int* blockSizes, int focusedBlock, int caret)
+{
+ int blockIndex = -1, remainingBlockSize = 0;
+ if (preeditCount == 0 || blockCount == 0)
+ {
+ strcpy(preeditBuf, "(empty)");
+ return;
+ }
+
+ strcpy(preeditBuf, "");
+
+ for (int i = 0; i < preeditCount; i++)
+ {
+ char encoded[5] = "";
+ size_t encodedCount = 0;
+
+ if (i == caret)
+ {
+ if (strlen(preeditBuf) + strlen("|") < MAX_PREEDIT_LEN)
+ strcat(preeditBuf, "|");
+ }
+ if (remainingBlockSize == 0)
+ {
+ if (blockIndex == focusedBlock)
+ {
+ if (strlen(preeditBuf) + strlen("]") < MAX_PREEDIT_LEN)
+ strcat(preeditBuf, "]");
+ }
+ blockIndex++;
+ remainingBlockSize = blockSizes[blockIndex];
+ if (blockIndex == focusedBlock)
+ {
+ if (strlen(preeditBuf) + strlen("[") < MAX_PREEDIT_LEN)
+ strcat(preeditBuf, "[");
+ }
+ }
+ encodedCount = encode_utf8(encoded, preeditString[i]);
+ encoded[encodedCount] = '\0';
+ if (strlen(preeditBuf) + strlen(encoded) < MAX_PREEDIT_LEN)
+ strcat(preeditBuf, encoded);
+ remainingBlockSize--;
+ }
+ if (blockIndex == focusedBlock)
+ {
+ if (strlen(preeditBuf) + strlen("]") < MAX_PREEDIT_LEN)
+ strcat(preeditBuf, "]");
+ }
+ if (caret == preeditCount)
+ {
+ if (strlen(preeditBuf) + strlen("|") < MAX_PREEDIT_LEN)
+ strcat(preeditBuf, "|");
+ }
+}
+
+static void candidate_callback(GLFWwindow* window, int candidates_count,
+ int selected_index, int page_start, int page_size)
+{
+ int i, j;
+ candidatePageSize = page_size;
+ for (i = 0; i < page_size; ++i)
+ {
+ int index = i + page_start;
+ int textCount;
+ unsigned int* text = glfwGetPreeditCandidate(window, index, &textCount);
+ if (index == selected_index)
+ strcpy(candidateBuf[i], "> ");
+ else
+ strcpy(candidateBuf[i], "");
+ for (j = 0; j < textCount; ++j)
+ {
+ char encoded[5] = "";
+ encode_utf8(encoded, text[j]);
+ if (strlen(candidateBuf[i]) + strlen(encoded) < MAX_PREEDIT_LEN)
+ strcat(candidateBuf[i], encoded);
+ }
+ }
+}
+
+int main(int argc, char** argv)
+{
+ GLFWwindow* window;
+ struct nk_context* nk;
+ int width, height;
+ char boxBuffer[MAX_BUFFER_LEN] = "Input text here.";
+ int boxLen = strlen(boxBuffer);
+ int isAutoUpdatingCursorPosEnabled = GLFW_TRUE;
+ int managePreeditCandidate = GLFW_FALSE;
+ int ch;
+
+ while ((ch = getopt(argc, argv, "hsc")) != -1)
+ {
+ switch (ch)
+ {
+ case 'h':
+ usage();
+ exit(EXIT_SUCCESS);
+
+ case 's':
+ glfwInitHint(GLFW_X11_ONTHESPOT, GLFW_TRUE);
+ break;
+
+ case 'c':
+ glfwInitHint(GLFW_MANAGE_PREEDIT_CANDIDATE, GLFW_TRUE);
+ managePreeditCandidate = GLFW_TRUE;
+ break;
+ }
+ }
+
+ if (!glfwInit())
+ exit(EXIT_FAILURE);
+
+ glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
+ glfwWindowHint(GLFW_WIN32_KEYBOARD_MENU, GLFW_TRUE);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
+
+ window = glfwCreateWindow(600, 600, "Input Text", NULL, NULL);
+ if (!window)
+ {
+ glfwTerminate();
+ exit(EXIT_FAILURE);
+ }
+
+ currentIMEStatus = glfwGetInputMode(window, GLFW_IME);
+ glfwSetPreeditCursorRectangle(window, 0, 0, 1, 1);
+ glfwSetIMEStatusCallback(window, ime_callback);
+ glfwSetPreeditCallback(window, preedit_callback);
+ glfwSetPreeditCandidateCallback(window, candidate_callback);
+
+ glfwMakeContextCurrent(window);
+ gladLoadGL(glfwGetProcAddress);
+ glfwSwapInterval(0);
+
+ nk = nk_glfw3_init(window, NK_GLFW3_INSTALL_CALLBACKS);
+ init_font_list();
+ update_font(nk, 18);
+
+ while (!glfwWindowShouldClose(window))
+ {
+ struct nk_rect area;
+
+ glfwGetWindowSize(window, &width, &height);
+
+ area = nk_rect(0.f, 0.f, (float) width, (float) height);
+ nk_window_set_bounds(nk, "main", area);
+
+ nk_glfw3_new_frame();
+ if (nk_begin(nk, "main", area, 0))
+ {
+ set_menu_buttons(window, nk, 30);
+ if (set_font_selecter(window, nk, 30, 18))
+ update_font(nk, 18);
+ set_ime_buttons(window, nk, 30);
+ set_preedit_cursor_edit(window, nk, 30, &isAutoUpdatingCursorPosEnabled);
+ set_ime_stauts_labels(window, nk, 30);
+ set_preedit_labels(window, nk, 30);
+ if (managePreeditCandidate)
+ set_candidate_labels(window, nk);
+
+ nk_layout_row_dynamic(nk, height - 250, 1);
+ nk_edit_string(nk, NK_EDIT_BOX, boxBuffer, &boxLen, MAX_BUFFER_LEN, nk_filter_default);
+ }
+ nk_end(nk);
+
+ glClear(GL_COLOR_BUFFER_BIT);
+ nk_glfw3_render(NK_ANTI_ALIASING_ON);
+ glfwSwapBuffers(window);
+
+ if (isAutoUpdatingCursorPosEnabled)
+ // I don't know how to get the layout info of `nk_edit_string`.
+ update_cursor_pos(window, nk, ¤tFont->handle, boxBuffer, boxLen, 10,
+ managePreeditCandidate ? 385 : 220);
+
+ glfwWaitEvents();
+ }
+
+ deinit_font_list();
+
+ nk_glfw3_shutdown();
+ glfwTerminate();
+ exit(EXIT_SUCCESS);
+}