Implement text-input and input-method protocol support, v2 #235

Open
Shugyousha wants to merge 13 commits from Shugyousha/input-protocols-v2 into main
Shugyousha commented 2022-05-15 16:09:56 +02:00 (Migrated from github.com)

This is a rebased version of https://github.com/djpohly/dwl/pull/12. Using anthywl and wlroots 0.15.1 you should be able to input Japanese and see the input popup as well.

Note that there still some issues like the input popup position not updating properly when changing the layout while the popup is shown. If we decide to include this functionality in dwl, it's probably worth investing more time to figure out how to make this work properly.

Until then this code is mostly here so people can experiment with IMEs and their functionality in dwl (at least until these text-input and input-method protocols get fixed and more stable; see the old PR mentioned above for details).

This is a rebased version of https://github.com/djpohly/dwl/pull/12. Using `anthywl` and `wlroots` 0.15.1 you should be able to input Japanese and see the input popup as well. Note that there still some issues like the input popup position not updating properly when changing the layout while the popup is shown. If we decide to include this functionality in dwl, it's probably worth investing more time to figure out how to make this work properly. Until then this code is mostly here so people can experiment with IMEs and their functionality in dwl (at least until these `text-input` and `input-method` protocols get fixed and more stable; see the old PR mentioned above for details).
sevz17 commented 2022-05-15 23:00:56 +02:00 (Migrated from github.com)
see Shugyousha/dwl#1
Shugyousha commented 2022-05-16 21:35:53 +02:00 (Migrated from github.com)

see Shugyousha#1

hm, I must have totally missed that one last year for some reason ...

personally, I am not a fan of the "nospacesstyle" because it makes things harder to read in my opinion (I was actually contemplating to discuss opening a PR for dwl to change everything to snake_case ...). If djpohly prefers this style though, I can change it.

