diff --git a/src/icons/bandwidth_limit.png b/src/icons/bandwidth_limit.png new file mode 100644 index 0000000000..05d9a738f6 Binary files /dev/null and b/src/icons/bandwidth_limit.png differ diff --git a/src/xpra/client/gtk_base/gtk_tray_menu_base.py b/src/xpra/client/gtk_base/gtk_tray_menu_base.py index 6a6d993c8d..fd5906ea4c 100644 --- a/src/xpra/client/gtk_base/gtk_tray_menu_base.py +++ b/src/xpra/client/gtk_base/gtk_tray_menu_base.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 os from xpra.gtk_common.gobject_compat import import_gtk, import_glib gtk = import_gtk() glib = import_glib() @@ -41,6 +42,13 @@ SHOW_SHUTDOWN = envbool("XPRA_SHOW_SHUTDOWN", True) WINDOWS_MENU = envbool("XPRA_SHOW_WINDOWS_MENU", True) +BANDWIDTH_MENU_OPTIONS = [] +for x in os.environ.get("XPRA_BANDWIDTH_MENU_OPTIONS", "1,2,5,10,20,50,100").split(","): + try: + BANDWIDTH_MENU_OPTIONS.append(int(x)) + except ValueError: + log.warn("Warning: invalid bandwidth menu option '%s'", x) + LOSSLESS = "Lossless" QUALITY_OPTIONS_COMMON = { @@ -645,6 +653,7 @@ def make_picturemenuitem(self): menu = gtk.Menu() picture_menu_item.set_submenu(menu) self.popup_menu_workaround(menu) + menu.append(self.make_bandwidthlimitmenuitem()) if self.client.windows_enabled and len(self.client.get_encodings())>1: menu.append(self.make_encodingsmenuitem()) if self.client.can_scale: @@ -654,6 +663,41 @@ def make_picturemenuitem(self): picture_menu_item.show_all() return picture_menu_item + def make_bandwidthlimitmenuitem(self): + bandwidth_limit_menu_item = self.menuitem("Bandwidth Limit", "bandwidth_limit.png") + menu = gtk.Menu() + self.popup_menu_workaround(menu) + initial_value = self.client.bandwidth_limit or 0 + if initial_value>0: + initial_value //= 1000000 + def bwitem(bwlimit=0): + if bwlimit>0: + label = "%iMbps" % bwlimit + else: + label = "None" + c = CheckMenuItem(label) + c.set_draw_as_radio(True) + c.set_active(initial_value==bwlimit) + def activate_cb(item, *args): + self.client.bandwidth_limit = bwlimit*1000*1000 + self.client.send_bandwidth_limit() + c.connect("toggled", activate_cb) + return c + options = BANDWIDTH_MENU_OPTIONS + if initial_value and initial_value not in options: + options.append(initial_value) + menu.append(bwitem(0)) + menu.append(gtk.SeparatorMenuItem()) + for v in sorted(options): + menu.append(bwitem(v)) + bandwidth_limit_menu_item.set_submenu(menu) + bandwidth_limit_menu_item.show_all() + def set_bwlimitmenu(*_args): + set_sensitive(bandwidth_limit_menu_item, self.client.server_bandwidth_limit_change) + self.client.after_handshake(set_bwlimitmenu) + return bandwidth_limit_menu_item + + def make_encodingsmenuitem(self): encodings = self.menuitem("Encoding", "encoding.png", "Choose picture data encoding", None) set_sensitive(encodings, False) diff --git a/src/xpra/client/ui_client_base.py b/src/xpra/client/ui_client_base.py index 22eb48a11d..9b95d54a0f 100644 --- a/src/xpra/client/ui_client_base.py +++ b/src/xpra/client/ui_client_base.py @@ -1949,6 +1949,7 @@ def parse_server_capabilities(self): self.server_randr = c.boolget("resize_screen") log("server has randr: %s", self.server_randr) self.server_av_sync = c.boolget("av-sync.enabled") + self.server_bandwidth_limit_change = c.boolget("network.bandwidth-limit-change") avsynclog("av-sync: server=%s, client=%s", self.server_av_sync, self.av_sync) e = c.strget("encoding") if e: @@ -2265,6 +2266,10 @@ def send_input_devices(self, fmt, input_devices): self.send("input-devices", fmt, input_devices) + def send_bandwidth_limit(self): + self.send("bandwidth-limit", self.bandwidth_limit) + + def start_sending_webcam(self): with self.webcam_lock: self.do_start_sending_webcam(self.webcam_option) diff --git a/src/xpra/server/server_base.py b/src/xpra/server/server_base.py index aa75d9297d..abd7519280 100644 --- a/src/xpra/server/server_base.py +++ b/src/xpra/server/server_base.py @@ -36,7 +36,7 @@ from xpra.keyboard.mask import DEFAULT_MODIFIER_MEANINGS from xpra.server.server_core import ServerCore, get_thread_info from xpra.server.control_command import ArgsControlCommand, ControlError -from xpra.simple_stats import to_std_unit +from xpra.simple_stats import to_std_unit, std_unit from xpra.child_reaper import getChildReaper from xpra.os_util import BytesIOClass, thread, livefds, load_binary_file, pollwait, monotonic_time, bytestostr, OSX, WIN32, POSIX, PYTHON3 from xpra.util import typedict, flatten_dict, updict, envbool, envint, log_screen_sizes, engs, repr_ellipsized, csv, iround, \ @@ -730,6 +730,7 @@ def init_packet_handlers(self): "webcam-stop": self._process_webcam_stop, "webcam-frame": self._process_webcam_frame, "connection-data": self._process_connection_data, + "bandwidth-limit": self._process_bandwidth_limit, } self._authenticated_ui_packet_handlers = self._default_packet_handlers.copy() self._authenticated_ui_packet_handlers.update({ @@ -1198,15 +1199,7 @@ def drop_client(reason="unknown", *args): self.disconnect_client(proto, reason, *args) def get_window_id(wid): return self._window_to_id.get(wid) - bandwidth_limit = self.bandwidth_limit - if bandwidth_limit is None: - pinfo = proto.get_info() - socket_speed = pinfo.get("socket", {}).get("speed") - if socket_speed: - #auto: use 80% of socket speed if we have it: - bandwidth_limit = socket_speed*AUTO_BANDWIDTH_PCT//100 or 0 - else: - bandwidth_limit = 0 + bandwidth_limit = self.get_client_bandwidth_limit(proto) ServerSourceClass = self.get_server_source_class() ss = ServerSourceClass(proto, drop_client, self.idle_add, self.timeout_add, self.source_remove, @@ -1238,6 +1231,18 @@ def get_window_id(wid): send_ui = ui_client and not is_request self.idle_add(self.parse_hello_ui, ss, c, auth_caps, send_ui, share_count) + def get_client_bandwidth_limit(self, proto): + if self.bandwidth_limit is None: + #auto-detect: + pinfo = proto.get_info() + socket_speed = pinfo.get("socket", {}).get("speed") + if socket_speed: + #auto: use 80% of socket speed if we have it: + return socket_speed*AUTO_BANDWIDTH_PCT//100 or 0 + else: + return 0 + return self.bandwidth_limit + def get_server_source_class(self): from xpra.server.source import ServerSource @@ -1466,6 +1471,9 @@ def get_server_features(self): f["encoding"] = { "generic" : True, } + f["network"] = { + "bandwidth-limit-change" : True, + } return f def make_hello(self, source): @@ -3308,6 +3316,16 @@ def _process_connection_data(self, proto, packet): if ss: ss.update_connection_data(packet[1]) + def _process_bandwidth_limit(self, proto, packet): + ss = self._server_sources.get(proto) + if not ss: + return + bandwidth_limit = packet[1] + if self.bandwidth_limit>0: + bandwidth_limit = min(self.bandwidth_limit, bandwidth_limit) + ss.bandwidth_limit = bandwidth_limit + netlog.info("bandwidth-limit changed to %sbps for client %i", std_unit(bandwidth_limit), ss.counter) + def _process_input_devices(self, _proto, packet): self.input_devices_format = packet[1]