Allow command_name to be None
[zeroinstall/zeroinstall-afb.git] / zeroinstall / 0launch-gui / main.py
blob3dab03d4cc7457db336c8a47ca5cd3616a434351
1 # Copyright (C) 2009, Thomas Leonard
2 # See the README file for details, or visit http://0install.net.
4 import os, sys
6 from optparse import OptionParser
8 from zeroinstall.injector import model, arch
9 from zeroinstall.injector.policy import Policy
10 from zeroinstall.injector.iface_cache import iface_cache
11 from zeroinstall.support import tasks
13 _recalculate = tasks.Blocker('recalculate')
15 def recalculate():
16 """Ask the mainloop to recalculate. If we're already recalculating, wait for that to finish
17 and then do it again."""
18 global _recalculate
19 _recalculate.trigger()
20 _recalculate = tasks.Blocker('recalculate')
22 def run_gui(args):
23 parser = OptionParser(usage=_("usage: %prog [options] interface"))
24 parser.add_option("", "--before", help=_("choose a version before this"), metavar='VERSION')
25 parser.add_option("", "--cpu", help=_("target CPU type"), metavar='CPU')
26 parser.add_option("-c", "--cache", help=_("show the cache"), action='store_true')
27 parser.add_option("", "--command", help=_("command to select"), metavar='COMMAND')
28 parser.add_option("-d", "--download-only", help=_("fetch but don't run"), action='store_true')
29 parser.add_option("", "--message", help=_("message to display when interacting with user"))
30 parser.add_option("", "--not-before", help=_("minimum version to choose"), metavar='VERSION')
31 parser.add_option("", "--os", help=_("target operation system type"), metavar='OS')
32 parser.add_option("-r", "--refresh", help=_("check for updates of all interfaces"), action='store_true')
33 parser.add_option("", "--select-only", help=_("only download the feeds"), action='store_true')
34 parser.add_option("-s", "--source", help=_("select source code"), action='store_true')
35 parser.add_option("", "--systray", help=_("download in the background"), action='store_true')
36 parser.add_option("-v", "--verbose", help=_("more verbose output"), action='count')
37 parser.add_option("-V", "--version", help=_("display version information"), action='store_true')
38 parser.add_option("", "--with-store", help=_("add an implementation cache"), action='append', metavar='DIR')
40 parser.disable_interspersed_args()
42 (options, args) = parser.parse_args(args)
44 if options.verbose:
45 import logging
46 logger = logging.getLogger()
47 if options.verbose == 1:
48 logger.setLevel(logging.INFO)
49 else:
50 logger.setLevel(logging.DEBUG)
52 if options.cache:
53 # Must fork before importing gtk, or ATK dies
54 if os.fork():
55 # We exit, so our parent can call waitpid and unblock.
56 sys.exit(0)
57 # The grandchild continues...
59 if options.with_store:
60 from zeroinstall import zerostore
61 for x in options.with_store:
62 iface_cache.stores.stores.append(zerostore.Store(os.path.abspath(x)))
64 import gui
66 if options.version:
67 print "0launch-gui (zero-install) " + gui.version
68 print "Copyright (C) 2010 Thomas Leonard"
69 print _("This program comes with ABSOLUTELY NO WARRANTY,"
70 "\nto the extent permitted by law."
71 "\nYou may redistribute copies of this program"
72 "\nunder the terms of the GNU Lesser General Public License."
73 "\nFor more information about these matters, see the file named COPYING.")
74 sys.exit(0)
76 import gtk
77 if gtk.gdk.get_display() is None:
78 print >>sys.stderr, "Failed to connect to display. Aborting."
79 sys.exit(1)
81 if not hasattr(gtk, 'combo_box_new_text'):
82 import combo_compat
84 if options.cache:
85 import cache
86 cache_explorer = cache.CacheExplorer()
87 cache_explorer.show()
88 cache_explorer.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
89 gtk.gdk.flush()
90 cache_explorer.populate_model()
91 cache_explorer.window.set_cursor(None)
92 gtk.main()
93 sys.exit(0)
95 handler = gui.GUIHandler()
97 if len(args) < 1:
98 import preferences
99 # Once we separate configuration from Policy, this hack can go away
100 class DummyPolicy(Policy):
101 def recalculate(fetch_stale_interfaces = True):
102 pass
103 def solve_with_downloads(force = False):
104 pass
105 box = preferences.show_preferences(DummyPolicy('http://localhost/dummy', handler))
106 box.connect('destroy', gtk.main_quit)
107 gtk.main()
108 sys.exit(0)
110 interface_uri = args[0]
112 if len(args) > 1:
113 parser.print_help()
114 sys.exit(1)
116 import mainwindow, dialog
118 restrictions = []
119 if options.before or options.not_before:
120 restrictions.append(model.VersionRangeRestriction(model.parse_version(options.before),
121 model.parse_version(options.not_before)))
123 widgets = dialog.Template('main')
125 policy = Policy(interface_uri, handler, src = bool(options.source), command = options.command)
126 policy.target_arch = arch.get_architecture(options.os, options.cpu)
127 root_iface = iface_cache.get_interface(interface_uri)
128 policy.solver.extra_restrictions[root_iface] = restrictions
129 policy.solver.record_details = True
131 window = mainwindow.MainWindow(policy, widgets, download_only = bool(options.download_only), select_only = bool(options.select_only))
132 handler.mainwindow = window
134 if options.message:
135 window.set_message(options.message)
137 root = iface_cache.get_interface(policy.root)
138 window.browser.set_root(root)
140 window.window.connect('destroy', lambda w: handler.abort_all_downloads())
142 if options.systray:
143 window.use_systray_icon()
145 @tasks.async
146 def main():
147 force_refresh = bool(options.refresh)
148 while True:
149 window.refresh_button.set_sensitive(False)
150 window.browser.set_update_icons(force_refresh)
152 solved = policy.solve_with_downloads(force = force_refresh, update_local = True)
154 if not window.systray_icon:
155 window.show()
156 yield solved
157 try:
158 window.refresh_button.set_sensitive(True)
159 tasks.check(solved)
160 except Exception, ex:
161 window.report_exception(ex)
163 if window.systray_icon and window.systray_icon.get_visible() and \
164 window.systray_icon.is_embedded():
165 if policy.ready:
166 window.systray_icon.set_tooltip(_('Downloading updates for %s') % root_iface.get_name())
167 window.run_button.set_active(True)
168 else:
169 # Should already be reporting an error, but
170 # blink it again just in case
171 window.systray_icon.set_blinking(True)
173 refresh_clicked = dialog.ButtonClickedBlocker(window.refresh_button)
174 yield refresh_clicked, _recalculate
176 if refresh_clicked.happened:
177 force_refresh = True
179 handler.wait_for_blocker(main())