> see [Shugyousha#1](https://github.com/Shugyousha/dwl/pull/1) hm, I must have totally missed that one last year for some reason ... personally, I am not a fan of the "nospacesstyle" because it makes things harder to read in my opinion (I was actually contemplating to discuss opening a PR for dwl to change everything to snake_case ...). If djpohly prefers this style though, I can change it.
sevz17 commented 2022-05-16 23:02:45 +02:00 (Migrated from github.com)

personally, I am not a fan of the "nospacesstyle" because it makes things harder to read in my opinion (I was actually contemplating to discuss opening a PR for dwl to change everything to snake_case ...). If djpohly prefers this style though, I can change it.

I think the same, but it comes from the dwm style

> personally, I am not a fan of the "nospacesstyle" because it makes things harder to read in my opinion (I was actually contemplating to discuss opening a PR for dwl to change everything to snake_case ...). If djpohly prefers this style though, I can change it. I think the same, but it comes from the dwm style
sevz17 (Migrated from github.com) reviewed 2022-05-20 06:39:32 +02:00
sevz17 (Migrated from github.com) commented 2022-05-20 03:28:14 +02:00

This is included below.

This is included below.
@ -28,3 +29,4 @@
#include <wlr/types/wlr_input_inhibitor.h>
#include <wlr/types/wlr_input_method_v2.h>
#include <wlr/types/wlr_keyboard.h>
#include <wlr/types/wlr_layer_shell_v1.h>
sevz17 (Migrated from github.com) commented 2022-05-20 03:28:39 +02:00

No need to reorder.

No need to reorder.
sevz17 (Migrated from github.com) commented 2022-05-20 03:29:34 +02:00

This is not used anymore.

This is not used anymore.
sevz17 (Migrated from github.com) commented 2022-05-20 03:38:21 +02:00

double blank line.

double blank line.
sevz17 (Migrated from github.com) commented 2022-05-20 03:34:29 +02:00

This funcs, are not needed, and also client_from_wlr_surface() will crash if you send a wlr_surface that is a xwayland surface

This funcs, are not needed, and also `client_from_wlr_surface()` will crash if you send a wlr_surface that is a xwayland surface
sevz17 (Migrated from github.com) commented 2022-05-20 03:36:26 +02:00

use the LISTEN macro

use the `LISTEN` macro
sevz17 (Migrated from github.com) commented 2022-05-20 03:37:07 +02:00

The same as above

The same as above
sevz17 (Migrated from github.com) commented 2022-05-20 03:37:28 +02:00

The same as above

The same as above
sevz17 (Migrated from github.com) commented 2022-05-20 03:39:09 +02:00

Use the LISTEN macro

Use the `LISTEN` macro
sevz17 (Migrated from github.com) commented 2022-05-20 03:35:18 +02:00

Is really necessary add a listener for this?

Is really necessary add a listener for this?
sevz17 (Migrated from github.com) commented 2022-05-20 03:27:39 +02:00

This is never freed.

This is never freed.
guyuming76 commented 2022-05-20 07:52:21 +02:00 (Migrated from github.com)

@Shugyousha , i works on my machine. I think what follows is the magic lines

20220520_13h29m26s_grim

And all those popup->link , popup->view_link lines of code are useless now, and we may remove them, right?

@Shugyousha , i works on my machine. I think what follows is the magic lines ![20220520_13h29m26s_grim](https://user-images.githubusercontent.com/12045548/169459406-709aef52-f839-49ae-b02e-ad3c0694ee1a.png) **And all those popup->link , popup->view_link lines of code are useless now, and we may remove them, right?**
guyuming76 commented 2022-05-20 12:06:33 +02:00 (Migrated from github.com)

@Shugyousha , just found some issue:

Issue 1:
when i open alacritty in tag 1 and press Ctrl+space to switch to Chinese input, its fine;

but then i press Alt+2 to switch to tag 2, and continue to type, there will be input popup in tag 2, notice thitat currently, there is not application open in tag 2 yet, and the committed Chinese input will appear in the alacritty in tag 1.

Issue 2: it might be related to issue 1: i switch to a new tag, open alacritty in new tag and input some Chinese , then i go back to the original tag, sometimes, i cannot switch to Chinese again in the original tag. I have not found the exact procedure to reproduce this, but by switching to new tag and switching back, chances are that you might lose the ability to turn on input. However, after some switching between tags, you can have input popup again.

@Shugyousha , just found some issue: Issue 1: when i open alacritty in tag 1 and press Ctrl+space to switch to Chinese input, its fine; but then i press Alt+2 to switch to tag 2, and continue to type, there will be input popup in tag 2, notice thitat currently, there is not application open in tag 2 yet, and the committed Chinese input will appear in the alacritty in tag 1. Issue 2: it might be related to issue 1: i switch to a new tag, open alacritty in new tag and input some Chinese , then i go back to the original tag, sometimes, i cannot switch to Chinese again in the original tag. I have not found the exact procedure to reproduce this, but by switching to new tag and switching back, chances are that you might lose the ability to turn on input. However, after some switching between tags, you can have input popup again.
sevz17 commented 2022-05-20 16:38:39 +02:00 (Migrated from github.com)

@Shugyousha , just found some issue:

Issue 1: when i open alacritty in tag 1 and press Ctrl+space to switch to Chinese input, its fine;

but then i press Alt+2 to switch to tag 2, and continue to type, there will be input popup in tag 2, notice thitat currently, there is not application open in tag 2 yet, and the committed Chinese input will appear in the alacritty in tag 1.

May be solved assigning tags to input popups, or even better popups should be children of the clients.

> @Shugyousha , just found some issue: > > Issue 1: when i open alacritty in tag 1 and press Ctrl+space to switch to Chinese input, its fine; > > but then i press Alt+2 to switch to tag 2, and continue to type, there will be input popup in tag 2, notice thitat currently, there is not application open in tag 2 yet, and the committed Chinese input will appear in the alacritty in tag 1. May be solved assigning tags to input popups, or even better popups should be children of the clients.
guyuming76 commented 2022-05-21 01:16:13 +02:00 (Migrated from github.com)

to reproduce issue2, xwayland in config.mk needs to be enabled.

to reproduce issue2, xwayland in config.mk needs to be enabled.
sevz17 commented 2022-05-21 01:18:51 +02:00 (Migrated from github.com)

to reproduce issue2, xwayland in config.mk needs to be enabled.

So, are you running alacritty through xwayland?

> to reproduce issue2, xwayland in config.mk needs to be enabled. So, are you running alacritty through xwayland?
guyuming76 (Migrated from github.com) reviewed 2022-05-21 01:38:54 +02:00
guyuming76 (Migrated from github.com) commented 2022-05-21 01:38:54 +02:00

I suggest keeping one piece of the code above commented as sample bellow the LISTEM macro definition. It took me some time to understand what the LISTEN definition mean. And those code which don't use the LISTEN macro helped me to understand at last.

I suggest keeping one piece of the code above commented as sample bellow the LISTEM macro definition. It took me some time to understand what the LISTEN definition mean. And those code which don't use the LISTEN macro helped me to understand at last.
guyuming76 commented 2022-05-21 02:15:06 +02:00 (Migrated from github.com)

to reproduce issue2, xwayland in config.mk needs to be enabled.

So, are you running alacritty through xwayland?

I don't think so.

if i run xwininfo, and then click a geogebra window, it will return xwindow information;
but if i run xwininfo and then click an alacritty window, nothing returns.

Any other way to check whether alacritty runs through xwayland?

> > to reproduce issue2, xwayland in config.mk needs to be enabled. > > So, are you running alacritty through xwayland? I don't think so. if i run xwininfo, and then click a geogebra window, it will return xwindow information; but if i run xwininfo and then click an alacritty window, nothing returns. Any other way to check whether alacritty runs through xwayland?
sevz17 commented 2022-05-21 02:48:23 +02:00 (Migrated from github.com)

Any other way to check whether alacritty runs through xwayland?

xeyes

> Any other way to check whether alacritty runs through xwayland? xeyes
guyuming76 commented 2022-05-21 06:16:56 +02:00 (Migrated from github.com)

May be solved assigning tags to input popups, or even better popups should be children of the clients.

  1. popups should be children of the clients. Does this mean that children can only be shown inside the client rectangle? what if the client rectangle is very small?

  2. assigning tags to popups. Sounds fine. But, when i switch to an empty tag, there is no popup at first. After i press a key, the popup appears in the empty tag. The problem seems to be why my key press has been routed to the previous client(or text_input associated with that client or anything else. I am not sure about the underlying process, have not traced the code yet) while i am now in an empty tag. When i leave a client to a new tag, no key press should be routed to the client as i understand. And my test to DWL without text_input works as expected. I mean, i am not sure whether this is cause by text_input and input_method or their usage in this PR

> May be solved assigning tags to input popups, or even better popups should be children of the clients. 1. popups should be children of the clients. Does this mean that children can only be shown inside the client rectangle? what if the client rectangle is very small? 2. assigning tags to popups. Sounds fine. But, when i switch to an empty tag, there is no popup at first. After i press a key, the popup appears in the empty tag. The problem seems to be why my key press has been routed to the previous client(or text_input associated with that client or anything else. I am not sure about the underlying process, have not traced the code yet) while i am now in an empty tag. When i leave a client to a new tag, no key press should be routed to the client as i understand. And my test to DWL without text_input works as expected. I mean, i am not sure whether this is cause by text_input and input_method or their usage in this PR
sevz17 commented 2022-05-21 06:32:09 +02:00 (Migrated from github.com)
  1. popups should be children of the clients. Does this mean that children can only be shown inside the client rectangle? what if the client rectangle is very small?

The current behavior: constrain popups to monitor size

  1. assigning tags to popups. Sounds fine. But, when i switch to an empty tag, there is no popup at first. After i press a key, the popup appears in the empty tag. The problem seems to be why my key press has been routed to the previous client(or text_input associated with that client) while i am now in an empty tag.

Sounds like a bug of this implementation, we should check this and keyboard_get_im_grab()

> 1. popups should be children of the clients. Does this mean that children can only be shown inside the client rectangle? what if the client rectangle is very small? The current behavior: constrain popups to monitor size > 2. assigning tags to popups. Sounds fine. But, when i switch to an empty tag, there is no popup at first. After i press a key, the popup appears in the empty tag. The problem seems to be why my key press has been routed to the previous client(or text_input associated with that client) while i am now in an empty tag. Sounds like a bug of this implementation, we should check [this](https://github.com/djpohly/dwl/pull/235/files#diff-f471c124277e21c02d19d16fd4e86287656f1aae579dc7820ff33c9b2403a9a1R1425-R1434) and [keyboard_get_im_grab()](https://github.com/djpohly/dwl/pull/235/files#diff-f471c124277e21c02d19d16fd4e86287656f1aae579dc7820ff33c9b2403a9a1R1385-R1395)
guyuming76 commented 2022-05-21 15:37:03 +02:00 (Migrated from github.com)

Sounds like a bug of this implementation, we should check this and keyboard_get_im_grab()

after some debugging, i find that:

  1. when i press MOD+3 to switch to tag3, callstack view->focusclient->client_activate_surface->wlr_xdg_toplevel_set_activated will be called to deactivate a surface which i guess correspond to the old client.
  2. in wlr_xdg_toplevel_set_activated, surface->toplevel->scheduled.activated is set to false, where surface is of type wlr_xdg_surface;
  3. in keyboard_get_im_grab, wl_resource_get_client(input_method->keyboard_grab->resource) return wl_client ;
  4. I want to try adding a condition in keyboard_get_im_grab to check the activated status set in step 2, but what we can get as in step 3 is a wl_client, how can we get a wlr_xdg_surface from a wl_client, or from input_method->keyboard_grab->resource ?

And if this can be done, why not just suppress the key in the following wlr_input_method_keyboard_grab_v2_xxxxxxx method calls due to some client surface is not activated?

And in step 1, wlr_seat_keyboard_notify_clear_focus are called in focusclient if no client, but it does not seem to have effect on input_method->keyboard_grab. I am not sure about what keyboard_grab mean, but why wlr_seat_keyboard_notify_clear_focus don't prevent input_method from accepting key here?

Anyway, i tried a simple but "brutal" fix here: guyuming76/dwl@3e55353b3d , which seems to fix Issue 1 i mentioned above. But issue 2 still exist. Actually, i still have no idea about what cause issue 2.

By the way, creating popup in layers[lyrFloat] seems to conflict with existing floating window features. If i press MOD and drag an alacritty window to float, input method popup won't appear in that window.

UPDATE: found a better fix for issue1: guyuming76/dwl@342f9658ca

UPDATE: looks like the cause of Issue2 i mentioned above can be "fixed" this way: guyuming76/dwl@1edd23d818 . But this will crash DWL when press key in a tag with no client. The question is, no way to disable a specific text_input in wlroots?

UPDATE: to my surprise, this seem to fix Issue2: guyuming76/dwl@6fd7a417f5

> Sounds like a bug of this implementation, we should check [this](https://github.com/djpohly/dwl/pull/235/files#diff-f471c124277e21c02d19d16fd4e86287656f1aae579dc7820ff33c9b2403a9a1R1425-R1434) and [keyboard_get_im_grab()](https://github.com/djpohly/dwl/pull/235/files#diff-f471c124277e21c02d19d16fd4e86287656f1aae579dc7820ff33c9b2403a9a1R1385-R1395) after some debugging, i find that: 1. when i press MOD+3 to switch to tag3, callstack view->focusclient->client_activate_surface->wlr_xdg_toplevel_set_activated will be called to deactivate a surface which i guess correspond to the old client. 2. in wlr_xdg_toplevel_set_activated, surface->toplevel->scheduled.activated is set to false, where surface is of type wlr_xdg_surface; 3. in keyboard_get_im_grab, wl_resource_get_client(input_method->keyboard_grab->resource) return wl_client ; 4. I want to try adding a condition in keyboard_get_im_grab to check the activated status set in step 2, but what we can get as in step 3 is a wl_client, how can we get a wlr_xdg_surface from a wl_client, or from input_method->keyboard_grab->resource ? And if this can be done, why not just suppress the key in the following wlr_input_method_keyboard_grab_v2_xxxxxxx method calls due to some client surface is not activated? And in step 1, wlr_seat_keyboard_notify_clear_focus are called in focusclient if no client, but it does not seem to have effect on input_method->keyboard_grab. I am not sure about what keyboard_grab mean, but why wlr_seat_keyboard_notify_clear_focus don't prevent input_method from accepting key here? Anyway, i tried a simple but "brutal" fix here: https://gitee.com/guyuming76/dwl/commit/3e55353b3d5094540eb89c33e5875f16c086a223 , which seems to fix Issue 1 i mentioned above. But issue 2 still exist. Actually, i still have no idea about what cause issue 2. By the way, creating popup in layers[lyrFloat] seems to conflict with existing floating window features. If i press MOD and drag an alacritty window to float, input method popup won't appear in that window. UPDATE: found a better fix for issue1: https://gitee.com/guyuming76/dwl/commit/342f9658ca5a03b149f7fdb35d212497e083cf3b UPDATE: looks like the cause of Issue2 i mentioned above can be "fixed" this way: https://gitee.com/guyuming76/dwl/commit/1edd23d81864cb7845820e55d638f68a6dbe81f5 . But this will crash DWL when press key in a tag with no client. The question is, no way to disable a specific text_input in wlroots? UPDATE: to my surprise, this seem to fix Issue2: https://gitee.com/guyuming76/dwl/commit/6fd7a417f53bcf70a1cfd6d40e15fefec0753907
Shugyousha (Migrated from github.com) reviewed 2022-05-22 14:58:16 +02:00
Shugyousha (Migrated from github.com) commented 2022-05-22 14:58:16 +02:00

Not a fan of the macro either but I have changed it.

Not a fan of the macro either but I have changed it.
Shugyousha (Migrated from github.com) reviewed 2022-05-22 14:58:59 +02:00
Shugyousha (Migrated from github.com) commented 2022-05-22 14:58:58 +02:00

I think it makes sense to make the initialisation consistent so I will change it everywhere.

I think it makes sense to make the initialisation consistent so I will change it everywhere.
Shugyousha (Migrated from github.com) reviewed 2022-05-22 15:05:00 +02:00
Shugyousha (Migrated from github.com) commented 2022-05-22 15:05:00 +02:00

This funcs, are not needed, and also

If you prefer I can call wlr_xdg_surface_from_wlr_surface(surface)->data; directly?

client_from_wlr_surface() will crash if you send a wlr_surface that is a xwayland surface

AFAICT this function is only used for the input text protocol handling so I don't think any XWayland surfaces are able to be passed to this function. Not 100% sure about this though.

> This funcs, are not needed, and also If you prefer I can call `wlr_xdg_surface_from_wlr_surface(surface)->data;` directly? > client_from_wlr_surface() will crash if you send a wlr_surface that is a xwayland surface AFAICT this function is only used for the input text protocol handling so I don't think any XWayland surfaces are able to be passed to this function. Not 100% sure about this though.
Shugyousha (Migrated from github.com) reviewed 2022-05-22 15:21:09 +02:00
Shugyousha (Migrated from github.com) commented 2022-05-22 15:21:08 +02:00

I assume that the Wayland protocol requires that the state is updated when a commit request is sent.
When I removed the code and made the handler a no-op, it didn't seem to make a different when doing some light testing ...

I assume that the Wayland protocol requires that the state is updated when a `commit` request is sent. When I removed the code and made the handler a no-op, it didn't seem to make a different when doing some light testing ...
Shugyousha commented 2022-05-22 15:36:13 +02:00 (Migrated from github.com)

And all those popup->link , popup->view_link lines of code are useless now, and we may remove them, right?

Yes, I think so. I have removed them, thanks!

> And all those popup->link , popup->view_link lines of code are useless now, and we may remove them, right? Yes, I think so. I have removed them, thanks!
guyuming76 commented 2022-05-23 08:49:56 +02:00 (Migrated from github.com)

i am curious why wlr_input_method_v2_send_deactivate(relay->input_method) and relay_disable_text_input are needed. So, i tried removing them (https://gitee.com/guyuming76/dwl/commits/PR235_5) . It works except that DWL crash if i try to input Chinese in geogebra (xwayland). But i still does not understand why.

i am curious why wlr_input_method_v2_send_deactivate(relay->input_method) and relay_disable_text_input are needed. So, i tried removing them (https://gitee.com/guyuming76/dwl/commits/PR235_5) . It works except that DWL crash if i try to input Chinese in geogebra (xwayland). But i still does not understand why.
guyuming76 commented 2022-05-23 13:38:01 +02:00 (Migrated from github.com)

why did we call input_popup_update so often? i removed most of them and have not got error yet: (guyuming76/dwl@1cd75e7706 )

why did we call input_popup_update so often? i removed most of them and have not got error yet: (https://gitee.com/guyuming76/dwl/commit/1cd75e7706925120235cd94dbf7325427baba3c8 )
sevz17 commented 2022-05-23 16:08:29 +02:00 (Migrated from github.com)

i am curious why wlr_input_method_v2_send_deactivate(relay->input_method) and relay_disable_text_input are needed. So, i tried removing them (https://gitee.com/guyuming76/dwl/commits/PR235_5) . It works except that DWL crash if i try to input Chinese in geogebra (xwayland). But i still does not understand why.

See https://github.com/djpohly/dwl/pull/235#discussion_r877658137

> i am curious why wlr_input_method_v2_send_deactivate(relay->input_method) and relay_disable_text_input are needed. So, i tried removing them (https://gitee.com/guyuming76/dwl/commits/PR235_5) . It works except that DWL crash if i try to input Chinese in geogebra (xwayland). But i still does not understand why. See https://github.com/djpohly/dwl/pull/235#discussion_r877658137
sevz17 (Migrated from github.com) reviewed 2022-05-23 18:17:48 +02:00
sevz17 (Migrated from github.com) commented 2022-05-23 18:17:48 +02:00

If you prefer I can call wlr_xdg_surface_from_wlr_surface(surface)->data; directly?

client_from_wlr_surface() will crash if you send a wlr_surface that is a xwayland surface

AFAICT this function is only used for the input text protocol handling so I don't think any XWayland surfaces are able to be passed to this function. Not 100% sure about this though.

I pushed a replace for this function.

> If you prefer I can call `wlr_xdg_surface_from_wlr_surface(surface)->data;` directly? > > > client_from_wlr_surface() will crash if you send a wlr_surface that is a xwayland surface > > AFAICT this function is only used for the input text protocol handling so I don't think any XWayland surfaces are able to be passed to this function. Not 100% sure about this though. I pushed a replace for this function.
guyuming76 commented 2022-05-24 11:11:08 +02:00 (Migrated from github.com)

just found a new issue (call it Issue3 hereafter): with sloppyfocus=0, when i click into the Chinese input popup, dwl crash.
if sloppyfocus=1, dwl crash as soon as i move mouse onto the input popup, which i never noticed before,although i also had never paid attention to the sloppyfocus setting.

dwl_popup_click_crash_PR235_branch1

UPDATE: fixed with a new separate layer for input popup: guyuming76/dwl@79fd6df77d

just found a new issue (call it Issue3 hereafter): with sloppyfocus=0, when i click into the Chinese input popup, dwl crash. if sloppyfocus=1, dwl crash as soon as i move mouse onto the input popup, which i never noticed before,although i also had never paid attention to the sloppyfocus setting. ![dwl_popup_click_crash_PR235_branch1](https://user-images.githubusercontent.com/12045548/169995574-eea67795-5604-4fba-8323-2612fc04ea9f.png) UPDATE: fixed with a new separate layer for input popup: https://gitee.com/guyuming76/dwl/commit/79fd6df77db336c3518ceca2053a6739bd96c452
guyuming76 commented 2022-05-25 10:34:24 +02:00 (Migrated from github.com)

See #235 (comment)

I don't quite understand the difference between surface->role_data->data and surface->data->data ; but since i find in the creation of Client, surface->data->data is used, i think use the same in client_from_wlr_surface make sense.

I merged recent changes from dwl here: https://gitee.com/guyuming76/dwl/tree/PR235_7

> See [#235 (comment)](https://github.com/djpohly/dwl/pull/235#discussion_r877658137) I don't quite understand the difference between surface->role_data->data and surface->data->data ; but since i find in the creation of Client, surface->data->data is used, i think use the same in client_from_wlr_surface make sense. I merged recent changes from dwl here: https://gitee.com/guyuming76/dwl/tree/PR235_7
guyuming76 commented 2022-06-06 05:12:10 +02:00 (Migrated from github.com)

@Shugyousha @Sevz17
input method suddenly stopped to work on my machine: the popup does not appear anymore.

So i added some wlr_log entries to help troubleshooting: https://gitee.com/guyuming76/dwl/commits/PR235_8

The log shows that input_method->keyboard_grab return false in function keyboard_get_im_grab.

but i don't know why this happens.

In wlr_input_method_v2.c , input_method->keyboard_grab is initialized in im_grab_keyboard, but i don't know what triggers im_grab_keyboard.

any suggestion?

UPDATE: turned out a silly mistake,i fixed it with: guyuming76/dwl@7ad12a9aaa

@Shugyousha @Sevz17 input method suddenly stopped to work on my machine: the popup does not appear anymore. So i added some wlr_log entries to help troubleshooting: https://gitee.com/guyuming76/dwl/commits/PR235_8 The log shows that input_method->keyboard_grab return false in function keyboard_get_im_grab. but i don't know why this happens. In wlr_input_method_v2.c , input_method->keyboard_grab is initialized in im_grab_keyboard, but i don't know what triggers im_grab_keyboard. any suggestion? UPDATE: turned out a silly mistake,i fixed it with: https://gitee.com/guyuming76/dwl/commit/7ad12a9aaa8aa561a5dd6e469917f0415dd9739b
Shugyousha (Migrated from github.com) reviewed 2022-06-06 13:15:05 +02:00
Shugyousha (Migrated from github.com) commented 2022-06-06 13:15:04 +02:00

I have merged your changes and used the function you added, thanks!

I have merged your changes and used the function you added, thanks!
Shugyousha commented 2022-06-06 13:22:46 +02:00 (Migrated from github.com)

i am curious why wlr_input_method_v2_send_deactivate(relay->input_method) and relay_disable_text_input are needed. So, i tried removing them (https://gitee.com/guyuming76/dwl/commits/PR235_5) . It works except that DWL crash if i try to input Chinese in geogebra (xwayland). But i still does not understand why.

See https://github.com/djpohly/dwl/pull/235#discussion_r877658137

This should be fixed now that I have merged your changes.

>> i am curious why wlr_input_method_v2_send_deactivate(relay->input_method) and relay_disable_text_input are needed. So, i tried removing them (https://gitee.com/guyuming76/dwl/commits/PR235_5) . It works except that DWL crash if i try to input Chinese in geogebra (xwayland). But i still does not understand why. > > See https://github.com/djpohly/dwl/pull/235#discussion_r877658137 This should be fixed now that I have merged your changes.
Shugyousha commented 2022-06-12 14:27:39 +02:00 (Migrated from github.com)

why did we call input_popup_update so often? i removed most of them and have not got error yet: (guyuming76/dwl@1cd75e7706 )

input_popup_update updates the popup state and also calls wlr_input_popup_surface_v2_send_text_input_rectangle. The latter will send the text_input_rectangle event to the input method v2 client. AFAIK anthywl is not processing this event but in theory another input method implementation could depend on it being sent.

> why did we call input_popup_update so often? i removed most of them and have not got error yet: (https://gitee.com/guyuming76/dwl/commit/1cd75e7706925120235cd94dbf7325427baba3c8 ) `input_popup_update` updates the popup state and also calls `wlr_input_popup_surface_v2_send_text_input_rectangle`. The latter will send the `text_input_rectangle` event to the input method v2 client. AFAIK anthywl is not processing this event but in theory another input method implementation could depend on it being sent.
Shugyousha commented 2022-06-12 14:53:33 +02:00 (Migrated from github.com)

By the way, creating popup in layers[lyrFloat] seems to conflict with existing floating window features. If i press MOD and drag an alacritty window to float, input method popup won't appear in that window.

I have changed the code to use its own layer for the input popups. I assume that this will fix this issue.

> By the way, creating popup in layers[lyrFloat] seems to conflict with existing floating window features. If i press MOD and drag an alacritty window to float, input method popup won't appear in that window. I have changed the code to use its own layer for the input popups. I assume that this will fix this issue.
guyuming76 commented 2022-06-15 10:22:25 +02:00 (Migrated from github.com)

@Shugyousha @Sevz17

recently, i added support to onclick in my waybar custom module, so that i can use mouse click to switch between tags, besides the MOD+num keypress. it works by running "wtype -M alt 2 -m alt" command on mouse click.

And i got sigsegv exception as in screenshot:

dwl_keyboard_modifier_null

so, i tried to fix in guyuming76/dwl@226155c196

not sure whether the fix is correct, anyway i don't have the sigsegv exception now.

@Shugyousha @Sevz17 recently, i added support to onclick in my waybar custom module, so that i can use mouse click to switch between tags, besides the MOD+num keypress. it works by running "wtype -M alt 2 -m alt" command on mouse click. And i got sigsegv exception as in screenshot: ![dwl_keyboard_modifier_null](https://user-images.githubusercontent.com/12045548/173777795-0e1cc7d7-94b4-479f-8b4e-353a2e2f3f9b.png) so, i tried to fix in https://gitee.com/guyuming76/dwl/commit/226155c196c17be9bc4bfb08c35a27fd1b37037a not sure whether the fix is correct, anyway i don't have the sigsegv exception now.
fauxmight commented 2022-06-15 13:52:52 +02:00 (Migrated from github.com)

recently, i added support to onclick in my waybar custom module, so that i can use mouse click to switch between tags, besides the MOD+num keypress. it works by running "wtype -M alt 2 -m alt" command on mouse click.

@guyuming76 Where is your custom waybar module hosted?

> recently, i added support to onclick in my waybar custom module, so that i can use mouse click to switch between tags, besides the MOD+num keypress. it works by running "wtype -M alt 2 -m alt" command on mouse click. @guyuming76 Where is your custom waybar module hosted?
guyuming76 commented 2022-06-16 05:04:46 +02:00 (Migrated from github.com)

@fauxmight

@guyuming76 Where is your custom waybar module hosted?

I put the files for my waybar here, if login is required, github account is supported: https://gitee.com/guyuming76/personal/tree/dwl/gentoo/waybar-dwl

And the README here https://gitee.com/guyuming76/dwl include my guide to start dwl with waybar.

@fauxmight > @guyuming76 Where is your custom waybar module hosted? I put the files for my waybar here, if login is required, github account is supported: https://gitee.com/guyuming76/personal/tree/dwl/gentoo/waybar-dwl And the README here https://gitee.com/guyuming76/dwl include my guide to start dwl with waybar.
guyuming76 commented 2022-06-16 05:22:47 +02:00 (Migrated from github.com)

@Shugyousha @Sevz17

I made a new change: guyuming76/dwl@1df4b18d7f

to address an issue which may reproduce as follows:

  1. start dwl
  2. MOD+enter to start alacritty in TAG 1
  3. switch to TAG 2 with mouse click
  4. switch back to TAG 1 with mouse click
  5. repeat step 3,4 several times, until when you find that you cannot click to switch to TAG 2 or any other TAG again; press a key, such as 'a', you will be able to switch to other TAG again with mouse click

With this change, i don't have the issue anymore, the change is notdeactivating and activating input_method again and again, instead, only activate input_method only once on new. And the change is only for situation where XWAYLAND is not compiled in.

@Shugyousha @Sevz17 I made a new change: https://gitee.com/guyuming76/dwl/commit/1df4b18d7f93d63f2abd43d663623387d71afdac to address an issue which may reproduce as follows: 1. start dwl 2. MOD+enter to start alacritty in TAG 1 3. switch to TAG 2 with mouse click 4. switch back to TAG 1 with mouse click 5. repeat step 3,4 several times, until when you find that you cannot click to switch to TAG 2 or any other TAG again; press a key, such as 'a', you will be able to switch to other TAG again with mouse click With this change, i don't have the issue anymore, the change is notdeactivating and activating input_method again and again, instead, only activate input_method only once on new. And the change is only for situation where XWAYLAND is not compiled in.
Shugyousha commented 2022-06-26 14:20:13 +02:00 (Migrated from github.com)

@Shugyousha , just found some issue:
Issue 1: when i open alacritty in tag 1 and press Ctrl+space to switch to Chinese input, its fine;
but then i press Alt+2 to switch to tag 2, and continue to type, there will be input popup in tag 2, notice thitat currently, there is not application open in tag 2 yet, and the committed Chinese input will appear in the alacritty in tag 1.

May be solved assigning tags to input popups, or even better popups should be children of the clients.

I have pushed a change to add a list of input popups to the Client and used it to deactivate the popup scene if the client is not visible on a tag.

> > @Shugyousha , just found some issue: > > Issue 1: when i open alacritty in tag 1 and press Ctrl+space to switch to Chinese input, its fine; > > but then i press Alt+2 to switch to tag 2, and continue to type, there will be input popup in tag 2, notice thitat currently, there is not application open in tag 2 yet, and the committed Chinese input will appear in the alacritty in tag 1. > > May be solved assigning tags to input popups, or even better popups should be children of the clients. I have pushed a change to add a list of input popups to the Client and used it to deactivate the popup scene if the client is not visible on a tag.
guyuming76 commented 2022-07-20 00:36:18 +02:00 (Migrated from github.com)

@Shugyousha @sevz17
when i open search engine in firefox, such as bing and baidu, and enable text input in the search box and type, very often, the input popup will appear outside the firefox window, at its upper left hand, instead of below the cursor.

after adding wlr_log entries, i found that it is related to the cursor_rect in input_popup_update method, when the WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE is not set.

is it a wlroots problem or not?

@Shugyousha @sevz17 when i open search engine in firefox, such as bing and baidu, and enable text input in the search box and type, very often, the input popup will appear outside the firefox window, at its upper left hand, instead of below the cursor. after adding wlr_log entries, i found that it is related to the cursor_rect in input_popup_update method, when the WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE is not set. is it a wlroots problem or not?
Shugyousha commented 2022-07-20 22:19:53 +02:00 (Migrated from github.com)

after adding wlr_log entries, i found that it is related to the cursor_rect in input_popup_update method, when the WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE is not set.

I think if the text-input implementation used in the client (so firefox in this case) is not sending this information (in time?) this feature flag will not be set and the dwl implementation cannot get the cursor position. In that case the popup x and y coordinate will be set to 0 and the popup will show up in a corner.

> after adding wlr_log entries, i found that it is related to the cursor_rect in input_popup_update method, when the WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE is not set. I *think* if the text-input implementation used in the client (so firefox in this case) is not sending this information (in time?) this feature flag will not be set and the dwl implementation cannot get the cursor position. In that case the popup `x` and `y` coordinate will be set to 0 and the popup will show up in a corner.
guyuming76 commented 2022-07-22 13:54:03 +02:00 (Migrated from github.com)

@Shugyousha
thanks! in my log, i can see the call to text_input_set_cursor_rectangle shortly after input_popup_update, but i don't know how to double check in code whether this is due to the client(firefox) or wlroots.

Anyway, i make a change to input_popup_update here: guyuming76/dwl@c5019437ca

When !cursor_rect, i set
popup->x=parent.x;
popup->y=parent.y;
popup->visible=true;

That is, show popup at upper left corner of the parent surface.

Before the change popup->y will set to some negative value.

Many lines of code seems involved in this change only because they are indented after being added into the if (!cursor_rect) else branch.

@Shugyousha thanks! in my log, i can see the call to text_input_set_cursor_rectangle shortly after input_popup_update, but i don't know how to double check in code whether this is due to the client(firefox) or wlroots. Anyway, i make a change to input_popup_update here: https://gitee.com/guyuming76/dwl/commit/c5019437ca7f360d1518cb9a45d1db670fdba277 When !cursor_rect, i set popup->x=parent.x; popup->y=parent.y; popup->visible=true; That is, show popup at upper left corner of the parent surface. Before the change popup->y will set to some negative value. Many lines of code seems involved in this change only because they are indented after being added into the if (!cursor_rect) else branch.
guyuming76 commented 2022-11-25 14:31:33 +01:00 (Migrated from github.com)

@Shugyousha , what do you use for Japanese IME?

I tried fcitx5 with fcitx5-anthy these days, but it is NOT working normally,although:
fcitx5 Chinese works with this PR;
fcitx5 Chinese works with DWM;
fcitx5 Anthy works with DWM;

With Chinese, in both DWM and DWL, there is only one kind of popup for input.

With Anthy in DWM, there are two kinds of popup for input, one displays your type-ins, and the other(which appear after you press space) let you make selection.

But with Anthy in this PR, the first kind of popup won't appear, only the second appears.

@Shugyousha , what do you use for Japanese IME? I tried fcitx5 with fcitx5-anthy these days, but it is NOT working normally,although: fcitx5 Chinese works with this PR; fcitx5 Chinese works with DWM; fcitx5 Anthy works with DWM; With Chinese, in both DWM and DWL, there is only one kind of popup for input. With Anthy in DWM, there are two kinds of popup for input, one displays your type-ins, and the other(which appear after you press space) let you make selection. But with Anthy in this PR, the first kind of popup won't appear, only the second appears.
Shugyousha commented 2022-11-25 21:43:14 +01:00 (Migrated from github.com)

Shugyousha , what do you use for Japanese IME?

I was using https://github.com/tadeokondrak/anthywl for testing. From what I remember, there is only one popup for this one.

> Shugyousha , what do you use for Japanese IME? I was using https://github.com/tadeokondrak/anthywl for testing. From what I remember, there is only one popup for this one.
tienbuigia commented 2023-02-18 18:29:57 +01:00 (Migrated from github.com)

does anyone still working on this? I'm an user who really need to type asian language and love dwl.

does anyone still working on this? I'm an user who really need to type asian language and love dwl.
Contributor

The patch seems to work fine in most cases, but one issue is that when I use (Mod + cursor) to move two floating Windows that support text-input (such as two terminals), after I have moved the first window by holding down the Mod key, If you continue to move the second window without releasing the mod key, you will find that it cannot be used at all.

Through debugging, it is found that when executing this code , the status of all mod keys will be automatically clear, regardless of whether you continue to hold down the mod key.

wlr_text_input_v3_send_enter(text_input->input, surface);

I studied it for a long time, but I couldn't find a solution.

the code line in the line 2627 which in function dwl_input_method_relay_set_focus.

Can someone help, please?

The patch seems to work fine in most cases, but one issue is that when I use (Mod + cursor) to move two floating Windows that support text-input (such as two terminals), after I have moved the first window by holding down the Mod key, If you continue to move the second window without releasing the mod key, you will find that it cannot be used at all. Through debugging, it is found that when executing this code , the status of all mod keys will be automatically clear, regardless of whether you continue to hold down the mod key. ```c wlr_text_input_v3_send_enter(text_input->input, surface); ``` I studied it for a long time, but I couldn't find a solution. the code line in the line 2627 which in function `dwl_input_method_relay_set_focus`. Can someone help, please?
Owner

@DreamMaoMao I have tested this using two alacritty terminals and cannot reproduce the issue. What specific terminal are you using that has this issue?

My mistake. I was testing without the patch applied.

~~@DreamMaoMao I have tested this using two alacritty terminals and cannot reproduce the issue. What specific terminal are you using that has this issue?~~ My mistake. I was testing without the patch applied.
Contributor

@DreamMaoMao I have tested this using two alacritty terminals and cannot reproduce the issue. What specific terminal are you using that has this issue?

i use foot terminal.

> @DreamMaoMao I have tested this using two alacritty terminals and cannot reproduce the issue. What specific terminal are you using that has this issue? i use foot terminal.
Owner

My mistake. I was testing without the patch applied.

My mistake. I was testing without the patch applied.
Contributor

image

how can I fix this update for wlroots,

![image](/attachments/fb0e1b88-6433-423c-9098-e2de48c69b0c) how can I fix this update for wlroots,
274 KiB
Contributor

@DreamMaoMao wrote in #235 (comment):

image

how can I fix this update for wlroots,我如何修复wlroots的此更新,

image

fix by add the four line code

@DreamMaoMao wrote in https://codeberg.org/dwl/dwl/pulls/235#issuecomment-2927533: > ![image](/dwl/dwl/attachments/fb0e1b88-6433-423c-9098-e2de48c69b0c) > > how can I fix this update for wlroots,我如何修复wlroots的此更新, ![image](/attachments/0d054cdc-0498-4d4e-897c-fcd73d6b34fd) fix by add the four line code
188 KiB
Member

I see that the readme says this will be considered "once ibus implements input-method v2." As the latest ibus release candidate claims support, are we interested in revisiting this?

I've had some success adapting the patch for dwl 0.7 (LaKato/dwl@edfc90ef94), and it appears to work with anthywl (with occasional issues with the popup like not always appearing), as well as both anthy and mozc under fcitx5 (no popup issues), though I haven't yet tried ibus.

I papered over a segfault in arrange() from referencing a popup's scene member before it's set, though I suspect it could be solved more properly. I've also realized that moving the cursor over the popup (I think specifically bringing it into focus) is causing a segfault when calling wl_list_remove() in focusclient(), though I'm yet to fully understand or resolve it.

If we still want to support these protocols (either in mainline or a patch), I'd be happy to work with someone to test and resolve remaining problems.

I see that the readme says this will be considered "once ibus implements input-method v2." As [the latest ibus release candidate](https://github.com/ibus/ibus/releases/tag/1.5.32-rc1) claims support, are we interested in revisiting this? I've had some success adapting the patch for dwl 0.7 (LaKato/dwl@edfc90ef94), and it appears to work with anthywl (with occasional issues with the popup like not always appearing), as well as both anthy and mozc under fcitx5 (no popup issues), though I haven't yet tried ibus. I papered over a segfault in `arrange()` from referencing a popup's `scene` member before it's set, though I suspect it could be solved more properly. I've also realized that moving the cursor over the popup (I think specifically bringing it into focus) is causing a segfault when calling `wl_list_remove()` in `focusclient()`, though I'm yet to fully understand or resolve it. If we still want to support these protocols (either in mainline or a patch), I'd be happy to work with someone to test and resolve remaining problems.
Contributor

the pr is broken

if you want a 0.7 version

you can refer this:
guyuming76/dwl@d40b3d6ef1

@LaKato

the pr is broken if you want a 0.7 version you can refer this: https://gitee.com/guyuming76/dwl/commit/d40b3d6ef1b66dba312df66561a5a2d36a90b8bf @LaKato
Member

@DreamMaoMao wrote in #235 (comment):

the pr is broken

I am aware, which is why I've tried updating it. If there is no intention of pursuing these protocols further in this thread, I'd like to suggest closing this PR and removing the reference to it from the dwl readme to avoid misleading anyone.

if you want a 0.7 version

you can refer this: d40b3d6ef1

I haven't tried this out, but I can see that it's explicitly described as not working. Unless the "not working" part only applies to the handwriting functionality (which my update does not include), it sounds like my update is more functional (except for the noted crash when focusing the popup window). It's allowed me to use both fcitx5 and anthywl on Qt6 and GTK3 applications (I haven't tested other versions) as well as on foot terminal (which uses neither, of course).

@DreamMaoMao wrote in https://codeberg.org/dwl/dwl/pulls/235#issuecomment-3155655: > the pr is broken I am aware, which is why I've tried updating it. If there is no intention of pursuing these protocols further in this thread, I'd like to suggest closing this PR and removing the reference to it from the dwl readme to avoid misleading anyone. > if you want a 0.7 version > > you can refer this: [`d40b3d6ef1`](https://gitee.com/guyuming76/dwl/commit/d40b3d6ef1b66dba312df66561a5a2d36a90b8bf) I haven't tried this out, but I can see that it's explicitly described as not working. Unless the "not working" part only applies to the handwriting functionality (which my update does not include), it sounds like my update is more functional (except for the noted crash when focusing the popup window). It's allowed me to use both fcitx5 and anthywl on Qt6 and GTK3 applications (I haven't tested other versions) as well as on foot terminal (which uses neither, of course).
Contributor

@LaKato wrote in #235 (comment):

@DreamMaoMao wrote in #235 (评论):

the pr is broken

I am aware, which is why I've tried updating it. If there is no intention of pursuing these protocols further in this thread, I'd like to suggest closing this PR and removing the reference to it from the dwl readme to avoid misleading anyone.

if you want a 0.7 version
you can refer this: d40b3d6ef1

I haven't tried this out, but I can see that it's explicitly described as not working. Unless the "not working" part only applies to the handwriting functionality (which my update does not include), it sounds like my update is more functional (except for the noted crash when focusing the popup window). It's allowed me to use both fcitx5 and anthywl on Qt6 and GTK3 applications (I haven't tested other versions) as well as on foot terminal (which uses neither, of course).

it work well with me , No problems, no crash.
You can try it out and notice the fixes in the next few commits, the one I gave you doesn't seem to be the final commit yet .
the work is base sway work in text_input,You can check out the source code for sway, they're pretty much the same.
.

@LaKato wrote in https://codeberg.org/dwl/dwl/pulls/235#issuecomment-3166362: > @DreamMaoMao wrote in #235 (评论): > > > the pr is broken > > I am aware, which is why I've tried updating it. If there is no intention of pursuing these protocols further in this thread, I'd like to suggest closing this PR and removing the reference to it from the dwl readme to avoid misleading anyone. > > > if you want a 0.7 version > > you can refer this: [`d40b3d6ef1`](https://gitee.com/guyuming76/dwl/commit/d40b3d6ef1b66dba312df66561a5a2d36a90b8bf) > > I haven't tried this out, but I can see that it's explicitly described as not working. Unless the "not working" part only applies to the handwriting functionality (which my update does not include), it sounds like my update is more functional (except for the noted crash when focusing the popup window). It's allowed me to use both fcitx5 and anthywl on Qt6 and GTK3 applications (I haven't tested other versions) as well as on foot terminal (which uses neither, of course). it work well with me , No problems, no crash. You can try it out and notice the fixes in the next few commits, the one I gave you doesn't seem to be the final commit yet . the work is base sway work in text_input,You can check out the source code for sway, they're pretty much the same. .
Contributor
No description provided.
Member

@DreamMaoMao wrote in #235 (comment):

You can try it out and notice the fixes in the next few commits, the one I gave you doesn't seem to be the final commit yet .

Ah, I missed that there were other commits afterwards. Thanks for pointing that out.

If we have IME support working, then, where does that leave this "feature under consideration" as mentioned in the readme? Do we want to try merging this into dwl (I imagine this is unlikely, but the readme suggests it's a possibility), or perhaps someone would be willing to submit this to the patches repository?

If nobody else is interested in doing so, I'd be fine with taking responsibility for maintenance of a patch.

@DreamMaoMao wrote in https://codeberg.org/dwl/dwl/pulls/235#issuecomment-3170379: > You can try it out and notice the fixes in the next few commits, the one I gave you doesn't seem to be the final commit yet . Ah, I missed that there were other commits afterwards. Thanks for pointing that out. If we have IME support working, then, where does that leave this "feature under consideration" as mentioned in the readme? Do we want to try merging this into dwl (I imagine this is unlikely, but the readme suggests it's a possibility), or perhaps someone would be willing to submit this to the patches repository? If nobody else is interested in doing so, I'd be fine with taking responsibility for maintenance of a patch.
Member

Since DreamMaoMao deleted his comment and repository I'm reposting his patch. I'm not sure about copyright compatibility since it says GPL 2 only but it works great if someone wants to apply it.

You need to use events.text_input on wlroots 0.19 and events.new_text_input on wlroots master.

From 5d710c9cac89dbc6dd3ad7b7ee3466715a4c9ea9 Mon Sep 17 00:00:00 2001
From: DreamMaoMao <[email protected]>
Date: Tue, 5 Aug 2025 21:27:06 +0200
Subject: [PATCH] feat: text-input

---
 Makefile |   2 +-
 dwl.c    |  42 +++-
 ime.h    | 753 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 787 insertions(+), 10 deletions(-)
 create mode 100644 ime.h

diff --git a/Makefile b/Makefile
index 578194f..bcf200c 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,7 @@ include config.mk
 # flags for compiling
 DWLCPPFLAGS = -I. -DWLR_USE_UNSTABLE -D_POSIX_C_SOURCE=200809L \
 	-DVERSION=\"$(VERSION)\" $(XWAYLAND)
-DWLDEVCFLAGS = -g -Wpedantic -Wall -Wextra -Wdeclaration-after-statement \
+DWLDEVCFLAGS = -g -Wpedantic -Wall -Wextra \
 	-Wno-unused-parameter -Wshadow -Wunused-macros -Werror=strict-prototypes \
 	-Werror=implicit -Werror=return-type -Werror=incompatible-pointer-types \
 	-Wfloat-conversion
diff --git a/dwl.c b/dwl.c
index 5f1fbc7..634837b 100644
--- a/dwl.c
+++ b/dwl.c
@@ -86,7 +86,7 @@
 /* enums */
 enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */
 enum { XDGShell, LayerShell, X11 }; /* client types */
-enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */
+enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrIMPopup, LyrBlock, NUM_LAYERS }; /* scene layers */

 typedef union {
 	int i;
@@ -478,6 +478,9 @@ static struct wlr_xwayland *xwayland;
 /* attempt to encapsulate suck into one file */
 #include "client.h"

+/* ime */
+#include "ime.h"
+
 /* function implementations */
 void
 applybounds(Client *c, struct wlr_box *bbox)
@@ -754,6 +757,9 @@ cleanup(void)

 	destroykeyboardgroup(&kb_group->destroy, NULL);

+	/* Destroy input method relay */
+	input_method_relay_finish(input_method_relay);
+
 	/* If it's not destroyed manually, it will cause a use-after-free of wlr_seat.
 	 * Destroy it until it's fixed on the wlroots side */
 	wlr_backend_destroy(backend);
@@ -1512,6 +1518,7 @@ focusclient(Client *c, int lift)

 	if (!c) {
 		/* With no client, all we have left is to clear focus */
+		input_method_relay_set_focus(input_method_relay, NULL);
 		wlr_seat_keyboard_notify_clear_focus(seat);
 		return;
 	}
@@ -1522,6 +1529,9 @@ focusclient(Client *c, int lift)
 	/* Have a client, so focus its top-level wlr_surface */
 	client_notify_enter(client_surface(c), wlr_seat_get_keyboard(seat));

+  	/* set text input focus */
+  	input_method_relay_set_focus(input_method_relay, client_surface(c));
+
 	/* Activate the new client */
 	client_activate_surface(client_surface(c), 1);
 }
@@ -1766,10 +1776,12 @@ keypress(struct wl_listener *listener, void *data)
 	if (handled)
 		return;

-	wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard);
-	/* Pass unhandled keycodes along to the client. */
-	wlr_seat_keyboard_notify_key(seat, event->time_msec,
-			event->keycode, event->state);
+	if (!input_method_keyboard_grab_forward_key(group, event)) {
+	  wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard);
+	  /* Pass unhandled keycodes along to the client. */
+	  wlr_seat_keyboard_notify_key(seat, event->time_msec, event->keycode,
+	                               event->state);
+	}
 }

 void
@@ -1779,10 +1791,12 @@ keypressmod(struct wl_listener *listener, void *data)
 	 * pressed. We simply communicate this to the client. */
 	KeyboardGroup *group = wl_container_of(listener, group, modifiers);

-	wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard);
-	/* Send modifiers to the client. */
-	wlr_seat_keyboard_notify_modifiers(seat,
-			&group->wlr_group->keyboard.modifiers);
+	if (!input_method_keyboard_grab_forward_modifiers(group)) {
+	  wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard);
+	  /* Send modifiers to the client. */
+	  wlr_seat_keyboard_notify_modifiers(seat,
+	                                     &group->wlr_group->keyboard.modifiers);
+	}
 }

 int
@@ -2779,6 +2793,12 @@ setup(void)
 	wl_signal_add(&output_mgr->events.apply, &output_mgr_apply);
 	wl_signal_add(&output_mgr->events.test, &output_mgr_test);

+	/* create text_input-, and input_method-protocol relevant globals */
+	input_method_manager = wlr_input_method_manager_v2_create(dpy);
+	text_input_manager = wlr_text_input_manager_v3_create(dpy);
+	input_method_relay = calloc(1, sizeof(*input_method_relay));
+	input_method_relay = input_method_relay_create();
+
 	/* Make sure XWayland clients don't connect to the parent X server,
 	 * e.g when running in the x11 backend or the wayland backend and the
 	 * compositor has Xwayland support */
@@ -3190,6 +3210,10 @@ xytonode(double x, double y, struct wlr_surface **psurface,
 	int layer;

 	for (layer = NUM_LAYERS - 1; !surface && layer >= 0; layer--) {
+
+		if (layer == LyrIMPopup)
+    	  continue;
+
 		if (!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny)))
 			continue;

diff --git a/ime.h b/ime.h
new file mode 100644
index 0000000..653b8ac
--- /dev/null
+++ b/ime.h
@@ -0,0 +1,753 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Based on labwc (https://github.com/labwc/labwc) */
+
+#include <assert.h>
+#include <wlr/types/wlr_input_method_v2.h>
+#include <wlr/types/wlr_text_input_v3.h>
+
+#define DWL_SET_MAX_SIZE 16
+
+#define znew(expr) ((__typeof__(expr) *)xzalloc(sizeof(expr)))
+
+#define SAME_CLIENT(wlr_obj1, wlr_obj2)                                        \
+  (wl_resource_get_client((wlr_obj1)->resource) ==                             \
+   wl_resource_get_client((wlr_obj2)->resource))
+
+struct dwl_set {
+  uint32_t values[DWL_SET_MAX_SIZE];
+  int size;
+};
+
+static void die_if_null(void *ptr) {
+  if (!ptr) {
+    perror("Failed to allocate memory");
+    exit(EXIT_FAILURE);
+  }
+}
+
+void *xzalloc(size_t size) {
+  if (!size) {
+    return NULL;
+  }
+  void *ptr = calloc(1, size);
+  die_if_null(ptr);
+  return ptr;
+}
+
+/*
+ * The relay structure manages the relationship between text-inputs and
+ * input-method on a given seat. Multiple text-inputs may be bound to a relay,
+ * but at most one will be "active" (communicating with input-method) at a time.
+ * At most one input-method may be bound to the seat. When an input-method and
+ * an active text-input is present, the relay passes messages between them.
+ */
+struct input_method_relay {
+  struct wl_list text_inputs; /* struct text_input.link */
+  struct wlr_input_method_v2 *input_method;
+  struct wlr_surface *focused_surface;
+
+  struct dwl_set forwarded_pressed_keys;
+  struct wlr_keyboard_modifiers forwarded_modifiers;
+
+  /*
+   * Text-input which is enabled by the client and communicating with
+   * input-method.
+   * This must be NULL if input-method is not present.
+   * Its client must be the same as that of focused_surface.
+   */
+  struct text_input *active_text_input;
+
+  struct wl_list popups; /* input_method_popup.link */
+  struct wlr_scene_tree *popup_tree;
+
+  struct wl_listener new_text_input;
+  struct wl_listener new_input_method;
+
+  struct wl_listener input_method_commit;
+  struct wl_listener input_method_grab_keyboard;
+  struct wl_listener input_method_destroy;
+  struct wl_listener input_method_new_popup_surface;
+
+  struct wl_listener keyboard_grab_destroy;
+  struct wl_listener focused_surface_destroy;
+};
+
+struct input_method_popup {
+  struct wlr_input_popup_surface_v2 *popup_surface;
+  struct wlr_scene_tree *tree;
+  struct wlr_scene_tree *scene_surface;
+  struct input_method_relay *relay;
+  struct wl_list link; /* input_method_relay.popups */
+
+  struct wl_listener destroy;
+  struct wl_listener commit;
+};
+
+struct text_input {
+  struct input_method_relay *relay;
+  struct wlr_text_input_v3 *input;
+  struct wl_list link;
+
+  struct wl_listener enable;
+  struct wl_listener commit;
+  struct wl_listener disable;
+  struct wl_listener destroy;
+};
+
+struct wlr_input_method_manager_v2 *input_method_manager;
+struct wlr_text_input_manager_v3 *text_input_manager;
+struct input_method_relay *input_method_relay;
+
+/*
+ * Forward key event to keyboard grab of the seat from the keyboard
+ * if the keyboard grab exists.
+ * Returns true if the key event was forwarded.
+ */
+bool input_method_keyboard_grab_forward_key(
+    KeyboardGroup *keyboard, struct wlr_keyboard_key_event *event);
+
+/*
+ * Forward modifier state to keyboard grab of the seat from the keyboard
+ * if the keyboard grab exists.
+ * Returns true if the modifier state was forwarded.
+ */
+bool input_method_keyboard_grab_forward_modifiers(KeyboardGroup *keyboard);
+
+struct input_method_relay *input_method_relay_create(void);
+
+void input_method_relay_finish(struct input_method_relay *relay);
+
+/* Updates currently focused surface. Surface must belong to the same seat. */
+void input_method_relay_set_focus(struct input_method_relay *relay,
+                                  struct wlr_surface *surface);
+
+bool dwl_set_contains(struct dwl_set *set, uint32_t value) {
+  for (int i = 0; i < set->size; ++i) {
+    if (set->values[i] == value) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void dwl_set_add(struct dwl_set *set, uint32_t value) {
+  if (dwl_set_contains(set, value)) {
+    return;
+  }
+  if (set->size >= DWL_SET_MAX_SIZE) {
+    wlr_log(WLR_ERROR, "dwl_set size exceeded");
+    return;
+  }
+  set->values[set->size++] = value;
+}
+
+void dwl_set_remove(struct dwl_set *set, uint32_t value) {
+  for (int i = 0; i < set->size; ++i) {
+    if (set->values[i] == value) {
+      --set->size;
+      set->values[i] = set->values[set->size];
+      return;
+    }
+  }
+}
+
+Monitor *output_from_wlr_output(struct wlr_output *wlr_output) {
+  Monitor *m;
+  wl_list_for_each(m, &mons, link) {
+    if (m->wlr_output == wlr_output) {
+      return m;
+    }
+  }
+  return NULL;
+}
+
+Monitor *output_nearest_to(int lx, int ly) {
+  double closest_x, closest_y;
+  wlr_output_layout_closest_point(output_layout, NULL, lx, ly, &closest_x,
+                                  &closest_y);
+
+  return output_from_wlr_output(
+      wlr_output_layout_output_at(output_layout, closest_x, closest_y));
+}
+
+bool output_is_usable(Monitor *m) {
+  /* output_is_usable(NULL) is safe and returns false */
+  return m && m->wlr_output->enabled;
+}
+
+static bool
+is_keyboard_emulated_by_input_method(struct wlr_keyboard *keyboard,
+                                     struct wlr_input_method_v2 *input_method) {
+  if (!keyboard || !input_method) {
+    return false;
+  }
+
+  struct wlr_virtual_keyboard_v1 *virtual_keyboard =
+      wlr_input_device_get_virtual_keyboard(&keyboard->base);
+
+  return virtual_keyboard && SAME_CLIENT(virtual_keyboard, input_method);
+}
+
+/*
+ * Get keyboard grab of the seat from keyboard if we should forward events
+ * to it.
+ */
+static struct wlr_input_method_keyboard_grab_v2 *
+get_keyboard_grab(KeyboardGroup *keyboard) {
+  struct wlr_input_method_v2 *input_method = input_method_relay->input_method;
+  if (!input_method || !input_method->keyboard_grab) {
+    return NULL;
+  }
+
+  // labwc not need this , but maomao need
+  if (keyboard != kb_group)
+    return NULL;
+
+  /*
+   * Input-methods often use virtual keyboard to send raw key signals
+   * instead of sending encoded text via set_preedit_string and
+   * commit_string. We should not forward those key events back to the
+   * input-method so key events don't loop between the compositor and
+   * the input-method.
+   */
+  if (is_keyboard_emulated_by_input_method(&keyboard->wlr_group->keyboard,
+                                           input_method)) {
+    return NULL;
+  }
+
+  return input_method->keyboard_grab;
+}
+
+bool input_method_keyboard_grab_forward_modifiers(KeyboardGroup *keyboard) {
+  struct wlr_input_method_keyboard_grab_v2 *keyboard_grab =
+      get_keyboard_grab(keyboard);
+
+  struct wlr_keyboard_modifiers *forwarded_modifiers =
+      &input_method_relay->forwarded_modifiers;
+  struct wlr_keyboard_modifiers *modifiers =
+      &keyboard->wlr_group->keyboard.modifiers;
+
+  if (forwarded_modifiers->depressed == modifiers->depressed &&
+      forwarded_modifiers->latched == modifiers->latched &&
+      forwarded_modifiers->locked == modifiers->locked &&
+      forwarded_modifiers->group == modifiers->group) {
+    return false;
+  }
+
+  if (keyboard_grab) {
+    *forwarded_modifiers = keyboard->wlr_group->keyboard.modifiers;
+    wlr_input_method_keyboard_grab_v2_set_keyboard(
+        keyboard_grab, &keyboard->wlr_group->keyboard);
+    wlr_input_method_keyboard_grab_v2_send_modifiers(keyboard_grab, modifiers);
+    return true;
+  } else {
+    return false;
+  }
+}
+
+bool input_method_keyboard_grab_forward_key(
+    KeyboardGroup *keyboard, struct wlr_keyboard_key_event *event) {
+  /*
+   * We should not forward key-release events without corresponding
+   * key-press events forwarded
+   */
+  struct dwl_set *pressed_keys = &input_method_relay->forwarded_pressed_keys;
+  if (event->state == WL_KEYBOARD_KEY_STATE_RELEASED &&
+      !dwl_set_contains(pressed_keys, event->keycode)) {
+    return false;
+  }
+
+  struct wlr_input_method_keyboard_grab_v2 *keyboard_grab =
+      get_keyboard_grab(keyboard);
+  if (keyboard_grab) {
+    if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
+      dwl_set_add(pressed_keys, event->keycode);
+    } else {
+      dwl_set_remove(pressed_keys, event->keycode);
+    }
+    wlr_input_method_keyboard_grab_v2_set_keyboard(
+        keyboard_grab, &keyboard->wlr_group->keyboard);
+    wlr_input_method_keyboard_grab_v2_send_key(keyboard_grab, event->time_msec,
+                                               event->keycode, event->state);
+    return true;
+  } else {
+    return false;
+  }
+}
+
+/*
+ * update_text_inputs_focused_surface() should be called beforehand to set
+ * right text-inputs to choose from.
+ */
+static struct text_input *
+get_active_text_input(struct input_method_relay *relay) {
+  if (!relay->input_method) {
+    return NULL;
+  }
+  struct text_input *text_input;
+  wl_list_for_each(text_input, &relay->text_inputs, link) {
+    if (text_input->input->focused_surface &&
+        text_input->input->current_enabled) {
+      return text_input;
+    }
+  }
+  return NULL;
+}
+
+/*
+ * Updates active text-input and activates/deactivates the input-method if the
+ * value is changed.
+ */
+static void update_active_text_input(struct input_method_relay *relay) {
+  struct text_input *active_text_input = get_active_text_input(relay);
+
+  if (relay->input_method && relay->active_text_input != active_text_input) {
+    if (active_text_input) {
+      wlr_input_method_v2_send_activate(relay->input_method);
+    } else {
+      wlr_input_method_v2_send_deactivate(relay->input_method);
+    }
+    wlr_input_method_v2_send_done(relay->input_method);
+  }
+
+  relay->active_text_input = active_text_input;
+}
+
+/*
+ * Updates focused surface of text-inputs and sends enter/leave events to
+ * the text-inputs whose focused surface is changed.
+ * When input-method is present, text-inputs whose client is the same as the
+ * relay's focused surface also have that focused surface. Clients can then
+ * send enable request on a text-input which has the focused surface to make
+ * the text-input active and start communicating with input-method.
+ */
+static void
+update_text_inputs_focused_surface(struct input_method_relay *relay) {
+  struct text_input *text_input;
+  wl_list_for_each(text_input, &relay->text_inputs, link) {
+    struct wlr_text_input_v3 *input = text_input->input;
+
+    struct wlr_surface *new_focused_surface;
+    if (relay->input_method && relay->focused_surface &&
+        SAME_CLIENT(input, relay->focused_surface)) {
+      new_focused_surface = relay->focused_surface;
+    } else {
+      new_focused_surface = NULL;
+    }
+
+    if (input->focused_surface == new_focused_surface) {
+      continue;
+    }
+    if (input->focused_surface) {
+      wlr_text_input_v3_send_leave(input);
+    }
+    if (new_focused_surface) {
+      wlr_text_input_v3_send_enter(input, new_focused_surface);
+    }
+  }
+}
+
+static void update_popup_position(struct input_method_popup *popup) {
+  struct input_method_relay *relay = popup->relay;
+  struct text_input *text_input = relay->active_text_input;
+
+  if (!text_input || !relay->focused_surface ||
+      !popup->popup_surface->surface->mapped) {
+    return;
+  }
+
+  struct wlr_box cursor_rect;
+  struct wlr_xdg_surface *xdg_surface =
+      wlr_xdg_surface_try_from_wlr_surface(relay->focused_surface);
+  struct wlr_layer_surface_v1 *layer_surface =
+      wlr_layer_surface_v1_try_from_wlr_surface(relay->focused_surface);
+
+  if ((text_input->input->current.features &
+       WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE) &&
+      (xdg_surface || layer_surface)) {
+    cursor_rect = text_input->input->current.cursor_rectangle;
+
+    /*
+     * wlr_surface->data is:
+     * - for XDG surfaces: view->content_tree
+     * - for layer surfaces: dwl_layer_surface->scene_layer_surface->tree
+     * - for layer popups: dwl_layer_popup->scene_tree
+     */
+    struct wlr_scene_tree *tree = relay->focused_surface->data;
+    int lx, ly;
+    wlr_scene_node_coords(&tree->node, &lx, &ly);
+    cursor_rect.x += lx;
+    cursor_rect.y += ly;
+
+    if (xdg_surface) {
+      /* Take into account invisible xdg-shell CSD borders */
+      cursor_rect.x -= xdg_surface->geometry.x;
+      cursor_rect.y -= xdg_surface->geometry.y;
+    }
+  } else {
+    cursor_rect = (struct wlr_box){0};
+  }
+
+  Monitor *output = output_nearest_to(cursor_rect.x, cursor_rect.y);
+  if (!output_is_usable(output)) {
+    wlr_log(WLR_ERROR, "Cannot position IME popup (unusable output)");
+    return;
+  }
+  struct wlr_box output_box;
+  wlr_output_layout_get_box(output_layout, output->wlr_output, &output_box);
+
+  /* Use xdg-positioner utilities to position popup */
+  struct wlr_xdg_positioner_rules positioner_rules = {
+      .anchor_rect = cursor_rect,
+      .anchor = XDG_POSITIONER_ANCHOR_BOTTOM_LEFT,
+      .gravity = XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT,
+      .size =
+          {
+              .width = popup->popup_surface->surface->current.width,
+              .height = popup->popup_surface->surface->current.height,
+          },
+      .constraint_adjustment = XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y |
+                               XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X,
+  };
+
+  struct wlr_box popup_box;
+  wlr_xdg_positioner_rules_get_geometry(&positioner_rules, &popup_box);
+  wlr_xdg_positioner_rules_unconstrain_box(&positioner_rules, &output_box, &popup_box);
+
+  wlr_scene_node_set_position(&popup->tree->node, popup_box.x, popup_box.y);
+  /* Make sure IME popups are always on top, above layer-shell surfaces */
+  wlr_scene_node_raise_to_top(&relay->popup_tree->node);
+
+  wlr_input_popup_surface_v2_send_text_input_rectangle(
+      popup->popup_surface, &(struct wlr_box){
+                                .x = cursor_rect.x - popup_box.x,
+                                .y = cursor_rect.y - popup_box.y,
+                                .width = cursor_rect.width,
+                                .height = cursor_rect.height,
+                            });
+}
+
+static void update_popups_position(struct input_method_relay *relay) {
+  struct input_method_popup *popup;
+  wl_list_for_each(popup, &relay->popups, link) {
+    update_popup_position(popup);
+  }
+}
+
+static void handle_input_method_commit(struct wl_listener *listener,
+                                       void *data) {
+  struct input_method_relay *relay =
+      wl_container_of(listener, relay, input_method_commit);
+  struct wlr_input_method_v2 *input_method = data;
+  assert(relay->input_method == input_method);
+
+  struct text_input *text_input = relay->active_text_input;
+  if (!text_input) {
+    return;
+  }
+
+  if (input_method->current.preedit.text) {
+    wlr_text_input_v3_send_preedit_string(
+        text_input->input, input_method->current.preedit.text,
+        input_method->current.preedit.cursor_begin,
+        input_method->current.preedit.cursor_end);
+  }
+  if (input_method->current.commit_text) {
+    wlr_text_input_v3_send_commit_string(text_input->input,
+                                         input_method->current.commit_text);
+  }
+  if (input_method->current.delete.before_length ||
+      input_method->current.delete.after_length) {
+    wlr_text_input_v3_send_delete_surrounding_text(
+        text_input->input, input_method->current.delete.before_length,
+        input_method->current.delete.after_length);
+  }
+  wlr_text_input_v3_send_done(text_input->input);
+}
+
+static void handle_keyboard_grab_destroy(struct wl_listener *listener,
+                                         void *data) {
+  struct input_method_relay *relay =
+      wl_container_of(listener, relay, keyboard_grab_destroy);
+  struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data;
+  wl_list_remove(&relay->keyboard_grab_destroy.link);
+
+  if (keyboard_grab->keyboard) {
+    /* Send modifier state to original client */
+    wlr_seat_keyboard_notify_modifiers(keyboard_grab->input_method->seat,
+                                       &keyboard_grab->keyboard->modifiers);
+  }
+}
+
+static void handle_input_method_grab_keyboard(struct wl_listener *listener,
+                                              void *data) {
+  struct input_method_relay *relay =
+      wl_container_of(listener, relay, input_method_grab_keyboard);
+  struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data;
+
+  struct wlr_keyboard *active_keyboard = wlr_seat_get_keyboard(seat);
+
+  if (!is_keyboard_emulated_by_input_method(active_keyboard,
+                                            relay->input_method)) {
+    /* Send modifier state to grab */
+    wlr_input_method_keyboard_grab_v2_set_keyboard(keyboard_grab,
+                                                   active_keyboard);
+  }
+
+  relay->forwarded_pressed_keys = (struct dwl_set){0};
+  relay->forwarded_modifiers = (struct wlr_keyboard_modifiers){0};
+
+  relay->keyboard_grab_destroy.notify = handle_keyboard_grab_destroy;
+  wl_signal_add(&keyboard_grab->events.destroy, &relay->keyboard_grab_destroy);
+}
+
+static void handle_input_method_destroy(struct wl_listener *listener,
+                                        void *data) {
+  struct input_method_relay *relay =
+      wl_container_of(listener, relay, input_method_destroy);
+  assert(relay->input_method == data);
+  wl_list_remove(&relay->input_method_commit.link);
+  wl_list_remove(&relay->input_method_grab_keyboard.link);
+  wl_list_remove(&relay->input_method_new_popup_surface.link);
+  wl_list_remove(&relay->input_method_destroy.link);
+  relay->input_method = NULL;
+
+  update_text_inputs_focused_surface(relay);
+  update_active_text_input(relay);
+}
+
+static void handle_popup_surface_destroy(struct wl_listener *listener,
+                                         void *data) {
+  struct input_method_popup *popup = wl_container_of(listener, popup, destroy);
+  wlr_scene_node_destroy(&popup->tree->node);
+  wl_list_remove(&popup->destroy.link);
+  wl_list_remove(&popup->commit.link);
+  wl_list_remove(&popup->link);
+  free(popup);
+}
+
+static void handle_popup_surface_commit(struct wl_listener *listener,
+                                        void *data) {
+  struct input_method_popup *popup = wl_container_of(listener, popup, commit);
+  update_popup_position(popup);
+}
+
+static void handle_input_method_new_popup_surface(struct wl_listener *listener,
+                                                  void *data) {
+  struct input_method_relay *relay =
+      wl_container_of(listener, relay, input_method_new_popup_surface);
+
+  struct input_method_popup *popup = znew(*popup);
+  popup->popup_surface = data;
+  popup->relay = relay;
+
+  popup->destroy.notify = handle_popup_surface_destroy;
+  wl_signal_add(&popup->popup_surface->events.destroy, &popup->destroy);
+
+  popup->commit.notify = handle_popup_surface_commit;
+  wl_signal_add(&popup->popup_surface->surface->events.commit, &popup->commit);
+
+  // popup->tree = wlr_scene_subsurface_tree_create(
+  // 	relay->popup_tree, popup->popup_surface->surface);
+  // node_descriptor_create(&popup->tree->node, dwl_NODE_DESC_IME_POPUP, NULL);
+
+  popup->tree = wlr_scene_tree_create(layers[LyrIMPopup]);
+  popup->scene_surface = wlr_scene_subsurface_tree_create(
+      popup->tree, popup->popup_surface->surface);
+  popup->scene_surface->node.data = popup;
+
+  wl_list_insert(&relay->popups, &popup->link);
+
+  update_popup_position(popup);
+}
+
+static void handle_new_input_method(struct wl_listener *listener, void *data) {
+  struct input_method_relay *relay =
+      wl_container_of(listener, relay, new_input_method);
+  struct wlr_input_method_v2 *input_method = data;
+  if (seat != input_method->seat) {
+    return;
+  }
+
+  if (relay->input_method) {
+    wlr_log(WLR_INFO, "Attempted to connect second input method to a seat");
+    wlr_input_method_v2_send_unavailable(input_method);
+    return;
+  }
+
+  relay->input_method = input_method;
+
+  relay->input_method_commit.notify = handle_input_method_commit;
+  wl_signal_add(&relay->input_method->events.commit,
+                &relay->input_method_commit);
+
+  relay->input_method_grab_keyboard.notify = handle_input_method_grab_keyboard;
+  wl_signal_add(&relay->input_method->events.grab_keyboard,
+                &relay->input_method_grab_keyboard);
+
+  relay->input_method_destroy.notify = handle_input_method_destroy;
+  wl_signal_add(&relay->input_method->events.destroy,
+                &relay->input_method_destroy);
+
+  relay->input_method_new_popup_surface.notify =
+      handle_input_method_new_popup_surface;
+  wl_signal_add(&relay->input_method->events.new_popup_surface,
+                &relay->input_method_new_popup_surface);
+
+  update_text_inputs_focused_surface(relay);
+  update_active_text_input(relay);
+}
+
+/* Conveys state from active text-input to input-method */
+static void send_state_to_input_method(struct input_method_relay *relay) {
+  assert(relay->active_text_input && relay->input_method);
+
+  struct wlr_input_method_v2 *input_method = relay->input_method;
+  struct wlr_text_input_v3 *input = relay->active_text_input->input;
+
+  /* TODO: only send each of those if they were modified */
+  if (input->active_features & WLR_TEXT_INPUT_V3_FEATURE_SURROUNDING_TEXT) {
+    wlr_input_method_v2_send_surrounding_text(
+        input_method, input->current.surrounding.text,
+        input->current.surrounding.cursor, input->current.surrounding.anchor);
+  }
+  wlr_input_method_v2_send_text_change_cause(input_method,
+                                             input->current.text_change_cause);
+  if (input->active_features & WLR_TEXT_INPUT_V3_FEATURE_CONTENT_TYPE) {
+    wlr_input_method_v2_send_content_type(input_method,
+                                          input->current.content_type.hint,
+                                          input->current.content_type.purpose);
+  }
+  wlr_input_method_v2_send_done(input_method);
+}
+
+static void handle_text_input_enable(struct wl_listener *listener, void *data) {
+  struct text_input *text_input = wl_container_of(listener, text_input, enable);
+  struct input_method_relay *relay = text_input->relay;
+
+  update_active_text_input(relay);
+  if (relay->active_text_input == text_input) {
+    update_popups_position(relay);
+    send_state_to_input_method(relay);
+  }
+}
+
+static void handle_text_input_disable(struct wl_listener *listener,
+                                      void *data) {
+  struct text_input *text_input =
+      wl_container_of(listener, text_input, disable);
+  /*
+   * When the focus is moved between surfaces from different clients and
+   * then the old client sends "disable" event, the relay ignores it and
+   * doesn't deactivate the input-method.
+   */
+  update_active_text_input(text_input->relay);
+}
+
+static void handle_text_input_commit(struct wl_listener *listener, void *data) {
+  struct text_input *text_input = wl_container_of(listener, text_input, commit);
+  struct input_method_relay *relay = text_input->relay;
+
+  if (relay->active_text_input == text_input) {
+    update_popups_position(relay);
+    send_state_to_input_method(relay);
+  }
+}
+
+static void handle_text_input_destroy(struct wl_listener *listener,
+                                      void *data) {
+  struct text_input *text_input =
+      wl_container_of(listener, text_input, destroy);
+  wl_list_remove(&text_input->enable.link);
+  wl_list_remove(&text_input->disable.link);
+  wl_list_remove(&text_input->commit.link);
+  wl_list_remove(&text_input->destroy.link);
+  wl_list_remove(&text_input->link);
+  update_active_text_input(text_input->relay);
+  free(text_input);
+}
+
+static void handle_new_text_input(struct wl_listener *listener, void *data) {
+  struct input_method_relay *relay =
+      wl_container_of(listener, relay, new_text_input);
+  struct wlr_text_input_v3 *wlr_text_input = data;
+  if (seat != wlr_text_input->seat) {
+    return;
+  }
+
+  struct text_input *text_input = znew(*text_input);
+  text_input->input = wlr_text_input;
+  text_input->relay = relay;
+  wl_list_insert(&relay->text_inputs, &text_input->link);
+
+  text_input->enable.notify = handle_text_input_enable;
+  wl_signal_add(&text_input->input->events.enable, &text_input->enable);
+
+  text_input->disable.notify = handle_text_input_disable;
+  wl_signal_add(&text_input->input->events.disable, &text_input->disable);
+
+  text_input->commit.notify = handle_text_input_commit;
+  wl_signal_add(&text_input->input->events.commit, &text_input->commit);
+
+  text_input->destroy.notify = handle_text_input_destroy;
+  wl_signal_add(&text_input->input->events.destroy, &text_input->destroy);
+
+  update_text_inputs_focused_surface(relay);
+}
+
+/*
+ * Usually this function is not called because the client destroys the surface
+ * role (like xdg_toplevel) first and input_method_relay_set_focus() is called
+ * before wl_surface is destroyed.
+ */
+static void handle_focused_surface_destroy(struct wl_listener *listener,
+                                           void *data) {
+  struct input_method_relay *relay =
+      wl_container_of(listener, relay, focused_surface_destroy);
+  assert(relay->focused_surface == data);
+
+  input_method_relay_set_focus(relay, NULL);
+}
+
+struct input_method_relay *input_method_relay_create() {
+  struct input_method_relay *relay = znew(*relay);
+  wl_list_init(&relay->text_inputs);
+  wl_list_init(&relay->popups);
+  relay->popup_tree = wlr_scene_tree_create(&scene->tree);
+
+  relay->new_text_input.notify = handle_new_text_input;
+  wl_signal_add(&text_input_manager->events.text_input, &relay->new_text_input);
+
+  relay->new_input_method.notify = handle_new_input_method;
+  wl_signal_add(&input_method_manager->events.input_method,
+                &relay->new_input_method);
+
+  relay->focused_surface_destroy.notify = handle_focused_surface_destroy;
+
+  return relay;
+}
+
+void input_method_relay_finish(struct input_method_relay *relay) {
+  wl_list_remove(&relay->new_text_input.link);
+  wl_list_remove(&relay->new_input_method.link);
+  free(relay);
+}
+
+void input_method_relay_set_focus(struct input_method_relay *relay,
+                                  struct wlr_surface *surface) {
+  if (relay->focused_surface == surface) {
+    wlr_log(WLR_INFO, "The surface is already focused");
+    return;
+  }
+
+  if (relay->focused_surface) {
+    wl_list_remove(&relay->focused_surface_destroy.link);
+  }
+  relay->focused_surface = surface;
+  if (surface) {
+    wl_signal_add(&surface->events.destroy, &relay->focused_surface_destroy);
+  }
+
+  update_text_inputs_focused_surface(relay);
+  update_active_text_input(relay);
+}
--
2.50.1
Since DreamMaoMao deleted his comment and repository I'm reposting his patch. I'm not sure about copyright compatibility since it says GPL 2 only but it works great if someone wants to apply it. You need to use `events.text_input` on wlroots 0.19 and `events.new_text_input` on wlroots master. ```patch From 5d710c9cac89dbc6dd3ad7b7ee3466715a4c9ea9 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <[email protected]> Date: Tue, 5 Aug 2025 21:27:06 +0200 Subject: [PATCH] feat: text-input --- Makefile | 2 +- dwl.c | 42 +++- ime.h | 753 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 787 insertions(+), 10 deletions(-) create mode 100644 ime.h diff --git a/Makefile b/Makefile index 578194f..bcf200c 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ include config.mk # flags for compiling DWLCPPFLAGS = -I. -DWLR_USE_UNSTABLE -D_POSIX_C_SOURCE=200809L \ -DVERSION=\"$(VERSION)\" $(XWAYLAND) -DWLDEVCFLAGS = -g -Wpedantic -Wall -Wextra -Wdeclaration-after-statement \ +DWLDEVCFLAGS = -g -Wpedantic -Wall -Wextra \ -Wno-unused-parameter -Wshadow -Wunused-macros -Werror=strict-prototypes \ -Werror=implicit -Werror=return-type -Werror=incompatible-pointer-types \ -Wfloat-conversion diff --git a/dwl.c b/dwl.c index 5f1fbc7..634837b 100644 --- a/dwl.c +++ b/dwl.c @@ -86,7 +86,7 @@ /* enums */ enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ enum { XDGShell, LayerShell, X11 }; /* client types */ -enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ +enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrIMPopup, LyrBlock, NUM_LAYERS }; /* scene layers */ typedef union { int i; @@ -478,6 +478,9 @@ static struct wlr_xwayland *xwayland; /* attempt to encapsulate suck into one file */ #include "client.h" +/* ime */ +#include "ime.h" + /* function implementations */ void applybounds(Client *c, struct wlr_box *bbox) @@ -754,6 +757,9 @@ cleanup(void) destroykeyboardgroup(&kb_group->destroy, NULL); + /* Destroy input method relay */ + input_method_relay_finish(input_method_relay); + /* If it's not destroyed manually, it will cause a use-after-free of wlr_seat. * Destroy it until it's fixed on the wlroots side */ wlr_backend_destroy(backend); @@ -1512,6 +1518,7 @@ focusclient(Client *c, int lift) if (!c) { /* With no client, all we have left is to clear focus */ + input_method_relay_set_focus(input_method_relay, NULL); wlr_seat_keyboard_notify_clear_focus(seat); return; } @@ -1522,6 +1529,9 @@ focusclient(Client *c, int lift) /* Have a client, so focus its top-level wlr_surface */ client_notify_enter(client_surface(c), wlr_seat_get_keyboard(seat)); + /* set text input focus */ + input_method_relay_set_focus(input_method_relay, client_surface(c)); + /* Activate the new client */ client_activate_surface(client_surface(c), 1); } @@ -1766,10 +1776,12 @@ keypress(struct wl_listener *listener, void *data) if (handled) return; - wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); - /* Pass unhandled keycodes along to the client. */ - wlr_seat_keyboard_notify_key(seat, event->time_msec, - event->keycode, event->state); + if (!input_method_keyboard_grab_forward_key(group, event)) { + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + /* Pass unhandled keycodes along to the client. */ + wlr_seat_keyboard_notify_key(seat, event->time_msec, event->keycode, + event->state); + } } void @@ -1779,10 +1791,12 @@ keypressmod(struct wl_listener *listener, void *data) * pressed. We simply communicate this to the client. */ KeyboardGroup *group = wl_container_of(listener, group, modifiers); - wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); - /* Send modifiers to the client. */ - wlr_seat_keyboard_notify_modifiers(seat, - &group->wlr_group->keyboard.modifiers); + if (!input_method_keyboard_grab_forward_modifiers(group)) { + wlr_seat_set_keyboard(seat, &group->wlr_group->keyboard); + /* Send modifiers to the client. */ + wlr_seat_keyboard_notify_modifiers(seat, + &group->wlr_group->keyboard.modifiers); + } } int @@ -2779,6 +2793,12 @@ setup(void) wl_signal_add(&output_mgr->events.apply, &output_mgr_apply); wl_signal_add(&output_mgr->events.test, &output_mgr_test); + /* create text_input-, and input_method-protocol relevant globals */ + input_method_manager = wlr_input_method_manager_v2_create(dpy); + text_input_manager = wlr_text_input_manager_v3_create(dpy); + input_method_relay = calloc(1, sizeof(*input_method_relay)); + input_method_relay = input_method_relay_create(); + /* Make sure XWayland clients don't connect to the parent X server, * e.g when running in the x11 backend or the wayland backend and the * compositor has Xwayland support */ @@ -3190,6 +3210,10 @@ xytonode(double x, double y, struct wlr_surface **psurface, int layer; for (layer = NUM_LAYERS - 1; !surface && layer >= 0; layer--) { + + if (layer == LyrIMPopup) + continue; + if (!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))) continue; diff --git a/ime.h b/ime.h new file mode 100644 index 0000000..653b8ac --- /dev/null +++ b/ime.h @@ -0,0 +1,753 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Based on labwc (https://github.com/labwc/labwc) */ + +#include <assert.h> +#include <wlr/types/wlr_input_method_v2.h> +#include <wlr/types/wlr_text_input_v3.h> + +#define DWL_SET_MAX_SIZE 16 + +#define znew(expr) ((__typeof__(expr) *)xzalloc(sizeof(expr))) + +#define SAME_CLIENT(wlr_obj1, wlr_obj2) \ + (wl_resource_get_client((wlr_obj1)->resource) == \ + wl_resource_get_client((wlr_obj2)->resource)) + +struct dwl_set { + uint32_t values[DWL_SET_MAX_SIZE]; + int size; +}; + +static void die_if_null(void *ptr) { + if (!ptr) { + perror("Failed to allocate memory"); + exit(EXIT_FAILURE); + } +} + +void *xzalloc(size_t size) { + if (!size) { + return NULL; + } + void *ptr = calloc(1, size); + die_if_null(ptr); + return ptr; +} + +/* + * The relay structure manages the relationship between text-inputs and + * input-method on a given seat. Multiple text-inputs may be bound to a relay, + * but at most one will be "active" (communicating with input-method) at a time. + * At most one input-method may be bound to the seat. When an input-method and + * an active text-input is present, the relay passes messages between them. + */ +struct input_method_relay { + struct wl_list text_inputs; /* struct text_input.link */ + struct wlr_input_method_v2 *input_method; + struct wlr_surface *focused_surface; + + struct dwl_set forwarded_pressed_keys; + struct wlr_keyboard_modifiers forwarded_modifiers; + + /* + * Text-input which is enabled by the client and communicating with + * input-method. + * This must be NULL if input-method is not present. + * Its client must be the same as that of focused_surface. + */ + struct text_input *active_text_input; + + struct wl_list popups; /* input_method_popup.link */ + struct wlr_scene_tree *popup_tree; + + struct wl_listener new_text_input; + struct wl_listener new_input_method; + + struct wl_listener input_method_commit; + struct wl_listener input_method_grab_keyboard; + struct wl_listener input_method_destroy; + struct wl_listener input_method_new_popup_surface; + + struct wl_listener keyboard_grab_destroy; + struct wl_listener focused_surface_destroy; +}; + +struct input_method_popup { + struct wlr_input_popup_surface_v2 *popup_surface; + struct wlr_scene_tree *tree; + struct wlr_scene_tree *scene_surface; + struct input_method_relay *relay; + struct wl_list link; /* input_method_relay.popups */ + + struct wl_listener destroy; + struct wl_listener commit; +}; + +struct text_input { + struct input_method_relay *relay; + struct wlr_text_input_v3 *input; + struct wl_list link; + + struct wl_listener enable; + struct wl_listener commit; + struct wl_listener disable; + struct wl_listener destroy; +}; + +struct wlr_input_method_manager_v2 *input_method_manager; +struct wlr_text_input_manager_v3 *text_input_manager; +struct input_method_relay *input_method_relay; + +/* + * Forward key event to keyboard grab of the seat from the keyboard + * if the keyboard grab exists. + * Returns true if the key event was forwarded. + */ +bool input_method_keyboard_grab_forward_key( + KeyboardGroup *keyboard, struct wlr_keyboard_key_event *event); + +/* + * Forward modifier state to keyboard grab of the seat from the keyboard + * if the keyboard grab exists. + * Returns true if the modifier state was forwarded. + */ +bool input_method_keyboard_grab_forward_modifiers(KeyboardGroup *keyboard); + +struct input_method_relay *input_method_relay_create(void); + +void input_method_relay_finish(struct input_method_relay *relay); + +/* Updates currently focused surface. Surface must belong to the same seat. */ +void input_method_relay_set_focus(struct input_method_relay *relay, + struct wlr_surface *surface); + +bool dwl_set_contains(struct dwl_set *set, uint32_t value) { + for (int i = 0; i < set->size; ++i) { + if (set->values[i] == value) { + return true; + } + } + return false; +} + +void dwl_set_add(struct dwl_set *set, uint32_t value) { + if (dwl_set_contains(set, value)) { + return; + } + if (set->size >= DWL_SET_MAX_SIZE) { + wlr_log(WLR_ERROR, "dwl_set size exceeded"); + return; + } + set->values[set->size++] = value; +} + +void dwl_set_remove(struct dwl_set *set, uint32_t value) { + for (int i = 0; i < set->size; ++i) { + if (set->values[i] == value) { + --set->size; + set->values[i] = set->values[set->size]; + return; + } + } +} + +Monitor *output_from_wlr_output(struct wlr_output *wlr_output) { + Monitor *m; + wl_list_for_each(m, &mons, link) { + if (m->wlr_output == wlr_output) { + return m; + } + } + return NULL; +} + +Monitor *output_nearest_to(int lx, int ly) { + double closest_x, closest_y; + wlr_output_layout_closest_point(output_layout, NULL, lx, ly, &closest_x, + &closest_y); + + return output_from_wlr_output( + wlr_output_layout_output_at(output_layout, closest_x, closest_y)); +} + +bool output_is_usable(Monitor *m) { + /* output_is_usable(NULL) is safe and returns false */ + return m && m->wlr_output->enabled; +} + +static bool +is_keyboard_emulated_by_input_method(struct wlr_keyboard *keyboard, + struct wlr_input_method_v2 *input_method) { + if (!keyboard || !input_method) { + return false; + } + + struct wlr_virtual_keyboard_v1 *virtual_keyboard = + wlr_input_device_get_virtual_keyboard(&keyboard->base); + + return virtual_keyboard && SAME_CLIENT(virtual_keyboard, input_method); +} + +/* + * Get keyboard grab of the seat from keyboard if we should forward events + * to it. + */ +static struct wlr_input_method_keyboard_grab_v2 * +get_keyboard_grab(KeyboardGroup *keyboard) { + struct wlr_input_method_v2 *input_method = input_method_relay->input_method; + if (!input_method || !input_method->keyboard_grab) { + return NULL; + } + + // labwc not need this , but maomao need + if (keyboard != kb_group) + return NULL; + + /* + * Input-methods often use virtual keyboard to send raw key signals + * instead of sending encoded text via set_preedit_string and + * commit_string. We should not forward those key events back to the + * input-method so key events don't loop between the compositor and + * the input-method. + */ + if (is_keyboard_emulated_by_input_method(&keyboard->wlr_group->keyboard, + input_method)) { + return NULL; + } + + return input_method->keyboard_grab; +} + +bool input_method_keyboard_grab_forward_modifiers(KeyboardGroup *keyboard) { + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = + get_keyboard_grab(keyboard); + + struct wlr_keyboard_modifiers *forwarded_modifiers = + &input_method_relay->forwarded_modifiers; + struct wlr_keyboard_modifiers *modifiers = + &keyboard->wlr_group->keyboard.modifiers; + + if (forwarded_modifiers->depressed == modifiers->depressed && + forwarded_modifiers->latched == modifiers->latched && + forwarded_modifiers->locked == modifiers->locked && + forwarded_modifiers->group == modifiers->group) { + return false; + } + + if (keyboard_grab) { + *forwarded_modifiers = keyboard->wlr_group->keyboard.modifiers; + wlr_input_method_keyboard_grab_v2_set_keyboard( + keyboard_grab, &keyboard->wlr_group->keyboard); + wlr_input_method_keyboard_grab_v2_send_modifiers(keyboard_grab, modifiers); + return true; + } else { + return false; + } +} + +bool input_method_keyboard_grab_forward_key( + KeyboardGroup *keyboard, struct wlr_keyboard_key_event *event) { + /* + * We should not forward key-release events without corresponding + * key-press events forwarded + */ + struct dwl_set *pressed_keys = &input_method_relay->forwarded_pressed_keys; + if (event->state == WL_KEYBOARD_KEY_STATE_RELEASED && + !dwl_set_contains(pressed_keys, event->keycode)) { + return false; + } + + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = + get_keyboard_grab(keyboard); + if (keyboard_grab) { + if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + dwl_set_add(pressed_keys, event->keycode); + } else { + dwl_set_remove(pressed_keys, event->keycode); + } + wlr_input_method_keyboard_grab_v2_set_keyboard( + keyboard_grab, &keyboard->wlr_group->keyboard); + wlr_input_method_keyboard_grab_v2_send_key(keyboard_grab, event->time_msec, + event->keycode, event->state); + return true; + } else { + return false; + } +} + +/* + * update_text_inputs_focused_surface() should be called beforehand to set + * right text-inputs to choose from. + */ +static struct text_input * +get_active_text_input(struct input_method_relay *relay) { + if (!relay->input_method) { + return NULL; + } + struct text_input *text_input; + wl_list_for_each(text_input, &relay->text_inputs, link) { + if (text_input->input->focused_surface && + text_input->input->current_enabled) { + return text_input; + } + } + return NULL; +} + +/* + * Updates active text-input and activates/deactivates the input-method if the + * value is changed. + */ +static void update_active_text_input(struct input_method_relay *relay) { + struct text_input *active_text_input = get_active_text_input(relay); + + if (relay->input_method && relay->active_text_input != active_text_input) { + if (active_text_input) { + wlr_input_method_v2_send_activate(relay->input_method); + } else { + wlr_input_method_v2_send_deactivate(relay->input_method); + } + wlr_input_method_v2_send_done(relay->input_method); + } + + relay->active_text_input = active_text_input; +} + +/* + * Updates focused surface of text-inputs and sends enter/leave events to + * the text-inputs whose focused surface is changed. + * When input-method is present, text-inputs whose client is the same as the + * relay's focused surface also have that focused surface. Clients can then + * send enable request on a text-input which has the focused surface to make + * the text-input active and start communicating with input-method. + */ +static void +update_text_inputs_focused_surface(struct input_method_relay *relay) { + struct text_input *text_input; + wl_list_for_each(text_input, &relay->text_inputs, link) { + struct wlr_text_input_v3 *input = text_input->input; + + struct wlr_surface *new_focused_surface; + if (relay->input_method && relay->focused_surface && + SAME_CLIENT(input, relay->focused_surface)) { + new_focused_surface = relay->focused_surface; + } else { + new_focused_surface = NULL; + } + + if (input->focused_surface == new_focused_surface) { + continue; + } + if (input->focused_surface) { + wlr_text_input_v3_send_leave(input); + } + if (new_focused_surface) { + wlr_text_input_v3_send_enter(input, new_focused_surface); + } + } +} + +static void update_popup_position(struct input_method_popup *popup) { + struct input_method_relay *relay = popup->relay; + struct text_input *text_input = relay->active_text_input; + + if (!text_input || !relay->focused_surface || + !popup->popup_surface->surface->mapped) { + return; + } + + struct wlr_box cursor_rect; + struct wlr_xdg_surface *xdg_surface = + wlr_xdg_surface_try_from_wlr_surface(relay->focused_surface); + struct wlr_layer_surface_v1 *layer_surface = + wlr_layer_surface_v1_try_from_wlr_surface(relay->focused_surface); + + if ((text_input->input->current.features & + WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE) && + (xdg_surface || layer_surface)) { + cursor_rect = text_input->input->current.cursor_rectangle; + + /* + * wlr_surface->data is: + * - for XDG surfaces: view->content_tree + * - for layer surfaces: dwl_layer_surface->scene_layer_surface->tree + * - for layer popups: dwl_layer_popup->scene_tree + */ + struct wlr_scene_tree *tree = relay->focused_surface->data; + int lx, ly; + wlr_scene_node_coords(&tree->node, &lx, &ly); + cursor_rect.x += lx; + cursor_rect.y += ly; + + if (xdg_surface) { + /* Take into account invisible xdg-shell CSD borders */ + cursor_rect.x -= xdg_surface->geometry.x; + cursor_rect.y -= xdg_surface->geometry.y; + } + } else { + cursor_rect = (struct wlr_box){0}; + } + + Monitor *output = output_nearest_to(cursor_rect.x, cursor_rect.y); + if (!output_is_usable(output)) { + wlr_log(WLR_ERROR, "Cannot position IME popup (unusable output)"); + return; + } + struct wlr_box output_box; + wlr_output_layout_get_box(output_layout, output->wlr_output, &output_box); + + /* Use xdg-positioner utilities to position popup */ + struct wlr_xdg_positioner_rules positioner_rules = { + .anchor_rect = cursor_rect, + .anchor = XDG_POSITIONER_ANCHOR_BOTTOM_LEFT, + .gravity = XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT, + .size = + { + .width = popup->popup_surface->surface->current.width, + .height = popup->popup_surface->surface->current.height, + }, + .constraint_adjustment = XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y | + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X, + }; + + struct wlr_box popup_box; + wlr_xdg_positioner_rules_get_geometry(&positioner_rules, &popup_box); + wlr_xdg_positioner_rules_unconstrain_box(&positioner_rules, &output_box, &popup_box); + + wlr_scene_node_set_position(&popup->tree->node, popup_box.x, popup_box.y); + /* Make sure IME popups are always on top, above layer-shell surfaces */ + wlr_scene_node_raise_to_top(&relay->popup_tree->node); + + wlr_input_popup_surface_v2_send_text_input_rectangle( + popup->popup_surface, &(struct wlr_box){ + .x = cursor_rect.x - popup_box.x, + .y = cursor_rect.y - popup_box.y, + .width = cursor_rect.width, + .height = cursor_rect.height, + }); +} + +static void update_popups_position(struct input_method_relay *relay) { + struct input_method_popup *popup; + wl_list_for_each(popup, &relay->popups, link) { + update_popup_position(popup); + } +} + +static void handle_input_method_commit(struct wl_listener *listener, + void *data) { + struct input_method_relay *relay = + wl_container_of(listener, relay, input_method_commit); + struct wlr_input_method_v2 *input_method = data; + assert(relay->input_method == input_method); + + struct text_input *text_input = relay->active_text_input; + if (!text_input) { + return; + } + + if (input_method->current.preedit.text) { + wlr_text_input_v3_send_preedit_string( + text_input->input, input_method->current.preedit.text, + input_method->current.preedit.cursor_begin, + input_method->current.preedit.cursor_end); + } + if (input_method->current.commit_text) { + wlr_text_input_v3_send_commit_string(text_input->input, + input_method->current.commit_text); + } + if (input_method->current.delete.before_length || + input_method->current.delete.after_length) { + wlr_text_input_v3_send_delete_surrounding_text( + text_input->input, input_method->current.delete.before_length, + input_method->current.delete.after_length); + } + wlr_text_input_v3_send_done(text_input->input); +} + +static void handle_keyboard_grab_destroy(struct wl_listener *listener, + void *data) { + struct input_method_relay *relay = + wl_container_of(listener, relay, keyboard_grab_destroy); + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data; + wl_list_remove(&relay->keyboard_grab_destroy.link); + + if (keyboard_grab->keyboard) { + /* Send modifier state to original client */ + wlr_seat_keyboard_notify_modifiers(keyboard_grab->input_method->seat, + &keyboard_grab->keyboard->modifiers); + } +} + +static void handle_input_method_grab_keyboard(struct wl_listener *listener, + void *data) { + struct input_method_relay *relay = + wl_container_of(listener, relay, input_method_grab_keyboard); + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data; + + struct wlr_keyboard *active_keyboard = wlr_seat_get_keyboard(seat); + + if (!is_keyboard_emulated_by_input_method(active_keyboard, + relay->input_method)) { + /* Send modifier state to grab */ + wlr_input_method_keyboard_grab_v2_set_keyboard(keyboard_grab, + active_keyboard); + } + + relay->forwarded_pressed_keys = (struct dwl_set){0}; + relay->forwarded_modifiers = (struct wlr_keyboard_modifiers){0}; + + relay->keyboard_grab_destroy.notify = handle_keyboard_grab_destroy; + wl_signal_add(&keyboard_grab->events.destroy, &relay->keyboard_grab_destroy); +} + +static void handle_input_method_destroy(struct wl_listener *listener, + void *data) { + struct input_method_relay *relay = + wl_container_of(listener, relay, input_method_destroy); + assert(relay->input_method == data); + wl_list_remove(&relay->input_method_commit.link); + wl_list_remove(&relay->input_method_grab_keyboard.link); + wl_list_remove(&relay->input_method_new_popup_surface.link); + wl_list_remove(&relay->input_method_destroy.link); + relay->input_method = NULL; + + update_text_inputs_focused_surface(relay); + update_active_text_input(relay); +} + +static void handle_popup_surface_destroy(struct wl_listener *listener, + void *data) { + struct input_method_popup *popup = wl_container_of(listener, popup, destroy); + wlr_scene_node_destroy(&popup->tree->node); + wl_list_remove(&popup->destroy.link); + wl_list_remove(&popup->commit.link); + wl_list_remove(&popup->link); + free(popup); +} + +static void handle_popup_surface_commit(struct wl_listener *listener, + void *data) { + struct input_method_popup *popup = wl_container_of(listener, popup, commit); + update_popup_position(popup); +} + +static void handle_input_method_new_popup_surface(struct wl_listener *listener, + void *data) { + struct input_method_relay *relay = + wl_container_of(listener, relay, input_method_new_popup_surface); + + struct input_method_popup *popup = znew(*popup); + popup->popup_surface = data; + popup->relay = relay; + + popup->destroy.notify = handle_popup_surface_destroy; + wl_signal_add(&popup->popup_surface->events.destroy, &popup->destroy); + + popup->commit.notify = handle_popup_surface_commit; + wl_signal_add(&popup->popup_surface->surface->events.commit, &popup->commit); + + // popup->tree = wlr_scene_subsurface_tree_create( + // relay->popup_tree, popup->popup_surface->surface); + // node_descriptor_create(&popup->tree->node, dwl_NODE_DESC_IME_POPUP, NULL); + + popup->tree = wlr_scene_tree_create(layers[LyrIMPopup]); + popup->scene_surface = wlr_scene_subsurface_tree_create( + popup->tree, popup->popup_surface->surface); + popup->scene_surface->node.data = popup; + + wl_list_insert(&relay->popups, &popup->link); + + update_popup_position(popup); +} + +static void handle_new_input_method(struct wl_listener *listener, void *data) { + struct input_method_relay *relay = + wl_container_of(listener, relay, new_input_method); + struct wlr_input_method_v2 *input_method = data; + if (seat != input_method->seat) { + return; + } + + if (relay->input_method) { + wlr_log(WLR_INFO, "Attempted to connect second input method to a seat"); + wlr_input_method_v2_send_unavailable(input_method); + return; + } + + relay->input_method = input_method; + + relay->input_method_commit.notify = handle_input_method_commit; + wl_signal_add(&relay->input_method->events.commit, + &relay->input_method_commit); + + relay->input_method_grab_keyboard.notify = handle_input_method_grab_keyboard; + wl_signal_add(&relay->input_method->events.grab_keyboard, + &relay->input_method_grab_keyboard); + + relay->input_method_destroy.notify = handle_input_method_destroy; + wl_signal_add(&relay->input_method->events.destroy, + &relay->input_method_destroy); + + relay->input_method_new_popup_surface.notify = + handle_input_method_new_popup_surface; + wl_signal_add(&relay->input_method->events.new_popup_surface, + &relay->input_method_new_popup_surface); + + update_text_inputs_focused_surface(relay); + update_active_text_input(relay); +} + +/* Conveys state from active text-input to input-method */ +static void send_state_to_input_method(struct input_method_relay *relay) { + assert(relay->active_text_input && relay->input_method); + + struct wlr_input_method_v2 *input_method = relay->input_method; + struct wlr_text_input_v3 *input = relay->active_text_input->input; + + /* TODO: only send each of those if they were modified */ + if (input->active_features & WLR_TEXT_INPUT_V3_FEATURE_SURROUNDING_TEXT) { + wlr_input_method_v2_send_surrounding_text( + input_method, input->current.surrounding.text, + input->current.surrounding.cursor, input->current.surrounding.anchor); + } + wlr_input_method_v2_send_text_change_cause(input_method, + input->current.text_change_cause); + if (input->active_features & WLR_TEXT_INPUT_V3_FEATURE_CONTENT_TYPE) { + wlr_input_method_v2_send_content_type(input_method, + input->current.content_type.hint, + input->current.content_type.purpose); + } + wlr_input_method_v2_send_done(input_method); +} + +static void handle_text_input_enable(struct wl_listener *listener, void *data) { + struct text_input *text_input = wl_container_of(listener, text_input, enable); + struct input_method_relay *relay = text_input->relay; + + update_active_text_input(relay); + if (relay->active_text_input == text_input) { + update_popups_position(relay); + send_state_to_input_method(relay); + } +} + +static void handle_text_input_disable(struct wl_listener *listener, + void *data) { + struct text_input *text_input = + wl_container_of(listener, text_input, disable); + /* + * When the focus is moved between surfaces from different clients and + * then the old client sends "disable" event, the relay ignores it and + * doesn't deactivate the input-method. + */ + update_active_text_input(text_input->relay); +} + +static void handle_text_input_commit(struct wl_listener *listener, void *data) { + struct text_input *text_input = wl_container_of(listener, text_input, commit); + struct input_method_relay *relay = text_input->relay; + + if (relay->active_text_input == text_input) { + update_popups_position(relay); + send_state_to_input_method(relay); + } +} + +static void handle_text_input_destroy(struct wl_listener *listener, + void *data) { + struct text_input *text_input = + wl_container_of(listener, text_input, destroy); + wl_list_remove(&text_input->enable.link); + wl_list_remove(&text_input->disable.link); + wl_list_remove(&text_input->commit.link); + wl_list_remove(&text_input->destroy.link); + wl_list_remove(&text_input->link); + update_active_text_input(text_input->relay); + free(text_input); +} + +static void handle_new_text_input(struct wl_listener *listener, void *data) { + struct input_method_relay *relay = + wl_container_of(listener, relay, new_text_input); + struct wlr_text_input_v3 *wlr_text_input = data; + if (seat != wlr_text_input->seat) { + return; + } + + struct text_input *text_input = znew(*text_input); + text_input->input = wlr_text_input; + text_input->relay = relay; + wl_list_insert(&relay->text_inputs, &text_input->link); + + text_input->enable.notify = handle_text_input_enable; + wl_signal_add(&text_input->input->events.enable, &text_input->enable); + + text_input->disable.notify = handle_text_input_disable; + wl_signal_add(&text_input->input->events.disable, &text_input->disable); + + text_input->commit.notify = handle_text_input_commit; + wl_signal_add(&text_input->input->events.commit, &text_input->commit); + + text_input->destroy.notify = handle_text_input_destroy; + wl_signal_add(&text_input->input->events.destroy, &text_input->destroy); + + update_text_inputs_focused_surface(relay); +} + +/* + * Usually this function is not called because the client destroys the surface + * role (like xdg_toplevel) first and input_method_relay_set_focus() is called + * before wl_surface is destroyed. + */ +static void handle_focused_surface_destroy(struct wl_listener *listener, + void *data) { + struct input_method_relay *relay = + wl_container_of(listener, relay, focused_surface_destroy); + assert(relay->focused_surface == data); + + input_method_relay_set_focus(relay, NULL); +} + +struct input_method_relay *input_method_relay_create() { + struct input_method_relay *relay = znew(*relay); + wl_list_init(&relay->text_inputs); + wl_list_init(&relay->popups); + relay->popup_tree = wlr_scene_tree_create(&scene->tree); + + relay->new_text_input.notify = handle_new_text_input; + wl_signal_add(&text_input_manager->events.text_input, &relay->new_text_input); + + relay->new_input_method.notify = handle_new_input_method; + wl_signal_add(&input_method_manager->events.input_method, + &relay->new_input_method); + + relay->focused_surface_destroy.notify = handle_focused_surface_destroy; + + return relay; +} + +void input_method_relay_finish(struct input_method_relay *relay) { + wl_list_remove(&relay->new_text_input.link); + wl_list_remove(&relay->new_input_method.link); + free(relay); +} + +void input_method_relay_set_focus(struct input_method_relay *relay, + struct wlr_surface *surface) { + if (relay->focused_surface == surface) { + wlr_log(WLR_INFO, "The surface is already focused"); + return; + } + + if (relay->focused_surface) { + wl_list_remove(&relay->focused_surface_destroy.link); + } + relay->focused_surface = surface; + if (surface) { + wl_signal_add(&surface->events.destroy, &relay->focused_surface_destroy); + } + + update_text_inputs_focused_surface(relay); + update_active_text_input(relay); +} -- 2.50.1 ```
This pull request has changes conflicting with the target branch.
  • dwl.c
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin Shugyousha/input-protocols-v2:Shugyousha/input-protocols-v2
git switch Shugyousha/input-protocols-v2
Sign in to join this conversation.
No description provided.