Option to use IME for input detection
This commit is contained in:
parent
3934d727e4
commit
600a5c7cfc
|
|
@ -281,6 +281,7 @@
|
|||
|<<input.mouse.rocker_gestures,input.mouse.rocker_gestures>>|Enable Opera-like mouse rocker gestures.
|
||||
|<<input.partial_timeout,input.partial_timeout>>|Timeout (in milliseconds) for partially typed key bindings.
|
||||
|<<input.spatial_navigation,input.spatial_navigation>>|Enable spatial navigation.
|
||||
|<<input.use_ime,input.use_ime>>|Use Input Method Editor for input detection.
|
||||
|<<keyhint.blacklist,keyhint.blacklist>>|Keychains that shouldn't be shown in the keyhint dialog.
|
||||
|<<keyhint.delay,keyhint.delay>>|Time (in milliseconds) from pressing a key to seeing the keyhint dialog.
|
||||
|<<keyhint.radius,keyhint.radius>>|Rounding radius (in pixels) for the edges of the keyhint dialog.
|
||||
|
|
@ -3695,6 +3696,15 @@ Type: <<types,Bool>>
|
|||
|
||||
Default: +pass:[false]+
|
||||
|
||||
[[input.use_ime]]
|
||||
=== input.use_ime
|
||||
Use Input Method Editor for input detection.
|
||||
Try setting this to `true` if you sometimes click into input elements in a web page and qutebrowser doesn't automatically switch to input mode.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
[[keyhint.blacklist]]
|
||||
=== keyhint.blacklist
|
||||
Keychains that shouldn't be shown in the keyhint dialog.
|
||||
|
|
|
|||
|
|
@ -193,6 +193,9 @@ class TabEventFilter(QObject):
|
|||
self._check_insertmode_on_release = True
|
||||
return
|
||||
|
||||
if config.val.input.use_ime:
|
||||
return
|
||||
|
||||
if elem.is_editable():
|
||||
log.mouse.debug("Clicked editable element!")
|
||||
if config.val.input.insert_mode.auto_enter:
|
||||
|
|
|
|||
|
|
@ -1998,6 +1998,16 @@ input.mode_override:
|
|||
supports_pattern: true
|
||||
desc: Mode to change to when focusing on a tab/URL changes.
|
||||
|
||||
input.use_ime:
|
||||
default: false
|
||||
type: Bool
|
||||
desc: >-
|
||||
Use Input Method Editor for input detection.
|
||||
|
||||
Try setting this to `true` if
|
||||
you sometimes click into input elements in a web page and qutebrowser
|
||||
doesn't automatically switch to input mode.
|
||||
|
||||
## keyhint
|
||||
|
||||
keyhint.blacklist:
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@
|
|||
|
||||
from typing import cast, Optional
|
||||
|
||||
from qutebrowser.qt.core import pyqtSlot, QObject, QEvent, qVersion
|
||||
from qutebrowser.qt.gui import QKeyEvent, QWindow
|
||||
from qutebrowser.qt.core import pyqtSlot, QObject, QEvent, qVersion, Qt, QRectF
|
||||
from qutebrowser.qt.gui import QKeyEvent, QWindow, QInputMethodQueryEvent
|
||||
from qutebrowser.qt.widgets import QApplication
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.misc import quitter, objects
|
||||
from qutebrowser.utils import objreg, debug, log, qtutils
|
||||
from qutebrowser.utils import objreg, debug, log, qtutils, message, usertypes
|
||||
|
||||
|
||||
class EventFilter(QObject):
|
||||
|
|
@ -33,6 +35,7 @@ class EventFilter(QObject):
|
|||
QEvent.Type.ShortcutOverride: self._handle_key_event,
|
||||
}
|
||||
self._log_qt_events = "log-qt-events" in objects.debug_flags
|
||||
self._ime_event_handler: Optional[IMEEventHandler] = None
|
||||
|
||||
def install(self) -> None:
|
||||
objects.qapp.installEventFilter(self)
|
||||
|
|
@ -41,6 +44,15 @@ class EventFilter(QObject):
|
|||
def shutdown(self) -> None:
|
||||
objects.qapp.removeEventFilter(self)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_config_changed(self, option: str) -> None:
|
||||
"""Handle changes in the configuration."""
|
||||
if option != 'input.use_ime':
|
||||
return
|
||||
|
||||
log.config.debug("Option: {}".format(option))
|
||||
self.update_ime_registration()
|
||||
|
||||
def _handle_key_event(self, event: QKeyEvent) -> bool:
|
||||
"""Handle a key press/release event.
|
||||
|
||||
|
|
@ -82,7 +94,11 @@ class EventFilter(QObject):
|
|||
source = type(obj).__name__
|
||||
|
||||
ev_type_str = debug.qenum_key(QEvent, ev_type)
|
||||
log.misc.debug(f"{source} got event: {ev_type_str}")
|
||||
ignored_events = ["Timer", "MetaCall", "Paint", "UpdateRequest",
|
||||
"MouseMove", "CursorChange", "ToolTipChange", "PaletteChange", "FontChange",
|
||||
"StyleChange", "HoverMove"]
|
||||
if ev_type_str not in ignored_events:
|
||||
log.misc.debug(f"{source} got event: {ev_type_str}")
|
||||
|
||||
if (
|
||||
ev_type == QEvent.Type.DragEnter and
|
||||
|
|
@ -117,9 +133,128 @@ class EventFilter(QObject):
|
|||
self._activated = False
|
||||
raise
|
||||
|
||||
def update_ime_registration(self) -> None:
|
||||
"""Updates the IME registration based on current config value."""
|
||||
# Use IME for input detection if input.use_ime is true
|
||||
if config.val.input.use_ime:
|
||||
if self._ime_event_handler is not None:
|
||||
log.misc.debug("IME Event handler already registered")
|
||||
return
|
||||
|
||||
log.misc.debug("Registering IME Event handler")
|
||||
self._ime_event_handler = IMEEventHandler(parent=QApplication.instance())
|
||||
return
|
||||
|
||||
if self._ime_event_handler is None:
|
||||
return
|
||||
|
||||
log.misc.debug("Deregistering IME Event handler")
|
||||
self._ime_event_handler.deregister()
|
||||
self._ime_event_handler = None
|
||||
|
||||
|
||||
class IMEEventHandler(QObject):
|
||||
"""Handles the cursorRectangleChanged event from the inputMethod."""
|
||||
|
||||
def __init__(self, parent: QObject = None) -> None:
|
||||
super().__init__(parent)
|
||||
self._input_method = QApplication.inputMethod()
|
||||
assert self._input_method is not None
|
||||
self._input_method.cursorRectangleChanged.connect(
|
||||
self.cursor_rectangle_changed
|
||||
)
|
||||
self._last_seen_rect: Optional[QRectF] = None
|
||||
message.global_bridge.mode_left.connect(self._on_mode_left)
|
||||
|
||||
def deregister(self) -> None:
|
||||
"""Deregister the event handler."""
|
||||
try:
|
||||
message.global_bridge.mode_left.disconnect(self._on_mode_left)
|
||||
except Exception:
|
||||
log.misc.debug("Tried to disconnect mode_left but was not connected")
|
||||
|
||||
try:
|
||||
if self._input_method is not None:
|
||||
self._input_method.cursorRectangleChanged.disconnect(
|
||||
self.cursor_rectangle_changed
|
||||
)
|
||||
except Exception:
|
||||
log.misc.debug("Tried to disconnect cursor_rectangle_changed was not connected")
|
||||
self._last_seen_rect = None
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def _on_mode_left(self, mode: usertypes.KeyMode) -> None:
|
||||
"""Event handler for when a mode is left."""
|
||||
# Clear last_seen_rect when leaving insert mode
|
||||
log.modes.debug("Left mode {}".format(
|
||||
mode))
|
||||
if mode == usertypes.KeyMode.insert:
|
||||
self._last_seen_rect = None
|
||||
log.misc.debug("Cleared last seen rect")
|
||||
|
||||
@pyqtSlot()
|
||||
def cursor_rectangle_changed(self) -> None:
|
||||
"""Event handler for when the rectangle the cursor is in changes."""
|
||||
# May want to maintain last seen rect per window or tab.
|
||||
# Tab might work better with remembering focus for tabs
|
||||
# However, remembering modes per tab doesn't really seem to be implemented.
|
||||
# Could be confusing to the user to change modes for them and
|
||||
# this isn't something stock vim does.
|
||||
|
||||
# some input examples here https://www.javatpoint.com/html-form-input-types
|
||||
# <input type="date">: doesn't report as having an input method enabled,
|
||||
# although the existing heuristics pick it up
|
||||
|
||||
# if insert_mode_auto_load is false but there is a blinking cursor on
|
||||
# load clicking the scroll bar will enter insert mode
|
||||
assert self._input_method is not None
|
||||
|
||||
new_rect = self._input_method.cursorRectangle()
|
||||
if self._last_seen_rect and self._last_seen_rect.contains(new_rect):
|
||||
log.misc.debug("Rect changed but not handling because last seen rect already contains")
|
||||
return
|
||||
|
||||
self._last_seen_rect = new_rect
|
||||
|
||||
# implementation detail: qtwebengine doesn't set anchor for input
|
||||
# fields in a web page, qt widgets do, I haven't found any cases where
|
||||
# it doesn't work yet. Would like to compare with a "get focused thing
|
||||
# and examine" check first and compare across versions.
|
||||
anchor_rect = self._input_method.anchorRectangle()
|
||||
if anchor_rect:
|
||||
log.misc.debug("Rectangle changed but not handling because anchor rect is set")
|
||||
return
|
||||
|
||||
focused_window = objreg.last_focused_window()
|
||||
focus_object = QApplication.focusObject()
|
||||
query = None
|
||||
|
||||
if not new_rect and focus_object:
|
||||
# sometimes we get a rectangle changed event and the queried
|
||||
# rectangle is empty but we are still in an editable element. For
|
||||
# instance when pressing enter in a text box on confluence or jira
|
||||
# (including comment on the Qt instance) and tabbing between cells
|
||||
# on https://html-online.com/editor/
|
||||
# Checking ImEnabled helps in these cases.
|
||||
query = QInputMethodQueryEvent(Qt.InputMethodQuery.ImEnabled)
|
||||
QApplication.sendEvent(focus_object, query)
|
||||
|
||||
if new_rect or (query and query.value(Qt.InputMethodQuery.ImEnabled)):
|
||||
log.mouse.debug("Clicked editable element!")
|
||||
if config.val.input.insert_mode.auto_enter:
|
||||
modeman.enter(focused_window.win_id, usertypes.KeyMode.insert,
|
||||
'click', only_if_normal=True)
|
||||
else:
|
||||
log.mouse.debug("Clicked non-editable element!")
|
||||
if config.val.input.insert_mode.auto_leave:
|
||||
modeman.leave(focused_window.win_id, usertypes.KeyMode.insert,
|
||||
'click', maybe=True)
|
||||
|
||||
|
||||
def init() -> None:
|
||||
"""Initialize the global EventFilter instance."""
|
||||
event_filter = EventFilter(parent=objects.qapp)
|
||||
event_filter.install()
|
||||
quitter.instance.shutting_down.connect(event_filter.shutdown)
|
||||
config.instance.changed.connect(event_filter.on_config_changed)
|
||||
event_filter.update_ime_registration()
|
||||
|
|
|
|||
Loading…
Reference in New Issue