Skip to content
This repository was archived by the owner on Jan 17, 2026. It is now read-only.
This repository was archived by the owner on Jan 17, 2026. It is now read-only.

Lỗi backspace sai khi câu đang tự động fill bằng suggestion của browser #27

@hien-ngo29

Description

@hien-ngo29

Mình sẽ lấy ví dụ Firefox (có thể tái hiện lên hầu hết các browser khác như Chrome,...).
Giải thích khá dài dòng, mình sẽ để video ở đây cho mọi người dễ hình dung

output_clip.mp4

Lỗi chỉ xảy ra ở chế độ gõ non-preedit (tất cả các chế độ gõ trừ chế độ số 1 và số 2. Số 2 cũng là kiểu non-preedit nhưng vấn đề hơi khác một chút, tạm thời để sau)

Cách hoạt động kiểu gõ non-preedit của bamboo/lotus:

  1. Ví dụ người dùng gõ từ Hien
  2. Gõ thêm chữ r để thêm dấu hỏi
  3. Thực chất bộ gõ không thể trực tiếp thay đổi chữ e trong từ trên thành ngay được mà phải xóa chữ e và cả từ đứng sau nó -> Với từ trên, bộ gõ sẽ tự động fake 2 lần phím backspace vào chương trình
  4. Thêm từ vào và bổ sung lại các từ đứng sau nó -> Trong trường hợp này là thêm từ ẻn
  5. Ra từ Hiẻn
  6. Tương tự nếu thêm chữ e để cho ra ê vào

Tuy nhiên, đã xảy ra một vấn đề, nếu từ đang gõ đang bị chèn bởi chữ suggest của browser (xem video trên cho dễ hiểu) thì khi backspace sẽ bị "thiếu" một backspace nữa (bởi 1 backspace đã dùng để xóa chữ suggest kia), gây ra vấn đề như trên.


Mình đã thử sửa bằng một idea đó là dùng một field mang tên currentWordNearCursor (tên biến đã rõ ràng các bạn có thể hiểu để làm gì). Biến này được update thông qua hook SetSurroundingText(...) của ibus để cập nhật từ mới mỗi khi bạn làm chuột chữ di chuyển

currentWordNearCursor string

ibus-lotus/engine.go

Lines 161 to 163 in 4ccb3a1

