Send text_input_rectangle requests for text-input #253
Manually merged
dnkl
merged 1 commits from tdeo/foot:text-input-rectangle
into master
1 year ago
Loading…
Reference in new issue
There is no content yet.
Delete Branch 'tdeo/foot:text-input-rectangle'
Deleting a branch is permanent. It CANNOT be undone. Continue?
This enables input method popups to show in the correct location.
Closes #309
Looks nice and clean!
I haven't actually tested it yet, and I'll need to benchmark it as well, before it can be merged. But looks good so far!
Oh, and another thing: please add an entry to CHANGELOG.md as well
bool
ime_check_cursor_rect(struct seat *seat, struct terminal *term)
{
I'm not too fond of the name; it doesn't say what it is checking, and doesn't imply anything is modified.
But I can't really come up with a better name right now. If you can come up with something better, that's great, otherwise we can keep it as-is for the time being.
Yeah, I'm not sure either.
int x =
term->margins.left + term->grid->cursor.point.col * term->cell_width;
int y =
term->margins.top + term->grid->cursor.point.row * term->cell_height;
y
will be incorrect here, if the user has scrolled up in the scrollback history. You will need to adjust it, seeb25b201819/render.c (L1987)
tll_foreach(term->wl->seats, it)
if (it->item.kbd_focus == term)
ime_update_cursor_rect(&it->item, term);
This looping of seats is a bit unfortunate. But there's currently no other way to do it. I think we should change that, but I also think that's better done in a follow up PR. So let's keep this the way it is right now.
Actually, it might make more sense to keep things the way they are; 99% of all systems will have exactly one seat - tracking which seats are currently focusing a specific terminal instance just adds code complexity (we'd need to sync the seat list in the terminal instances), and increases the memory foot print.
The remaining %1 is likely to have, what, 2-3 seats? That's unlikely to be a performance problem.
Tested briefly with anthywl. Didn't play around long enough to figure out how to get a popup so haven't been able to test/verify popup positioning.
But benchmarks are looking good; no measurable differences with or without this PR.
93cb34487c
to3c7d4b0a46
1 year agoIt needs a sway patch which you might not have.
b32bb7e4ae
to4f26c7e856
1 year agoWhen scrolling up, the popup shows up outside of the window with my Sway patch, but I believe this is a problem with the Sway patch and not with sending those bounds.
GTK seems to send out-of-bounds coordinates too, and it lets the compositor hide the popup rather than keeping it in an outdated location or the top corner.
Thanks, you're right, I did not.
Ok, sounds reasonable. I don't use IME that much personally, but I do think it makes sense to "scoll out" the popup along with the cursor, and hiding it when outside the windo, rather than bounding it to the window dimensions.
row &= term->grid->num_rows - 1;
if (term->hide_cursor)
col = row = 0;
Can you explain the reasoning behind this? Why not use the correct coordinates even if the cursor is hidden?
I've removed this now, it's true that the top left is probably worse than wherever the cursor would be.
void
ime_update_cursor_rect(struct seat *seat, struct terminal *term)
{
if (!ime_check_cursor_rect(seat, term))
We need to check that the text-input interface is available, and just return here if it isn't. See #260, and #259
4f26c7e856
toc435765379
1 year agorow += term->grid->offset;
row -= term->grid->view;
row &= term->grid->num_rows - 1;
I've finally been able to test this with your Sway+wlroots patches, and that made me realize that this doesn't work in scrollback search mode.
I.e. if you do Ctrl+Shift+R to enter search mode, then start typing, the popup is positioned near the terminal cursor, not the search input "box".
Positioning the popup for the search box may be tricky, depending on how fine grained you want it. There is state that can be used to derive the cursor position, but it isn't straight forward. The state you want to use is:
term->render.search_glyph_offset
: this is the offset into the seach input string where we start rendering (i.e if the search string is wider than the window, we may for example only render the end of it).term->search.cursor
: where in the search input string new characters are inserted.What makes things complicated is that the glyph offset is in cells, not characters, and the cursor is in characters, not cells.
Let's say the search input string is "😊|", with the bar representing the cursor. The cursor would in this case be
1
. The corresponding glyph offset is2
.The name,
search_glyph_offset
, is a bad name, but was introduced before the search mode handled multi-column characters.Other options might be to position the popup at the right margin. Or maybe at the beginning of the search input box, although that may prove to be annoying as the left side of the input box moves as the user types (until it reaches the left side of the window).
c435765379
to819c8e8736
1 year agoWorks very well, nicely done! Just two minor nitpicks...
* IME popup location support: foot now sends the location of the cursor
so any popup can be displayed near the text that is being typed.
Forgot to tell you before: feel free to add yourself to the contributors list a bit further down, if you like.
size_t post_preedit_cells =
wcswidth(&term->search.buf[term->search.cursor],
term->search.len - term->search.cursor);
wcswidth()
returns anint
, and will return-1
if the string contains a non-printable character. I'm wondering if we should at least convert-1
to0
, since-1
in asize_t
is a really large value...To trigger, press ctrl+shift+v (instead of ctrl+v, to paste)
Related: #314
I think it's better solved by #314 than adding 4 checks here.
Agreed. That should suffice.
seat->ime.cursor_rect.x = x;
seat->ime.cursor_rect.y = y;
seat->ime.cursor_rect.width = width;
seat->ime.cursor_rect.height = height;
Convert coordination unit to pixel
cursor_rect
should be the area of the pre-edit cursor when there is the pre-edit string.row &= term->grid->num_rows - 1;
x = term->margins.left + col * term->cell_width;
y = term->margins.top + row * term->cell_height;
width = term->width - term->margins.right - x;
Arguments of set_cursor_rectangle() are cursor area, so
width
has to the width of the cursor.@ngkz I've now set the cursor width to 0px when there's no preedit text entered.
This is simpler and it seems consistent with what other applications I tested do.
819c8e8736
to6c6aee6e62
1 year agoOh, all gtk widgets except VTE are calling set_cursor_rectangle with width=0. Somehow I hadn't noticed it even though I read the wayland debug log many times.
x = term->margins.left + col * term->cell_width;
y = term->margins.top + row * term->cell_height;
width = preedit_cells * term->cell_width;
height = term->cell_height;
What I meant was that it should pass the area of
the cursor(the insertion point to set_cursor_rectangle(), not the area of the pre-edit text. (As you said, it should be▌
or|
) atset_cursor_rectangle(insertion point x, insertion point y, 0, line height)
, through)Otherwise, the suggestion popup and the candidate selection popup won't show at the insertion point like other apps.
Mutter shows the popup at center-top or center-bottom of the cursor rectangle.
Sway looks like that shows the popup at left-bottom of the cursor rectangle.
Aha, I got why I didn't get my point across.
The cursor in foot is a range tuple and it points to whole pre-edit text when the insert point is the end of the text. I've never seen apps like this before.
I've made !term->is_searching path behave like GTKText with this quick-n-dirty patch.
I think that it needs extracting positioning logic from the renderer or letting renderer positioning the cursor rectangle.
It is a range while pre-editing, and in this case the range is the
cursor_begin
andcursor_end
parameters fromtext_input_v3::preedit_string()
. I.e. it is ultimately controlled by the IME engine.If either
cursor_begin
orcursor_end
is-1
, the cursor is hidden. If they are equal, we render a bar,|
, at the indicated pre-edit position. Otherwise we render a rectangle covering the whole pre-edit cursor range. Note that this isn't necessarily the entire pre-edit text - it can be a sub-component of it.When not pre-editing, the cursor is not a range, just a single point, rendered as either a block, underline or bar, depending on the current cursor style setting.
We definitely do not want to duplicate that much logic. Updating the cursor rectangle in the corresponding render functions is fine:
render_ime_preedit()
could handle the!searching && is_preediting
branchrender_search_box()
could handle thesearching
(bothis_preediting
and!is_preediting
) branches.render_cell()
, but I rather not add all theif
-statements needed (!searching && !is_preediting
) there, since that is a performance critical code path.The first two,
render_ime_preedit()
andrender_search_box()
are mutually exclusive; we explicitly reset the IME state when entering/leaving search mode.This is a bug in foot: it doesn't handle the case where the cursor is after the preedit text.
(anthywl does set both cursor_begin and cursor_end to the end of the pre-edit, and it is handled incorrectly by foot. I didn't realize this was a foot bug until now.)
Hmm, now that I see that VTE sets the cursor to the cursor block, I think it makes sense for foot to do that too.
It's actually not true (anymore) that foot hasn't been tested with anything but
cursor_begin == cursor_end == 0
.But
cursor_begin == cursor_end == end-of-preedit
hasn't been tested specifically.Which is why it would make sense to update the rectangle in the
render_*
functions :)When does it do this? Enabling debug logging in foot:
Oh wait... I see now :/
#318 should fix the pre-edit cursor issue, when positioned after the pre-edit string.
Aha, The IME uses it for highlighting a word while selecting conversion candidates, right? I misunderstood what text-input-v3 protocol does. In GNOME on Wayland, It looks like Mutter or Fcitx doesn't send ranged
preedit_string()
due to a bug.6c6aee6e62
todbba35227e
1 year agoI think I got everything mentioned so far.
@ngkz, can you test again, with #318 also applied?
It works 👍👍EDIT: I couldn't test hollow cursor path due to a bug of Mutter or Fcitx5
EDIT 2: Sorry, I forgot to test a search input box, wait a moment.
You forgot to update the cursor position at render.c:2453.
seat->ime.serial++;
memcpy(&seat->ime.cursor_rect.sent, &seat->ime.cursor_rect.pending,
sizeof(seat->ime.cursor_rect.sent));
I suggest using structure assignment here. It is clearer and safer than memcpy.
seat->ime.cursor_rect.pending.height);
memcpy(&seat->ime.cursor_rect.sent, &seat->ime.cursor_rect.pending,
sizeof(seat->ime.cursor_rect.sent));
I suggest using structure assignment.
int height;
} sent;
} cursor_rect;
I suggest that define the named rectangle struct and declare
pending
andsent
with it (to use structure assignment)+1
Just a couple of comments below.
Note that I haven't yet tested the code, but will as soon as possible. I'll add more comments if I encounter any issues.
if (term->is_searching)
goto update;
Can we add comments before the two
goto
s and describe the rectangle is set byrender_xyz()
in these two cases?seat->ime.cursor_rect.pending.y = y / term->scale;
seat->ime.cursor_rect.pending.width = width / term->scale;
seat->ime.cursor_rect.pending.height = height / term->scale;
Can we move the conversion to surface coordinates to the call to
zwp_text_input_v3_set_cursor_rectangle()
, and keeppending
andsent
in pixels?it->item.ime.cursor_rect.pending.width = 1 / term->scale;
it->item.ime.cursor_rect.pending.height = term->cell_height / term->scale;
}
}
I think we should add a function to set the
pending
rectangle, taking theterm
pointer, and thex
,y
,width
andheight
values.@tdeo I've merged a ton of PRs, several related to scrollback search. I strongly suggest you rebase this PR on latest master.
I mean, it needs to update
seat->ime.cursor_rect.pending
at:dbba35227e
toae1a931d37
1 year ago}
term_ime_set_cursor_rect(
term, x, y, (end * start) * term->cell_width, term->cell_height);
typo
ae1a931d37
to9b8ed57eb6
1 year agoGetting close now I think :)
I've tested it with Sway+anthywl and couldn't find any functional issues - good job!
}
void ime_update_cursor_rect(struct seat *seat, struct terminal *term) {
// Set in render_ime_preedit()
style:
seat->ime.cursor_rect.pending.x = x / term->scale;
seat->ime.cursor_rect.pending.y = y / term->scale;
seat->ime.cursor_rect.pending.width = width / term->scale;
seat->ime.cursor_rect.pending.height = height / term->scale;
You shouldn't divide with the scaling factor here.
xassert(cell_idx >= glyph_offset);
xassert(cell_idx <= glyph_offset + visible_cells);
draw_bar(term, buf->pix[0], font, &fg, x, y);
term_ime_set_cursor_rect(term, margin + x, y, 1, term->cell_height);
Shouldn't
y
here byterm->height - margin - y - term->cell_height
?Speaking of which, I had trouble understanding why
x
andy
differs in thedraw_bar()
andterm_ime_set_cursor_rect()
calls. Even though I wrote the original code, and knows the coordinates are sub-surface local and that the cursor rectangle is in main surface coordinates.Perhaps we could wrap
term_ime_set_cursor_rect()
in a local macro inrender_search_box()
that translates from sub-surface to main-surface coordinates? Then the x/y parameters todraw_bar()
and the wrappedterm_ime_set_cursor_rect()
would match.Added two local macros that convert from subsurfce coordinates to window coordinates instead, let me know if you prefer a single wrapper macro instead.
Nah, that's fine. Better, actually :)
It works perfectly for me with GNOME on Wayland + Fcitx 5 + fcitx5-mozc-ut + gnome-shell-extension-kimpanel if issues dnkl pointed out are fixed.
9b8ed57eb6
tof00493e8c4
1 year agoterm_ime_set_cursor_rect(term,
margin + x, term->height - margin - y - term->cell_height,
1, term->cell_height);
}
I think you missed this one when adding the
WINDOW_{X,Y}
macros?seat->ime.cursor_rect.pending.x / term->scale,
seat->ime.cursor_rect.pending.y / term->scale,
seat->ime.cursor_rect.pending.width / term->scale,
seat->ime.cursor_rect.pending.height / term->scale);
I noticed that x of the cursor rectangle becomes negative and the im popup is shown outside of the window when there is long preedit text and the caret is at the hidden part of the text.
I think x and y of the rectangle should be limited in the window.
Yeah, I left it like that because GTK does the same thing.
Does mutter not hide it when it's out of bounds? I'd expect it to.
f00493e8c4
to80601c993a
1 year ago80601c993a
to95c0c89cac
1 year agoLGTM!
@ngkz @tdeo I'll merge as soon as the discussion around bounding the coordinates to the window has been resolved.
87d6739c03
into master manually 1 year ago@tdeo @ngkz thank you both, for all the time and effort!
Note: I've also decided to backport this to 1.6.3, to be released shortly.
Reviewers
87d6739c03
.