From 6e55a2a7845d7a75dce6da381857eecf2d244e7c Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Mon, 15 Sep 2014 05:34:18 +0000 Subject: [PATCH] #545: honour double-click time (X11 and win32) and distance (X11 only): * add "double_click.time" and "double_click.distance" caps * add ability to run gui class to directly see the values we get from the OS * add them to xpra info * add this info to bug reports * add code to patch "xsettings-blob" on the fly to add those values * trigger fake "xsettings" update to ensure values get applied * add test app to view current settings and test them git-svn-id: https://xpra.org/svn/Xpra/trunk@7613 3bb7dfac-3a0b-4e04-842a-767bc560f471 --- src/tests/xpra/test_apps/test_double_click.py | 69 +++++++++++++++++++ src/xpra/client/gtk_base/bug_report.py | 2 + src/xpra/client/ui_client_base.py | 4 +- src/xpra/platform/gui.py | 65 ++++++++++++++++- src/xpra/platform/win32/gui.py | 7 ++ src/xpra/platform/xposix/gui.py | 39 +++++++++++ src/xpra/server/server_base.py | 26 +++++-- src/xpra/server/source.py | 6 ++ src/xpra/x11/server.py | 19 ++++- src/xpra/x11/x11_server_base.py | 1 + 10 files changed, 230 insertions(+), 8 deletions(-) create mode 100755 src/tests/xpra/test_apps/test_double_click.py diff --git a/src/tests/xpra/test_apps/test_double_click.py b/src/tests/xpra/test_apps/test_double_click.py new file mode 100755 index 0000000000..66f020baa6 --- /dev/null +++ b/src/tests/xpra/test_apps/test_double_click.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# This file is part of Xpra. +# Copyright (C) 2013 Antoine Martin + +import sys +import pygtk +pygtk.require('2.0') +import gtk +import gobject + + +class TestForm(object): + + def __init__(self): + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.window.connect("destroy", gtk.main_quit) + self.window.set_default_size(320, 200) + self.window.set_border_width(20) + + vbox = gtk.VBox() + self.info = gtk.Label("") + self.show_click_settings() + gobject.timeout_add(1000, self.show_click_settings) + vbox.pack_start(self.info, False, False, 0) + self.label = gtk.Label("Ready") + vbox.pack_start(self.label, False, False, 0) + + self.eventbox = gtk.EventBox() + self.eventbox.connect('button-press-event', self.button_press_event) + self.eventbox.add_events(gtk.gdk.BUTTON_PRESS_MASK) + self.eventbox.add_events(gtk.gdk.BUTTON_RELEASE_MASK) + vbox.pack_start(self.eventbox, True, True, 0) + + self.window.add(vbox) + self.window.show_all() + + def show_click_settings(self): + root = gtk.gdk.get_default_root_window() + screen = root.get_screen() + #use undocumented constants found in source: + try: + t = screen.get_setting("gtk-double-click-time") + except: + t = "" + try: + d = screen.get_setting("gtk-double-click-distance") + except: + d = "" + self.info.set_text("Time (ms): %s, Distance: %s" % (t, d)) + return True + + def button_press_event(self, obj, event): + if event.type == gtk.gdk._3BUTTON_PRESS: + self.label.set_text("Triple Click!") + elif event.type == gtk.gdk._2BUTTON_PRESS: + self.label.set_text("Double Click!") + elif event.type == gtk.gdk.BUTTON_PRESS: + self.label.set_text("Click") + else: + self.label.set_text("Unexpected event: %s" % event) + +def main(): + TestForm() + gtk.main() + + +if __name__ == "__main__": + main() + sys.exit(0) diff --git a/src/xpra/client/gtk_base/bug_report.py b/src/xpra/client/gtk_base/bug_report.py index a0ad18ceb6..983efd592f 100755 --- a/src/xpra/client/gtk_base/bug_report.py +++ b/src/xpra/client/gtk_base/bug_report.py @@ -110,6 +110,7 @@ def get_gl_info(): get_gl_info = None from xpra.net.net_util import get_info as get_net_info from xpra.platform.paths import get_info as get_path_info + from xpra.platform.gui import get_info as get_gui_info from xpra.version_util import get_version_info, get_platform_info, get_host_info def get_sys_info(): d = { @@ -124,6 +125,7 @@ def get_sys_info(): "host" : get_host_info(), "paths" : get_path_info(), "gtk" : get_gtk_version_info(), + "gui" : get_gui_info(), "user" : get_user_info(), "env" : os.environ, "config" : read_xpra_defaults(), diff --git a/src/xpra/client/ui_client_base.py b/src/xpra/client/ui_client_base.py index 0cb58f8c56..e2b5c6d286 100644 --- a/src/xpra/client/ui_client_base.py +++ b/src/xpra/client/ui_client_base.py @@ -30,7 +30,7 @@ from xpra.client.keyboard_helper import KeyboardHelper from xpra.platform import set_application_name from xpra.platform.features import MMAP_SUPPORTED, SYSTEM_TRAY_SUPPORTED, CLIPBOARD_WANT_TARGETS, CLIPBOARD_GREEDY, CLIPBOARDS -from xpra.platform.gui import init as gui_init, ready as gui_ready, get_native_notifier_classes, get_native_tray_classes, get_native_system_tray_classes, get_native_tray_menu_helper_classes, ClientExtras +from xpra.platform.gui import init as gui_init, ready as gui_ready, get_double_click_time, get_double_click_distance, get_native_notifier_classes, get_native_tray_classes, get_native_system_tray_classes, get_native_tray_menu_helper_classes, ClientExtras from xpra.codecs.codec_constants import get_PIL_decodings from xpra.codecs.loader import codec_versions, has_codec, get_codec, PREFERED_ENCODING_ORDER, ALL_NEW_ENCODING_NAMES_TO_OLD, OLD_ENCODING_NAMES_TO_NEW, PROBLEMATIC_ENCODINGS from xpra.codecs.video_helper import getVideoHelper, NO_GFX_CSC_OPTIONS @@ -910,6 +910,8 @@ def make_hello(self): "notifications" : self.client_supports_notifications, "cursors" : self.client_supports_cursors, "bell" : self.client_supports_bell, + "double_click.time" : get_double_click_time(), + "double_click.distance" : get_double_click_distance(), "sound.server_driven" : True, "encoding.client_options" : True, "encoding_client_options" : True, diff --git a/src/xpra/platform/gui.py b/src/xpra/platform/gui.py index ee8a641852..4cc0b163be 100644 --- a/src/xpra/platform/gui.py +++ b/src/xpra/platform/gui.py @@ -4,6 +4,7 @@ # Xpra is released under the terms of the GNU GPL v2, or, at your option, any # later version. See the file COPYING for details. +import sys _init_done = False def init(): @@ -45,12 +46,39 @@ def system_bell(*args): def get_native_notifier_classes(): return [] +def get_double_click_time(): + return -1 + +def get_double_click_distance(): + return -1 + def gl_check(): return None #no problem take_screenshot = None ClientExtras = None + +def get_info_base(): + def fname(v): + try: + return v.__name__ + except: + return str(v) + def fnames(l): + return [fname(x) for x in l] + return { + "native_tray_menu_helpers" : fnames(get_native_tray_menu_helper_classes()), + "native_trays" : fnames(get_native_tray_classes()), + "native_system_trays" : fnames(get_native_system_tray_classes()), + "system_bell" : fname(system_bell), + "native_notifiers" : fnames(get_native_notifier_classes()), + "double_click.time" : get_double_click_time(), + "double_click.distance" : get_double_click_distance(), + } +get_info = get_info_base + + from xpra.platform import platform_import platform_import(globals(), "gui", False, "do_ready", @@ -62,4 +90,39 @@ def gl_check(): "get_native_tray_classes", "get_native_system_tray_classes", "get_native_notifier_classes", - "system_bell") + "get_double_click_time", "get_double_click_distance", + "system_bell", + "get_info") + + +def main(): + from xpra.platform import init as platform_init,clean + from xpra.util import nonl, pver + try: + platform_init("GUI-Properties") + verbose = "-v" in sys.argv or "--verbose" in sys.argv + if verbose: + from xpra.log import get_all_loggers + for x in get_all_loggers(): + x.enable_debug() + + #naughty, but how else can I hook this up? + import os + if os.name=="posix": + try: + from xpra.x11.gtk_x11 import gdk_display_source + assert gdk_display_source + except: + from xpra.x11.gtk3_x11 import gdk_display_source #@Reimport + assert gdk_display_source + + i = get_info() + for k in sorted(i.keys()): + v = i[k] + print("* %s : %s" % (k.ljust(32), nonl(pver(v)))) + finally: + clean() + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/xpra/platform/win32/gui.py b/src/xpra/platform/win32/gui.py index 85347d39ad..d0c414d7d8 100644 --- a/src/xpra/platform/win32/gui.py +++ b/src/xpra/platform/win32/gui.py @@ -59,6 +59,13 @@ def gl_check(): return "disabled when running under wine" return None +def get_double_click_time(): + try: + import win32gui #@UnresolvedImport + return win32gui.GetDoubleClickTime() + except: + return 0 + class ClientExtras(object): def __init__(self, client, opts): diff --git a/src/xpra/platform/xposix/gui.py b/src/xpra/platform/xposix/gui.py index 1a26c238ae..3f530a92b3 100644 --- a/src/xpra/platform/xposix/gui.py +++ b/src/xpra/platform/xposix/gui.py @@ -41,6 +41,33 @@ def get_native_system_tray_classes(): return get_native_tray_classes() +def _get_xsettings(): + try: + from xpra.x11.xsettings import XSettingsHelper + xsh = XSettingsHelper() + return xsh.get_settings() + except Exception as e: + log("_get_xsettings error: %s", e) + return None + +def _get_xsettings_int(name, default_value): + s = _get_xsettings() + if not s: + return default_value + from xpra.x11.xsettings_prop import XSettingsTypeInteger + _, values = s + for setting_type, prop_name, value, _ in values: + if setting_type==XSettingsTypeInteger and prop_name==name: + return value + return default_value + +def get_double_click_time(): + return _get_xsettings_int("Net/DoubleClickTime", -1) + +def get_double_click_distance(): + return _get_xsettings_int("Net/DoubleClickDistance", -1) + + def system_bell(window, device, percent, pitch, duration, bell_class, bell_id, bell_name): global device_bell if device_bell is False: @@ -62,6 +89,18 @@ def x11_bell(): return False +def get_info(): + from xpra.platform.gui import get_info_base + i = get_info_base() + s = _get_xsettings() + if s: + serial, values = s + i["xsettings.serial"] = serial + for _,name,value,_ in values: + i["xsettings.%s" % name] = value + return i + + class ClientExtras(object): def __init__(self, client, opts): self.client = client diff --git a/src/xpra/server/server_base.py b/src/xpra/server/server_base.py index 6bd60a4bf8..e08337458f 100644 --- a/src/xpra/server/server_base.py +++ b/src/xpra/server/server_base.py @@ -69,6 +69,9 @@ def __init__(self): self.cursors = False self.default_dpi = 96 self.dpi = 96 + #duplicated from Server Source... + self.double_click_time = -1 + self.double_click_distance = -1 self.supports_clipboard = False self.supports_dbus_proxy = False self.dbus_helper = None @@ -520,7 +523,8 @@ def hello_oked(self, proto, packet, c, auth_caps): elif not ss.share: self.disconnect_client(p, NEW_CLIENT, "this client had not enabled sharing") disconnected += 1 - share_count += 1 + else: + share_count += 1 if detach_request: self.disconnect_client(proto, DONE, "%i other clients have been disconnected" % disconnected) @@ -529,11 +533,23 @@ def hello_oked(self, proto, packet, c, auth_caps): if not is_request and ui_client: if share_count>0: log.info("sharing with %s other client(s)", share_count) - self.dpi = c.intget("dpi", self.default_dpi) + self.dpi = self.default_dpi + self.double_click_time = -1 + self.double_click_distance = -1 + else: + self.dpi = c.intget("dpi", self.default_dpi) + self.double_click_time = c.intget("double_click.time", -1) + self.double_click_distance = c.intget("double_click.distance", -1) + #if we're not sharing, reset all the settings: + reset = share_count==0 + #some non-posix clients never send us 'resource-manager' settings + #so just use a fake one to ensure the dpi gets applied: if self.dpi>0: - #some non-posix clients never send us 'resource-manager' settings - #so just use a fake one to ensure the dpi gets applied: - self.update_server_settings({'resource-manager' : ""}, reset=(share_count==0)) + self.update_server_settings({'resource-manager' : ""}, reset=reset) + #same for xsettings and double click settings: + #fake an empty xsettings update + if self.double_click_time>0 or self.double_click_distance>0: + self.update_server_settings({"xsettings-blob" : (0, [])}, reset=reset) #max packet size from client (the biggest we can get are clipboard packets) proto.max_packet_size = 1024*1024 #1MB proto.send_aliases = c.dictget("aliases") diff --git a/src/xpra/server/source.py b/src/xpra/server/source.py index 70a35dacd2..601534a07a 100644 --- a/src/xpra/server/source.py +++ b/src/xpra/server/source.py @@ -323,6 +323,8 @@ def init_vars(self): self.notify_startup_complete = False self.control_commands = [] self.supports_transparency = False + self.double_click_time = -1 + self.double_click_distance = -1 #what we send back in hello packet: self.ui_client = True self.wants_aliases = True @@ -539,6 +541,8 @@ def parse_batch_int(value, varname): self.notify_startup_complete = c.boolget("notify-startup-complete") self.control_commands = c.strlistget("control_commands") self.supports_transparency = HAS_ALPHA and c.boolget("encoding.transparency") + self.double_click_time = c.intget("double_click.time") + self.double_click_distance = c.intget("double_click.distance") self.desktop_size = c.intpair("desktop_size") if self.desktop_size is not None: @@ -1099,6 +1103,8 @@ def battr(k, name): "lz4", "lzo"): battr(prop, prop) for prop, name in {"clipboard_enabled" : "clipboard", + "double_click_time" : "double_click.time", + "double_click_distance" : "double_click.distance", "send_windows" : "windows", "send_cursors" : "cursors", "send_notifications" : "notifications", diff --git a/src/xpra/x11/server.py b/src/xpra/x11/server.py index 10ff020e41..bdbf3a3cd4 100644 --- a/src/xpra/x11/server.py +++ b/src/xpra/x11/server.py @@ -814,15 +814,17 @@ def update_server_settings(self, settings, reset=False): settingslog("ignoring xsettings update: %s", settings) return if reset: + #FIXME: preserve serial? (what happens when we change values which had the same serial?) self.reset_settings() self._settings = self.default_xsettings or {} old_settings = dict(self._settings) settingslog("server_settings: old=%s, updating with=%s", nonl(old_settings), nonl(settings)) + settingslog("overrides: dpi=%s, double click time=%s, double click distance=%s", self.dpi, self.double_click_time, self.double_click_distance) self._settings.update(settings) root = gtk.gdk.get_default_root_window() for k, v in settings.items(): #cook the "resource-manager" value to add the DPI: - if k == "resource-manager" and self.dpi>0: + if k=="resource-manager" and self.dpi>0: value = v.decode("utf-8") #parse the resources into a dict: values={} @@ -845,6 +847,21 @@ def update_server_settings(self, settings, reset=False): self._settings["resource-manager"] = value v = value.encode("utf-8") + #cook xsettings to add double-click settings: + #(as those may not be present in xsettings on some platforms.. like win32 and osx) + if k=="xsettings-blob" and (self.double_click_time>0 or self.double_click_distance>0): + from xpra.x11.xsettings_prop import XSettingsTypeInteger + def set_xsettings_int(name, value): + #remove existing one, if any: + serial, values = v + new_values = [(_t,_n,_v,_s) for (_t,_n,_v,_s) in values if _n!=name] + new_values.append((XSettingsTypeInteger, name, value, 0)) + return serial, new_values + if self.double_click_time>0: + v = set_xsettings_int("Net/DoubleClickTime", self.double_click_time) + if self.double_click_distance>0: + v = set_xsettings_int("Net/DoubleClickDistance", self.double_click_distance) + if k not in old_settings or v != old_settings[k]: def root_set(p): settingslog("server_settings: setting %s to %s", nonl(p), nonl(v)) diff --git a/src/xpra/x11/x11_server_base.py b/src/xpra/x11/x11_server_base.py index d6f4d17c15..e40c7fc270 100644 --- a/src/xpra/x11/x11_server_base.py +++ b/src/xpra/x11/x11_server_base.py @@ -427,6 +427,7 @@ def delfile(msg): def _process_server_settings(self, proto, packet): settings = packet[1] + log("process_server_settings: %s", settings) self.update_server_settings(settings) def update_server_settings(self, settings, reset=False):