func (e *IBusBambooEngine) SetSurroundingText(text dbus.Variant, cursorPos uint32, anchorPos uint32) *dbus.Error {
var str = reflect.ValueOf(reflect.ValueOf(text.Value()).Index(2).Interface()).String()
e.currentWordNearCursor = getLastWordFromSentence(str)

Từ đây, chúng ta có thể thông qua hàm updatePreviousTextInBatch(oldText, newText string, isWordBreakRune bool) xử lý phần tự động backspace và thêm từ mới (chỉ cần tập trung tham số newText sẽ chứa từ đáng ra sẽ được output, như ví dụ trên qua hàm này sẽ là từ hiẻn nhưng chưa backspace và thêm từ mới). Chỉ cần một chút thuật toán đối chiếu với field currentWordNearCursor để check có bị dư một từ sau khi thêm backspace để thêm một extra backspace nữa là xong.

func (e *IBusBambooEngine) updatePreviousTextInBatch(oldText, newText string, isWordBreakRune bool) {
offsetRunes, nBackSpace := e.getOffsetRunes(newText, oldText)
if nBackSpace > 0 {
e.SendBackSpace(nBackSpace)
}
var buffer = []string{string(offsetRunes)}
if isWordBreakRune {
e.preeditor.Reset()
buffer = append(buffer, "")
}
// isDirty means containing runes that are not committed
var isDirty = false
for i := 0; i < len(keyPressChan); i++ {
var keyEvents = <-keyPressChan
var keyVal, keyCode, state = keyEvents[0], keyEvents[1], keyEvents[2]
isValidKey := isValidState(state) && e.isValidKeyVal(keyVal)
if isValidKey {
var commitText, isWordBreakRune0 = e.getCommitText(keyVal, keyCode, state)
buffer[len(buffer)-1] = commitText
if isWordBreakRune0 {
buffer = append(buffer, "")
}
isDirty = true
} else {
if isDirty {
e.batchCommit(oldText, strings.Join(buffer, ""), nBackSpace, isWordBreakRune)
buffer = []string{""}
}
e.ForwardKeyEvent(keyVal, keyCode, state)
}
}
if isDirty {
e.batchCommit(oldText, strings.Join(buffer, ""), nBackSpace, isWordBreakRune)
return
}
log.Printf("Updating Previous Text %s ---> %s\n", oldText, newText)
e.bsCommitText(offsetRunes)
}

Tuy nhiên một vấn đề nhỏ là hàm SendBackspace() sử dụng ForwardKeyEvent(...) của ibus để fake phím backspace (trong hầu hết các chế độ gõ), cách này khiến cho hook SetSurroundingText(...) không trigger được cho dù trỏ chữ có di chuyển (với backspace bình thường thì được), dẫn đến việc currentWordNearCursor cũng không được update đúng cách và không lấy được từ sau khi backspace (chưa thêm chữ mới).

func (e *IBusBambooEngine) SendBackSpace(n int) {
// Gtk/Qt apps have a serious sync issue with fake backspaces
// and normal string committing, so we'll not commit right now
// but delay until all the sent backspaces got processed.
var now = time.Now()
var delta = 50*1000*1000 - (now.UnixNano() - e.lastCommitText)
if delta > 0 {
time.Sleep(time.Duration(delta) * time.Nanosecond)
}
if e.checkInputMode(config.XTestFakeKeyEventIM) {
e.setFakeBackspace(int32(n))
var sleep = func() {
var count = 0
for e.getFakeBackspace() > 0 && count < 10 {
time.Sleep(5 * time.Millisecond)
count++
}
}
log.Printf("Sendding %d backspace via XTestFakeKeyEvent\n", n)
time.Sleep(10 * time.Millisecond)
x11SendBackspace(n, 0)
sleep()
time.Sleep(time.Duration(n) * (10 + BACKSPACE_INTERVAL) * time.Millisecond)
} else if e.checkInputMode(config.SurroundingTextIM) {
time.Sleep(20 * time.Millisecond)
log.Printf("Sendding %d backspace via SurroundingText\n", n)
e.DeleteSurroundingText(-int32(n), uint32(n))
time.Sleep(20 * time.Millisecond)
} else if e.checkInputMode(config.ForwardAsCommitIM) {
time.Sleep(20 * time.Millisecond)
log.Printf("Sendding %d backspace via forwardAsCommitIM\n", n)
for i := 0; i < n; i++ {
e.ForwardKeyEvent(IBusBackSpace, XkBackspace-8, 0)
e.ForwardKeyEvent(IBusBackSpace, XkBackspace-8, IBusReleaseMask)
}
time.Sleep(time.Duration(n) * (20 + BACKSPACE_INTERVAL) * time.Millisecond)
} else if e.checkInputMode(config.ShiftLeftForwardingIM) {
time.Sleep(30 * time.Millisecond)
log.Printf("Sendding %d Shift+Left via shiftLeftForwardingIM\n", n)
for i := 0; i < n; i++ {
e.ForwardKeyEvent(IBusLeft, XkLeft-8, IBusShiftMask)
e.ForwardKeyEvent(IBusLeft, XkLeft-8, IBusReleaseMask)
}
time.Sleep(time.Duration(n) * (30 + BACKSPACE_INTERVAL) * time.Millisecond)
} else if e.checkInputMode(config.BackspaceForwardingIM) {
time.Sleep(30 * time.Millisecond)
log.Printf("Sendding %d backspace via backspaceForwardingIM\n", n)
for i := 0; i < n; i++ {
e.ForwardKeyEvent(IBusBackSpace, XkBackspace-8, 0)
e.ForwardKeyEvent(IBusBackSpace, XkBackspace-8, IBusReleaseMask)
}
time.Sleep(time.Duration(n) * (30 + BACKSPACE_INTERVAL) * time.Millisecond)
} else {
fmt.Println("There's something wrong with wmClasses")
}
}

Không biết có ai có solution để giúp mình vấn đề này không. Chỉ cần có một cách để trigger hook SetSurroundingText(...) mà không phụ thuộc vào điều kiện của ibus là được. Cám ơn mn bỏ thời gian để đọc 🥇Nếu được thì giúp mình một PR có ích nhé. (đang dang dở tại commit 4ccb3a1)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghelp wantedExtra attention is needed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions