Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# -*- coding: utf-8 -*- 

2# This file is part of Xpra. 

3# Copyright (C) 2010-2020 Antoine Martin <antoine@xpra.org> 

4# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com> 

5# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 

6# later version. See the file COPYING for details. 

7 

8from collections import deque 

9 

10from xpra.server.source.stub_source_mixin import StubSourceMixin 

11from xpra.platform.features import CLIPBOARDS 

12from xpra.util import envint, typedict 

13from xpra.os_util import monotonic_time 

14from xpra.log import Logger 

15 

16log = Logger("clipboard") 

17 

18MAX_CLIPBOARD_LIMIT = envint("XPRA_CLIPBOARD_LIMIT", 30) 

19MAX_CLIPBOARD_LIMIT_DURATION = envint("XPRA_CLIPBOARD_LIMIT_DURATION", 3) 

20 

21 

22class ClipboardConnection(StubSourceMixin): 

23 

24 @classmethod 

25 def is_needed(cls, caps : typedict) -> bool: 

26 return caps.boolget("clipboard") 

27 

28 

29 def init_state(self): 

30 self.clipboard_enabled = False 

31 self.clipboard_notifications = False 

32 self.clipboard_notifications_current = 0 

33 self.clipboard_notifications_pending = 0 

34 self.clipboard_progress_timer = None 

35 self.clipboard_stats = deque(maxlen=MAX_CLIPBOARD_LIMIT*MAX_CLIPBOARD_LIMIT_DURATION) 

36 self.clipboard_greedy = False 

37 self.clipboard_want_targets = False 

38 self.clipboard_client_selections = CLIPBOARDS 

39 self.clipboard_preferred_targets = () 

40 self.clipboard_contents_slice_fix = False 

41 

42 def cleanup(self): 

43 self.cancel_clipboard_progress_timer() 

44 

45 def parse_client_caps(self, c : typedict): 

46 self.clipboard_enabled = c.boolget("clipboard", False) 

47 self.clipboard_notifications = c.boolget("clipboard.notifications") 

48 log("client clipboard: enabled=%s, notifications=%s", 

49 self.clipboard_enabled, self.clipboard_notifications) 

50 self.clipboard_greedy = c.boolget("clipboard.greedy") 

51 self.clipboard_want_targets = c.boolget("clipboard.want_targets") 

52 self.clipboard_client_selections = c.strtupleget("clipboard.selections", CLIPBOARDS) 

53 self.clipboard_contents_slice_fix = c.boolget("clipboard.contents-slice-fix") 

54 self.clipboard_preferred_targets = c.strtupleget("clipboard.preferred-targets", ()) 

55 log("client clipboard: greedy=%s, want_targets=%s, client_selections=%s, contents_slice_fix=%s", 

56 self.clipboard_greedy, self.clipboard_want_targets, 

57 self.clipboard_client_selections, self.clipboard_contents_slice_fix) 

58 if self.clipboard_enabled and not self.clipboard_contents_slice_fix: 

59 log.info("client clipboard does not include contents slice fix") 

60 

61 def get_info(self) -> dict: 

62 return { 

63 "clipboard" : { 

64 "enabled" : self.clipboard_enabled, 

65 "notifications" : self.clipboard_notifications, 

66 "greedy" : self.clipboard_greedy, 

67 "want-targets" : self.clipboard_want_targets, 

68 "preferred-targets" : self.clipboard_preferred_targets, 

69 "selections" : self.clipboard_client_selections, 

70 "contents-slice-fix" : self.clipboard_contents_slice_fix, 

71 }, 

72 } 

73 

74 

75 def send_clipboard_enabled(self, reason=""): 

76 if not self.hello_sent: 

77 return 

78 self.send_async("set-clipboard-enabled", self.clipboard_enabled, reason) 

79 

80 def cancel_clipboard_progress_timer(self): 

81 cpt = self.clipboard_progress_timer 

82 if cpt: 

83 self.clipboard_progress_timer = None 

84 self.source_remove(cpt) 

85 

86 def send_clipboard_progress(self, count : int): 

87 if not self.clipboard_notifications or not self.hello_sent or self.clipboard_progress_timer: 

88 return 

89 #always set "pending" to the latest value: 

90 self.clipboard_notifications_pending = count 

91 #but send the latest value via a timer to tame toggle storms: 

92 def may_send_progress_update(): 

93 self.clipboard_progress_timer = None 

94 if self.clipboard_notifications_current!=self.clipboard_notifications_pending: 

95 self.clipboard_notifications_current = self.clipboard_notifications_pending 

96 log("sending clipboard-pending-requests=%s to %s", self.clipboard_notifications_current, self) 

97 self.send_more("clipboard-pending-requests", self.clipboard_notifications_current) 

98 delay = (count==0)*100 

99 self.clipboard_progress_timer = self.timeout_add(delay, may_send_progress_update) 

100 

101 def send_clipboard(self, packet): 

102 if not self.clipboard_enabled or not self.hello_sent: 

103 return 

104 if getattr(self, "suspended", False): 

105 return 

106 now = monotonic_time() 

107 self.clipboard_stats.append(now) 

108 if len(self.clipboard_stats)>=MAX_CLIPBOARD_LIMIT: 

109 event = self.clipboard_stats[-MAX_CLIPBOARD_LIMIT] 

110 elapsed = now-event 

111 log("send_clipboard(..) elapsed=%.2f, clipboard_stats=%s", elapsed, self.clipboard_stats) 

112 if elapsed<1: 

113 msg = "more than %s clipboard requests per second!" % MAX_CLIPBOARD_LIMIT 

114 log.warn("Warning: %s", msg) 

115 #disable if this rate is sustained for more than S seconds: 

116 events = [x for x in self.clipboard_stats if x>(now-MAX_CLIPBOARD_LIMIT_DURATION)] 

117 log("%i events in the last %i seconds: %s", len(events), MAX_CLIPBOARD_LIMIT_DURATION, events) 

118 if len(events)>=MAX_CLIPBOARD_LIMIT*MAX_CLIPBOARD_LIMIT_DURATION: 

119 log.warn(" limit sustained for more than %i seconds,", MAX_CLIPBOARD_LIMIT_DURATION) 

120 return 

121 #call compress_clibboard via the encode work queue: 

122 self.queue_encode((True, self.compress_clipboard, packet)) 

123 

124 def compress_clipboard(self, packet): 

125 from xpra.net.compression import Compressible, compressed_wrapper 

126 #Note: this runs in the 'encode' thread! 

127 packet = list(packet) 

128 for i, item in enumerate(packet): 

129 if isinstance(item, Compressible): 

130 if self.brotli: 

131 packet[i] = compressed_wrapper(item.datatype, item.data, 

132 level=9, brotli=True, can_inline=False) 

133 else: 

134 packet[i] = self.compressed_wrapper(item.datatype, item.data) 

135 self.queue_packet(packet)