1 """
2 Convenience routines for performing common operations.
3 @since: 0.28
4 """
5
6
7
8
9 from __future__ import print_function
10
11 import os, sys
12 from zeroinstall import support, SafeException, logger
13 from zeroinstall.support import tasks
14
15 DontUseGUI = object()
16
18 """Run the GUI to choose and download a set of implementations.
19 The user may ask the GUI to submit a bug report about the program. In that case,
20 the GUI may ask us to test it. test_callback is called in that case with the implementations
21 to be tested; the callback will typically call L{zeroinstall.injector.run.test_selections} and return the result of that.
22 @param iface_uri: the required program, or None to show just the preferences dialog
23 @type iface_uri: str
24 @param gui_args: any additional arguments for the GUI itself
25 @type gui_args: [str]
26 @param test_callback: function to use to try running the program
27 @type test_callback: L{zeroinstall.injector.selections.Selections} -> str
28 @param use_gui: if True, raise a SafeException if the GUI is not available. If None, returns DontUseGUI if the GUI cannot be started. If False, returns DontUseGUI always. (since 1.11)
29 @type use_gui: bool | None
30 @return: the selected implementations
31 @rtype: L{zeroinstall.injector.selections.Selections}
32 @since: 0.28"""
33 if use_gui is False:
34 return DontUseGUI
35
36 if not os.environ.get('DISPLAY', None):
37 if use_gui is None:
38 return DontUseGUI
39 else:
40 raise SafeException("Can't use GUI because $DISPLAY is not set")
41
42 from zeroinstall.injector import selections, qdom
43 from io import BytesIO
44
45 from os.path import join, dirname
46 gui_exe = join(dirname(__file__), '0launch-gui', '0launch-gui')
47
48 import socket
49 cli, gui = socket.socketpair()
50
51 try:
52 child = os.fork()
53 if child == 0:
54
55 try:
56 try:
57 cli.close()
58
59 os.dup2(gui.fileno(), 1)
60 os.dup2(gui.fileno(), 0)
61 if use_gui is True:
62 gui_args = ['-g'] + gui_args
63 if iface_uri is not None:
64 gui_args = gui_args + ['--', iface_uri]
65 os.execvp(sys.executable, [sys.executable, gui_exe] + gui_args)
66 except:
67 import traceback
68 traceback.print_exc(file = sys.stderr)
69 finally:
70 sys.stderr.flush()
71 os._exit(1)
72
73 gui.close()
74 gui = None
75
76 while True:
77 logger.info("Waiting for selections from GUI...")
78
79 reply = support.read_bytes(cli.fileno(), len('Length:') + 9, null_ok = True)
80 if reply:
81 if not reply.startswith(b'Length:'):
82 raise Exception("Expected Length:, but got %s" % repr(reply))
83 reply = reply.decode('ascii')
84 xml = support.read_bytes(cli.fileno(), int(reply.split(':', 1)[1], 16))
85
86 dom = qdom.parse(BytesIO(xml))
87 sels = selections.Selections(dom)
88
89 if dom.getAttribute('run-test'):
90 logger.info("Testing program, as requested by GUI...")
91 if test_callback is None:
92 output = b"Can't test: no test_callback was passed to get_selections_gui()\n"
93 else:
94 output = test_callback(sels)
95 logger.info("Sending results to GUI...")
96 output = ('Length:%8x\n' % len(output)).encode('utf-8') + output
97 logger.debug("Sending: %s", repr(output))
98 while output:
99 sent = cli.send(output)
100 output = output[sent:]
101 continue
102 else:
103 sels = None
104
105 pid, status = os.waitpid(child, 0)
106 assert pid == child
107 if status == 1 << 8:
108 logger.info("User cancelled the GUI; aborting")
109 return None
110 elif status == 100 << 8:
111 if use_gui is None:
112 return DontUseGUI
113 else:
114 raise SafeException("No GUI available")
115 if status != 0:
116 raise Exception("Error from GUI: code = %d" % status)
117 break
118 finally:
119 for sock in [cli, gui]:
120 if sock is not None: sock.close()
121
122 return sels
123
153
154 -def exec_man(stores, sels, main = None, fallback_name = None):
155 """Exec the man command to show the man-page for this interface.
156 Never returns.
157 @type stores: L{zeroinstall.zerostore.Stores}
158 @type sels: L{zeroinstall.injector.selections.Selections}
159 @type main: str | None
160 @type fallback_name: str | None
161 @since: 1.12"""
162 interface_uri = sels.interface
163 selected_impl = sels.selections[interface_uri]
164
165 if selected_impl.id.startswith('package'):
166 impl_path = None
167 else:
168 impl_path = selected_impl.get_path(stores)
169
170 if main is None:
171 if sels.commands:
172 selected_command = sels.commands[0]
173 else:
174 print("No <command> in selections!", file=sys.stderr)
175 sys.exit(1)
176 main = selected_command.path
177 if main is None:
178 print("No main program for interface '%s'" % interface_uri, file=sys.stderr)
179 sys.exit(1)
180
181 prog_name = os.path.basename(main)
182
183 if impl_path is None:
184
185 logger.debug("Searching for man-page native command %s (from %s)" % (prog_name, fallback_name))
186 os.execlp('man', 'man', prog_name)
187
188 assert impl_path
189
190 logger.debug("Searching for man-page for %s or %s in %s" % (prog_name, fallback_name, impl_path))
191
192
193
194 for mandir in ['man', 'share/man', 'usr/man', 'usr/share/man']:
195 manpath = os.path.join(impl_path, mandir)
196 if os.path.isdir(manpath):
197
198 os.environ['MANPATH'] = manpath
199 os.execlp('man', 'man', prog_name)
200 sys.exit(1)
201
202
203
204 manpages = []
205 for root, dirs, files in os.walk(impl_path):
206 for f in files:
207 if f.endswith('.gz'):
208 manpage_file = f[:-3]
209 else:
210 manpage_file = f
211 if manpage_file.endswith('.1') or \
212 manpage_file.endswith('.6') or \
213 manpage_file.endswith('.8'):
214 manpage_prog = manpage_file[:-2]
215 if manpage_prog == prog_name or manpage_prog == fallback_name:
216 os.execlp('man', 'man', os.path.join(root, f))
217 sys.exit(1)
218 else:
219 manpages.append((root, f))
220 for d in list(dirs):
221 if d.startswith('.'):
222 dirs.remove(d)
223
224 print("No matching manpage was found for '%s' (%s)" % (fallback_name, interface_uri))
225 if manpages:
226 print("These non-matching man-pages were found, however:")
227 for root, file in manpages:
228 print(os.path.join(root, file))
229 sys.exit(1)
230