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# This file is part of Xpra. 

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

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

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

5 

6from xpra.util import envbool, csv, typedict 

7from xpra.net.file_transfer import FileTransferHandler 

8from xpra.client.mixins.stub_client_mixin import StubClientMixin 

9from xpra.log import Logger 

10 

11printlog = Logger("printing") 

12filelog = Logger("file") 

13 

14DELETE_PRINTER_FILE = envbool("XPRA_DELETE_PRINTER_FILE", True) 

15SKIP_STOPPED_PRINTERS = envbool("XPRA_SKIP_STOPPED_PRINTERS", True) 

16 

17 

18class FilePrintMixin(StubClientMixin, FileTransferHandler): 

19 

20 def __init__(self): 

21 StubClientMixin.__init__(self) 

22 FileTransferHandler.__init__(self) 

23 self.printer_attributes = [] 

24 self.send_printers_timer = 0 

25 self.exported_printers = None 

26 self.remote_request_file = False 

27 

28 def init(self, opts): 

29 #printing and file transfer: 

30 FileTransferHandler.init_opts(self, opts) 

31 

32 def init_authenticated_packet_handlers(self): 

33 for packet_type, handler in { 

34 "open-url" : self._process_open_url, 

35 "send-file" : self._process_send_file, 

36 "send-data-request" : self._process_send_data_request, 

37 "send-data-response": self._process_send_data_response, 

38 "ack-file-chunk" : self._process_ack_file_chunk, 

39 "send-file-chunk" : self._process_send_file_chunk, 

40 }.items(): 

41 self.add_packet_handler(packet_type, handler, False) 

42 

43 def get_caps(self) -> dict: 

44 return self.get_file_transfer_features() 

45 

46 def cleanup(self): 

47 #we must clean printing before FileTransferHandler, which turns the printing flag off! 

48 self.cleanup_printing() 

49 FileTransferHandler.cleanup(self) 

50 

51 def parse_server_capabilities(self, caps : typedict) -> bool: 

52 self.parse_printing_capabilities(caps) 

53 self.parse_file_transfer_caps(caps) 

54 self.remote_request_file = caps.boolget("request-file", False) 

55 return True 

56 

57 def parse_printing_capabilities(self, caps : typedict): 

58 printlog("parse_printing_capabilities() client printing support=%s", self.printing) 

59 if self.printing: 

60 server_printing = caps.boolget("printing") 

61 printlog("parse_printing_capabilities() server printing support=%s", server_printing) 

62 if server_printing: 

63 self.printer_attributes = caps.strtupleget("printer.attributes", 

64 ("printer-info", "device-uri")) 

65 self.timeout_add(1000, self.init_printing) 

66 

67 

68 def init_printing(self): 

69 try: 

70 from xpra.platform.printing import init_printing 

71 printlog("init_printing=%s", init_printing) 

72 init_printing(self.send_printers) 

73 except Exception as e: 

74 printlog.error("Error initializing printing support:") 

75 printlog.error(" %s", e) 

76 self.printing = False 

77 else: 

78 try: 

79 self.do_send_printers() 

80 except Exception: 

81 printlog.error("Error sending the list of printers:", exc_info=True) 

82 self.printing = False 

83 printlog("init_printing() enabled=%s", self.printing) 

84 

85 def cleanup_printing(self): 

86 printlog("cleanup_printing() printing=%s", self.printing) 

87 if not self.printing: 

88 return 

89 self.cancel_send_printers_timer() 

90 try: 

91 from xpra.platform.printing import cleanup_printing 

92 printlog("cleanup_printing=%s", cleanup_printing) 

93 cleanup_printing() 

94 except ImportError as e: 

95 printlog("cleanup_printing()", exc_info=True) 

96 except Exception as e: 

97 printlog("cleanup_printing()", exc_info=True) 

98 printlog.warn("Warning: failed to cleanup printing subsystem:") 

99 printlog.warn(" %s", e) 

100 

101 def send_printers(self, *args): 

102 printlog("send_printers%s timer=%s", args, self.send_printers_timer) 

103 #dbus can fire dozens of times for a single printer change 

104 #so we wait a bit and fire via a timer to try to batch things together: 

105 if self.send_printers_timer: 

106 return 

107 self.send_printers_timer = self.timeout_add(500, self.do_send_printers) 

108 

109 def cancel_send_printers_timer(self): 

110 spt = self.send_printers_timer 

111 printlog("cancel_send_printers_timer() send_printers_timer=%s", spt) 

112 if spt: 

113 self.send_printers_timer = None 

114 self.source_remove(spt) 

115 

116 def do_send_printers(self): 

117 try: 

118 self.send_printers_timer = None 

119 from xpra.platform.printing import get_printers, get_mimetypes 

120 try: 

121 printers = get_printers() 

122 except Exception as e: 

123 printlog("%s", get_printers, exc_info=True) 

124 printlog.error("Error: cannot access the list of printers") 

125 printlog.error(" %s", e) 

126 return 

127 printlog("do_send_printers() found printers=%s", printers) 

128 #remove xpra-forwarded printers to avoid loops and multi-forwards, 

129 #also ignore stopped printers 

130 #and only keep the attributes that the server cares about (self.printer_attributes) 

131 exported_printers = {} 

132 def used_attrs(d): 

133 #filter attributes so that we only compare things that are actually used 

134 if not d: 

135 return d 

136 return dict((k,v) for k,v in d.items() if k in self.printer_attributes) 

137 for k,v in printers.items(): 

138 device_uri = v.get("device-uri", "") 

139 if device_uri: 

140 #this is cups specific.. oh well 

141 printlog("do_send_printers() device-uri(%s)=%s", k, device_uri) 

142 if device_uri.startswith("xpraforwarder"): 

143 printlog("do_send_printers() skipping xpra forwarded printer=%s", k) 

144 continue 

145 state = v.get("printer-state") 

146 #"3" if the destination is idle, 

147 #"4" if the destination is printing a job, 

148 #"5" if the destination is stopped. 

149 if state==5 and SKIP_STOPPED_PRINTERS: 

150 printlog("do_send_printers() skipping stopped printer=%s", k) 

151 continue 

152 attrs = used_attrs(v) 

153 #add mimetypes: 

154 attrs["mimetypes"] = get_mimetypes() 

155 exported_printers[k.encode("utf8")] = attrs 

156 if self.exported_printers is None: 

157 #not been sent yet, ensure we can use the dict below: 

158 self.exported_printers = {} 

159 elif exported_printers==self.exported_printers: 

160 printlog("do_send_printers() exported printers unchanged: %s", self.exported_printers) 

161 return 

162 #show summary of what has changed: 

163 added = tuple(k for k in exported_printers if k not in self.exported_printers) 

164 if added: 

165 printlog("do_send_printers() new printers: %s", added) 

166 removed = tuple(k for k in self.exported_printers if k not in exported_printers) 

167 if removed: 

168 printlog("do_send_printers() printers removed: %s", removed) 

169 modified = tuple(k for k,v in exported_printers.items() if 

170 self.exported_printers.get(k)!=v and k not in added) 

171 if modified: 

172 printlog("do_send_printers() printers modified: %s", modified) 

173 printlog("do_send_printers() printers=%s", exported_printers.keys()) 

174 printlog("do_send_printers() exported printers=%s", csv(str(x) for x in exported_printers)) 

175 self.exported_printers = exported_printers 

176 self.send("printers", self.exported_printers) 

177 except Exception: 

178 printlog.error("do_send_printers()", exc_info=True)