Skip to content

Commit

Permalink
Improve special key handling (#270)
Browse files Browse the repository at this point in the history
* refactor(api): timeout variable

property setter and getter are unnecessary as they add no logic.

* refactor(api): early attribute access

to improve readability move the case to handle a non-callable attribute
first as all the other code is only relevant for the callable case.

* fix: key-down / key-up discrepancy

Versions before Qemu 2.12 (from ~2016) had a bug in handling keys
requiring modifiers, e.g. Shift for upper-case letters: While the
original VNC protocol was designed for remoting X11 applications, where
high-level KeySyms are used (for example "Upper-case 'A'), Qemu (and
other hardware virtualization tools) are required to translate that back
into low-level KeyCode events:
1. Left-Shift down
2. 'A' down
3. 'A' up
4. Left-Shift up

With the default `force_caps = False` this is fully done by Qemu, where
version 2.8 shows the following bug: After sending a capital "A" even
the next lower-case "a" is still received as an upper-case "A". This
continues until the next upper-case letter is sent.

With `force_capse = True` `vncdotool` does part of that translation and
adds KeySyms for Shift-Down and Shift-Up itself, but reversed 3. and 4:
3. Left-Shift up
4. 'a' up

Again this triggers a Qemu bug as now there is a discrepancy: 2. downs
an *upper*-case "A" while 4. releases a *lower*-case "a". For `vndotool`
is is irrelevant, but graphical VNC clients were affected.

To not confuse Qemu release the keys in reverse order.

Switch to logging the actual KeySyms sent to the VNC server.
  • Loading branch information
pmhahn committed Sep 30, 2023
1 parent d61eaf5 commit a4bb988
Show file tree
Hide file tree
Showing 2 changed files with 13 additions and 21 deletions.
21 changes: 5 additions & 16 deletions vncdotool/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def __init__(
) -> None:
self.factory = factory
self.queue: queue.Queue[Any] = queue.Queue()
self._timeout = timeout
self.timeout = timeout
self.protocol: Optional[VNCDoToolClient] = None

def __enter__(self: TProxy) -> TProxy:
Expand All @@ -56,16 +56,6 @@ def __enter__(self: TProxy) -> TProxy:
def __exit__(self, *_: Any) -> None:
self.disconnect()

@property
def timeout(self) -> Optional[float]:
"""Timeout in seconds for API requests."""
return self._timeout

@timeout.setter
def timeout(self, timeout: float) -> None:
"""Timeout in seconds for API requests."""
self._timeout = timeout

def connect(
self, host: str, port: int = 5900, family: socket.AddressFamily = socket.AF_INET
) -> None:
Expand All @@ -84,6 +74,8 @@ def disconnector(protocol: VNCDoToolClient) -> None:

def __getattr__(self, attr: str) -> Any:
method = getattr(self.factory.protocol, attr)
if not callable(method):
return getattr(self.protocol, attr)

def threaded_call(
protocol: VNCDoToolClient, *args: Any, **kwargs: Any
Expand Down Expand Up @@ -111,7 +103,7 @@ def callable_threaded_proxy(*args: Any, **kwargs: Any) -> Any:
kwargs,
)
try:
result = self.queue.get(timeout=self._timeout)
result = self.queue.get(timeout=self.timeout)
except queue.Empty:
raise TimeoutError("Timeout while waiting for client response")

Expand All @@ -120,10 +112,7 @@ def callable_threaded_proxy(*args: Any, **kwargs: Any) -> Any:

return result

if callable(method):
return callable_threaded_proxy
else:
return getattr(self.protocol, attr)
return callable_threaded_proxy

def __dir__(self) -> List[str]:
return dir(self.__class__) + dir(self.factory.protocol)
Expand Down
13 changes: 8 additions & 5 deletions vncdotool/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,23 +195,26 @@ def keyPress(self: TClient, key: str) -> TClient:
key: string: either [a-z] or a from KEYMAP
"""
log.debug("keyPress %s", key)
self.keyDown(key)
self.keyUp(key)
keys = self._decodeKey(key)
log.debug("keyPress %s", keys)
for k in keys:
self.keyEvent(k, down=True)
for k in reversed(keys):
self.keyEvent(k, down=False)

return self

def keyDown(self: TClient, key: str) -> TClient:
log.debug("keyDown %s", key)
keys = self._decodeKey(key)
log.debug("keyDown %s", keys)
for k in keys:
self.keyEvent(k, down=True)

return self

def keyUp(self: TClient, key: str) -> TClient:
log.debug("keyUp %s", key)
keys = self._decodeKey(key)
log.debug("keyUp %s", keys)
for k in keys:
self.keyEvent(k, down=False)

Expand Down

0 comments on commit a4bb988

Please sign in to comment.