From 517860207b4eb67d3ffb3ff513541abd79eec620 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 4 Jan 2023 14:00:03 +0700 Subject: [PATCH] #1942 convert button-action to the more generic packet format --- xpra/client/client_window_base.py | 25 +++----- .../client/gtk_base/gtk_client_window_base.py | 3 +- xpra/client/mixins/window_manager.py | 57 ++++++++++++++----- xpra/client/rfb_protocol.py | 2 +- xpra/net/common.py | 2 + xpra/net/quic/connection.py | 2 +- xpra/platform/darwin/gui.py | 3 +- xpra/platform/darwin/shadow_server.py | 10 ++-- xpra/platform/win32/gui.py | 3 +- xpra/platform/win32/shadow_server.py | 9 +-- xpra/platform/xposix/gui.py | 8 +-- xpra/server/dbus/dbus_server.py | 2 +- xpra/server/mixins/input_server.py | 36 ++++++++++-- xpra/server/rfb/rfb_server.py | 2 +- xpra/x11/uinput_device.py | 2 +- xpra/x11/x11_server_core.py | 27 ++++----- 16 files changed, 120 insertions(+), 73 deletions(-) diff --git a/xpra/client/client_window_base.py b/xpra/client/client_window_base.py index 2946d27550..5bbe125e92 100644 --- a/xpra/client/client_window_base.py +++ b/xpra/client/client_window_base.py @@ -950,34 +950,23 @@ def _device_info(self, event): except AttributeError: return "" - def _button_action(self, button, event, depressed, *args): + def _button_action(self, button, event, depressed, props=None): if self._client.readonly or self._client.server_readonly or not self._client.server_pointer: return pointer_data, modifiers, buttons = self._pointer_modifiers(event) wid = self.get_mouse_event_wid(*pointer_data) mouselog("_button_action(%s, %s, %s) wid=%s / focus=%s / window wid=%i, device=%s, pointer=%s, modifiers=%s, buttons=%s", button, event, depressed, wid, self._client._focused, self._id, self._device_info(event), pointer_data, modifiers, buttons) - #map wheel buttons via translation table to support inverted axes: - server_button = button - if button>3: - server_button = self._client.wheel_map.get(button) - if not server_button: - return - server_buttons = [] - for b in buttons: - if b>3: - sb = self._client.wheel_map.get(button) - if not sb: - continue - b = sb - server_buttons.append(b) - def send_button(pressed): - self._client.send_button(wid, server_button, pressed, pointer_data, modifiers, server_buttons, *args) + device_id = 0 + def send_button(pressed, **kwargs): + sprops = props or {} + sprops.update(kwargs) + self._client.send_button(device_id, wid, button, pressed, pointer_data, modifiers, buttons, sprops) pressed_state = self.button_state.get(button, False) if SIMULATE_MOUSE_DOWN and pressed_state is False and depressed is False: mouselog("button action: simulating missing mouse-down event for window %s before mouse-up", wid) #(needed for some dialogs on win32): - send_button(True) + send_button(True, synthetic=True) self.button_state[button] = depressed send_button(depressed) diff --git a/xpra/client/gtk_base/gtk_client_window_base.py b/xpra/client/gtk_base/gtk_client_window_base.py index 7fa163ff06..b5bb8ecc53 100644 --- a/xpra/client/gtk_base/gtk_client_window_base.py +++ b/xpra/client/gtk_base/gtk_client_window_base.py @@ -2412,7 +2412,8 @@ def _do_scroll_event(self, event): if event.direction==Gdk.ScrollDirection.SMOOTH: mouselog("smooth scroll event: %s", event) pointer = self.get_pointer_data(event) - self._client.wheel_event(self._id, event.delta_x, -event.delta_y, pointer) + device_id = -1 + self._client.wheel_event(device_id, self._id, event.delta_x, -event.delta_y, pointer) return button_mapping = GDK_SCROLL_MAP.get(event.direction, -1) mouselog("do_scroll_event device=%s, direction=%s, button_mapping=%s", diff --git a/xpra/client/mixins/window_manager.py b/xpra/client/mixins/window_manager.py index fc52962eb9..66d0efbaff 100644 --- a/xpra/client/mixins/window_manager.py +++ b/xpra/client/mixins/window_manager.py @@ -412,18 +412,18 @@ def _process_pointer_position(self, packet): value = None show_pointer_overlay(value) - def send_wheel_delta(self, wid, button, distance, pointer=None, *args): + def send_wheel_delta(self, device_id, wid, button, distance, pointer=None, props=None): modifiers = self.get_current_modifiers() buttons = [] - mouselog("send_wheel_delta(%i, %i, %.4f, %s) precise wheel=%s, modifiers=%s, pointer=%s", - wid, button, distance, args, self.server_precise_wheel, modifiers, pointer) + mouselog("send_wheel_deltas% precise wheel=%s, modifiers=%s, pointer=%s", + (device_id, wid, button, distance, pointer, props), self.server_precise_wheel, modifiers, pointer) if self.server_precise_wheel: #send the exact value multiplied by 1000 (as an int) idist = round(distance*1000) if abs(idist)>0: packet = ["wheel-motion", wid, button, idist, - pointer, modifiers, buttons] + list(args) + pointer, modifiers, buttons] + list((props or {}).values()) mouselog("send_wheel_delta(..) %s", packet) self.send_positional(packet) return 0 @@ -436,8 +436,8 @@ def send_wheel_delta(self, wid, button, distance, pointer=None, *args): scaled_distance = math.sqrt(scaled_distance) steps = round(scaled_distance) for _ in range(steps): - self.send_button(wid, button, True, pointer, modifiers, buttons) - self.send_button(wid, button, False, pointer, modifiers, buttons) + for state in True, False: + self.send_button(device_id, wid, button, state, pointer, modifiers, buttons, props) #return remainder: scaled_remainder = steps if MOUSE_SCROLL_SQRT_SCALE: @@ -449,7 +449,7 @@ def send_wheel_delta(self, wid, button, distance, pointer=None, *args): return float(distance) - signed_remain_distance - def wheel_event(self, wid, deltax=0, deltay=0, pointer=(), deviceid=0): + def wheel_event(self, device_id=-1, wid=0, deltax=0, deltay=0, pointer=(), props=None): #this is a different entry point for mouse wheel events, #which provides finer grained deltas (if supported by the server) #accumulate deltas: @@ -457,22 +457,51 @@ def wheel_event(self, wid, deltax=0, deltay=0, pointer=(), deviceid=0): self.wheel_deltay += deltay button = self.wheel_map.get(6+int(self.wheel_deltax>0)) #RIGHT=7, LEFT=6 if button>0: - self.wheel_deltax = self.send_wheel_delta(wid, button, self.wheel_deltax, pointer, deviceid) + self.wheel_deltax = self.send_wheel_delta(device_id, wid, button, self.wheel_deltax, pointer, props) button = self.wheel_map.get(5-int(self.wheel_deltay>0)) #UP=4, DOWN=5 if button>0: - self.wheel_deltay = self.send_wheel_delta(wid, button, self.wheel_deltay, pointer, deviceid) + self.wheel_deltay = self.send_wheel_delta(device_id, wid, button, self.wheel_deltay, pointer, props) mouselog("wheel_event%s new deltas=%s,%s", - (wid, deltax, deltay, deviceid), self.wheel_deltax, self.wheel_deltay) + (device_id, wid, deltax, deltay), self.wheel_deltax, self.wheel_deltay) - def send_button(self, wid, button, pressed, pointer, modifiers, buttons, *args): + def send_button(self, device_id, wid, button, pressed, pointer, modifiers, buttons, props): pressed_state = self._button_state.get(button, False) if SKIP_DUPLICATE_BUTTON_EVENTS and pressed_state==pressed: mouselog("button action: unchanged state, ignoring event") return + #map wheel buttons via translation table to support inverted axes: + server_button = button + if button>3: + server_button = self.wheel_map.get(button, -1) + server_buttons = [] + for b in buttons: + if b>3: + sb = self.wheel_map.get(button) + if not sb: + continue + b = sb + server_buttons.append(b) self._button_state[button] = pressed - packet = ["button-action", wid, - button, pressed, - pointer, modifiers, buttons] + list(args) + if "pointer-button" in self.server_packet_types: + props = props or {} + if modifiers is not None: + props["modifiers"] = modifiers + props["buttons"] = server_buttons + if server_button!=button: + props["raw-button"] = button + if server_buttons!=buttons: + props["raw-buttons"] = buttons + seq = self.next_pointer_sequence(device_id) + packet = ["pointer-button", device_id, seq, wid, + server_button, pressed, pointer, props] + else: + if server_button==-1: + return + packet = ["button-action", wid, + server_button, pressed, + pointer, modifiers, server_buttons] + if props: + packet += list(props.values()) mouselog("button packet: %s", packet) self.send_positional(packet) diff --git a/xpra/client/rfb_protocol.py b/xpra/client/rfb_protocol.py index 5215ebf160..38aa2db9f5 100644 --- a/xpra/client/rfb_protocol.py +++ b/xpra/client/rfb_protocol.py @@ -81,7 +81,7 @@ def send_pointer_position(self, packet): self.do_send_pointer_event(button_mask, x, y) def send_button_action(self, packet): - log.warn("send_button_action(%s)", packet) + log("send_button_action(%s)", packet) if not self.check_wid(packet[1]): return #["button-action", wid, button, pressed, (x, y), modifiers, buttons] diff --git a/xpra/net/common.py b/xpra/net/common.py index 8d6ecf0548..66829a5ff5 100644 --- a/xpra/net/common.py +++ b/xpra/net/common.py @@ -54,7 +54,9 @@ class ConnectionClosedException(Exception): "configure-override-redirect", "lost-window", "window-icon", "draw", "eos", "cursor", "bell", + #pointer motion and events: "pointer-position", "pointer", + "button-action", "pointer-button", "pointer-grab", "pointer-ungrab", "webcam-stop", "webcam-ack", "set-clipboard-enabled", "clipboard-token", "clipboard-request", diff --git a/xpra/net/quic/connection.py b/xpra/net/quic/connection.py index 7868e01d86..73ddc24318 100644 --- a/xpra/net/quic/connection.py +++ b/xpra/net/quic/connection.py @@ -21,7 +21,7 @@ HttpConnection = Union[H0Connection, H3Connection] -DATAGRAM_PACKET_TYPES = os.environ.get("XPRA_QUIC_DATAGRAM_PACKET_TYPES", "pointer").split(",") +DATAGRAM_PACKET_TYPES = os.environ.get("XPRA_QUIC_DATAGRAM_PACKET_TYPES", "pointer,pointer-button").split(",") class XpraQuicConnection(Connection): diff --git a/xpra/platform/darwin/gui.py b/xpra/platform/darwin/gui.py index c4f390f323..2c42e41ceb 100644 --- a/xpra/platform/darwin/gui.py +++ b/xpra/platform/darwin/gui.py @@ -578,7 +578,8 @@ def normalize_precision(distance): client = window._client wid = window._id pointer = window.get_mouse_position() - client.wheel_event(wid, dx, dy, pointer) + device_id = -1 + client.wheel_event(device_id, wid, dx, dy, pointer) return True @objc.python_method diff --git a/xpra/platform/darwin/shadow_server.py b/xpra/platform/darwin/shadow_server.py index 17d4f4191b..9be31fe745 100644 --- a/xpra/platform/darwin/shadow_server.py +++ b/xpra/platform/darwin/shadow_server.py @@ -181,14 +181,14 @@ def fake_key(self, keycode, press): #this causes crashes, don't do it! #CG.CFRelease(e) - def do_process_button_action(self, proto, wid, button, pressed, pointer, modifiers, *args): - self._update_modifiers(proto, wid, modifiers) - device_id = -1 + def do_process_button_action(self, proto, device_id, wid, button, pressed, pointer, props): + if "modifiers" in props: + self._update_modifiers(proto, wid, props.get("modifiers")) pointer = self._process_mouse_common(proto, device_id, wid, pointer) if pointer: - self.button_action(wid, pointer, button, pressed, -1, *args) + self.button_action(device_id, wid, pointer, button, pressed, props) - def button_action(self, wid, pointer, button, pressed, _deviceid=-1, *_args): + def button_action(self, device_id, wid, pointer, button, pressed, props): if button<=3: #we should be using CGEventCreateMouseEvent #instead we clear previous clicks when a "higher" button is pressed... oh well diff --git a/xpra/platform/win32/gui.py b/xpra/platform/win32/gui.py index 96b592e87a..853e6f75a7 100644 --- a/xpra/platform/win32/gui.py +++ b/xpra/platform/win32/gui.py @@ -597,7 +597,8 @@ def handle_wheel(orientation, wParam, lParam): deltax = units deltay = 0 pointer = window.get_mouse_position() - client.wheel_event(wid, deltax, deltay, pointer) + device_id = -1 + client.wheel_event(device_id, wid, deltax, deltay, pointer) def mousewheel(_hwnd, _event, wParam, lParam): handle_wheel(VERTICAL, wParam, lParam) return 0 diff --git a/xpra/platform/win32/shadow_server.py b/xpra/platform/win32/shadow_server.py index 11f2cb503c..093b3450d3 100644 --- a/xpra/platform/win32/shadow_server.py +++ b/xpra/platform/win32/shadow_server.py @@ -585,15 +585,16 @@ def get_keyboard_config(self, _props=None): def fake_key(self, keycode, press): fake_key(keycode, press) - def do_process_button_action(self, proto, wid, button, pressed, pointer, modifiers, *args): - self._update_modifiers(proto, wid, modifiers) + def do_process_button_action(self, proto, device_id, wid, button, pressed, pointer, props): + if "modifiers" in props: + self._update_modifiers(proto, wid, props.get("modifiers")) device_id = -1 pointer = self._process_mouse_common(proto, device_id, wid, pointer) if pointer: self.get_server_source(proto).user_event() - self.button_action(wid, pointer, button, pressed, -1, *args) + self.button_action(device_id, wid, pointer, button, pressed, props) - def button_action(self, wid, pointer, button, pressed, deviceid=-1, *args): + def button_action(self, device_id, wid, pointer, button, pressed, props): event = BUTTON_EVENTS.get((button, pressed)) if event is None: log.warn("no matching event found for button=%s, pressed=%s", button, pressed) diff --git a/xpra/platform/xposix/gui.py b/xpra/platform/xposix/gui.py index c1b266e01d..122889c673 100644 --- a/xpra/platform/xposix/gui.py +++ b/xpra/platform/xposix/gui.py @@ -669,8 +669,8 @@ def do_xi_button(self, event, device): return button = event.detail depressed = (event.name == "XI_ButtonPress") - args = self.get_pointer_extra_args(event) - window._button_action(button, event, depressed, *args) + props = self.get_pointer_extra_args(event) + window._button_action(button, event, depressed, props) def do_xi_motion(self, event, device): window = self.window @@ -727,17 +727,17 @@ def do_xi_motion(self, event, device): #whatever happens, update our motion cached values: mv.update(event.valuators) #send plain motion first, if any: + props = self.get_pointer_extra_args(event) if unused_valuators: xinputlog("do_xi_motion(%s, %s) wid=%s / focus=%s / window wid=%i, device=%s, pointer=%s, modifiers=%s, buttons=%s", event, device, wid, window._client._focused, window._id, event.device, pointer_data, modifiers, buttons) device_id = 0 - props = self.get_pointer_extra_args(event) client.send_mouse_position(device_id, wid, pointer_data, modifiers, buttons, props) #now see if we have anything to send as a wheel event: if dx!=0 or dy!=0: xinputlog("do_xi_motion(%s, %s) wheel deltas: dx=%i, dy=%i", event, device, dx, dy) #normalize (xinput is always using 15 degrees?) - client.wheel_event(wid, dx/XINPUT_WHEEL_DIV, dy/XINPUT_WHEEL_DIV, pointer_data, event.device) + client.wheel_event(event.device, wid, dx/XINPUT_WHEEL_DIV, dy/XINPUT_WHEEL_DIV, pointer_data, props) def get_pointer_extra_args(self, event): def intscaled(f): diff --git a/xpra/server/dbus/dbus_server.py b/xpra/server/dbus/dbus_server.py index d21f2deadd..4011f1b1b9 100755 --- a/xpra/server/dbus/dbus_server.py +++ b/xpra/server/dbus/dbus_server.py @@ -125,7 +125,7 @@ def MouseClick(self, button, pressed): button, pressed = ni(button), nb(pressed) self.log(".MouseClick%s", (button, pressed)) device_id = -1 - self.server.button_action(0, None, button, pressed, device_id) + self.server.button_action(device_id, 0, None, button, pressed) @dbus.service.method(INTERFACE, in_signature='iiii') diff --git a/xpra/server/mixins/input_server.py b/xpra/server/mixins/input_server.py index e690ac6931..27f75765c9 100644 --- a/xpra/server/mixins/input_server.py +++ b/xpra/server/mixins/input_server.py @@ -366,6 +366,25 @@ def _process_mouse_common(self, proto, device_id, wid, opointer, props=None): def do_process_mouse_common(self, proto, device_id, wid, pointer, props): return True + def _process_pointer_button(self, proto, packet): + mouselog("process_pointer_button(%s, %s)", proto, packet) + if self.readonly: + return + ss = self.get_server_source(proto) + if ss is None: + return + ss.user_event() + self.last_mouse_user = ss.uuid + self.set_ui_driver(ss) + device_id, seq, wid, button, pressed, pointer, props = packet[1:8] + if device_id>=0: + highest_seq = self.pointer_sequence.get(device_id, 0) + if 0<=seq<=highest_seq: + mouselog(f"dropped outdated sequence {seq}, latest is {highest_seq}") + return + self.pointer_sequence[device_id] = seq + self.do_process_button_action(proto, device_id, wid, button, pressed, pointer, props) + def _process_button_action(self, proto, packet): mouselog("process_button_action(%s, %s)", proto, packet) if self.readonly: @@ -376,9 +395,15 @@ def _process_button_action(self, proto, packet): ss.user_event() self.last_mouse_user = ss.uuid self.set_ui_driver(ss) - self.do_process_button_action(proto, *packet[1:]) + wid, button, pressed, pointer, modifiers, buttons = packet[1:7] + device_id = 0 + props = { + "modifiers" : modifiers, + "buttons" : buttons, + } + self.do_process_button_action(proto, device_id, wid, button, pressed, pointer, props) - def do_process_button_action(self, proto, wid, button, pressed, pointer, modifiers, *args): + def do_process_button_action(self, proto, device_id, wid, button, pressed, pointer, props): """ all servers should implement this method """ @@ -472,9 +497,10 @@ def init_packet_handlers(self): "layout-changed" : self._process_layout, "keymap-changed" : self._process_keymap, #mouse: - "button-action" : self._process_button_action, - "pointer" : self._process_pointer, - "pointer-position" : self._process_pointer_position, + "pointer-button" : self._process_pointer_button, #v5 + "button-action" : self._process_button_action, #pre v5 + "pointer" : self._process_pointer, #v5 + "pointer-position" : self._process_pointer_position, #pre v5 #setup: "input-devices" : self._process_input_devices, }) diff --git a/xpra/server/rfb/rfb_server.py b/xpra/server/rfb/rfb_server.py index 8ce59f2373..e0d100eec4 100644 --- a/xpra/server/rfb/rfb_server.py +++ b/xpra/server/rfb/rfb_server.py @@ -151,7 +151,7 @@ def process_pointer_event(): if buttons & mask != self.rfb_buttons & mask: pressed = bool(buttons & mask) mouselog(" %spressing button %i", ["un",""][pressed], 1+button) - self.button_action(0, (x, y), 1+button, pressed, -1) + self.button_action(device_id, 0, (x, y), 1+button, pressed) self.rfb_buttons = buttons self.idle_add(process_pointer_event) diff --git a/xpra/x11/uinput_device.py b/xpra/x11/uinput_device.py index e0a73e8338..fab0adaca6 100644 --- a/xpra/x11/uinput_device.py +++ b/xpra/x11/uinput_device.py @@ -59,7 +59,7 @@ def __init__(self, device, device_path): if v<=(2, 2): self.wheel_motion(4, 1) - def click(self, button, pressed, *_args): + def click(self, button, pressed, props): #this multiplier is based on the values defined in 71-xpra-virtual-pointer.rules as: #MOUSE_WHEEL_CLICK_COUNT=360 #MOUSE_WHEEL_CLICK_ANGLE=1 diff --git a/xpra/x11/x11_server_core.py b/xpra/x11/x11_server_core.py index 37086ef277..8ba481a9b4 100644 --- a/xpra/x11/x11_server_core.py +++ b/xpra/x11/x11_server_core.py @@ -67,8 +67,8 @@ def move_pointer(self, screen_no, x, y, props): with xsync: X11Keyboard.xtest_fake_motion(screen_no, x, y) - def click(self, button, pressed, *_args): - mouselog("xtest_fake_button(%i, %s)", button, pressed) + def click(self, button, pressed, props): + mouselog("xtest_fake_button(%i, %s, %s)", button, pressed, props) with xsync: X11Keyboard.xtest_fake_button(button, pressed) @@ -1014,27 +1014,24 @@ def _update_modifiers(self, proto, wid, modifiers): if wid==self.get_focus(): ss.user_event() - def do_process_button_action(self, proto, wid, button, pressed, pointer, - modifiers, _buttons=(), deviceid=-1, *_args): - self._update_modifiers(proto, wid, modifiers) - #TODO: pass extra args - device_id = -1 + def do_process_button_action(self, proto, device_id, wid, button, pressed, pointer, props): + if "modifiers" in props: + self._update_modifiers(proto, wid, props.get("modifiers")) props = {} if self._process_mouse_common(proto, device_id, wid, pointer, props): - self.button_action(wid, pointer, button, pressed, deviceid) + self.button_action(device_id, wid, pointer, button, pressed, props) - def button_action(self, wid, pointer, button, pressed, deviceid=-1, *args): - device = self.get_pointer_device(deviceid) - assert device, "pointer device %s not found" % deviceid + def button_action(self, device_id, wid, pointer, button, pressed, props): + device = self.get_pointer_device(device_id) + assert device, "pointer device %s not found" % device_id if button in (4, 5) and wid: self.record_wheel_event(wid, button) try: - mouselog("%s%s", device.click, (button, pressed, args)) + mouselog("%s%s", device.click, (button, pressed, props)) with xsync: - device.click(button, pressed, *args) + device.click(button, pressed, props) except XError: - mouselog("button_action(%s, %s, %s, %s, %s, %s)", - wid, pointer, button, pressed, deviceid, args, exc_info=True) + mouselog("button_action%s", (device_id, wid, pointer, button, pressed, props), exc_info=True) mouselog.error("Error: failed (un)press mouse button %s", button) def record_wheel_event(self, wid, button):