+branch/ubiquity

« back to all changes in this revision

Viewing changes to wizard.py

* Reorganise oem-config to have a reasonable frontend/backend separation,
  thereby allowing for a future KDE port. This involved updating much of
  the core to look more like ubiquity (since ubiquity's core was an
  evolution of oem-config's), and a new UI using a single window/notebook
  rather than a succession of dialogs.
* Casualties of this work include the whole base-config-like menu
  structure and the include/exclude mechanism. The menu structure made it
  difficult to avoid spawning a new dialog for each step, which was a poor
  UI; I don't believe the include/exclude mechanism was heavily used, but
  contact me if you were relying on it and it may be possible to restore
  it in a new form.
* I haven't yet ported the new i18n infrastructure from ubiquity, so the
  oem-config UI will be untranslated.
* Like ubiquity, we now incorporate source for all d-i components we use
  under d-i/source/ and include them directly in our binary package,
  reducing the number of complex interdependencies with d-i packages.
  'debian/rules update' can be used to do automatic updates of these
  copied source packages.
* Remove some leftover code that dealt with restoring the inittab, as we
  don't do the temporary inittab thing any more.
* Fix kbd-chooser wrapper script to actually install the selected keymap
  (though only for the console at present).

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#! /usr/bin/env python
2
2
# -*- coding: UTF-8 -*-
3
3
 
4
 
# Copyright (C) 2005 Canonical Ltd.
 
4
# Copyright (C) 2005, 2006 Canonical Ltd.
5
5
# Written by Colin Watson <cjwatson@ubuntu.com>.
6
6
#
7
7
# This program is free software; you can redistribute it and/or modify
19
19
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20
20
 
21
21
import sys
22
 
import os
23
 
import re
24
22
import optparse
25
 
import pygtk
26
 
pygtk.require('2.0')
27
 
import gtk
28
 
import gtk.glade
29
 
import debconf
30
 
from debconffilter import DebconfFilter
31
 
from debconfcommunicator import DebconfCommunicator
32
 
 
33
 
menudir = '/usr/lib/oem-config/menu'
34
 
 
35
 
menu_line_re = re.compile(r'(.*?): (.*)')
36
23
 
37
24
class Wizard:
38
 
    def __init__(self, includes=None, excludes=None):
39
 
        if 'OEM_CONFIG_DEBUG' in os.environ:
40
 
            self.debug_enabled = True
41
 
        else:
42
 
            self.debug_enabled = False
43
 
 
44
 
        self.menus = {}
45
 
        for menu in [f for f in os.listdir(menudir) if f.endswith('.mnu')]:
46
 
            name = '.'.join(menu.split('.')[:-1])
47
 
 
48
 
            # Always include the exit item. Otherwise, check includes and
49
 
            # excludes.
50
 
            if name != 'exit':
51
 
                if includes is not None and not name in includes:
52
 
                    continue
53
 
                if excludes is not None and name in excludes:
54
 
                    continue
55
 
 
56
 
            menudata = {}
57
 
            menufile = open(os.path.join(menudir, menu))
58
 
            for line in menufile:
59
 
                match = menu_line_re.match(line)
60
 
                if match is not None:
61
 
                    menudata[match.group(1).lower()] = match.group(2)
62
 
 
63
 
            # Load any templates that come with this item.
64
 
            templates = os.path.join(menudir, '%s.templates' % name)
65
 
            if os.path.exists(templates):
66
 
                if self.load_template(templates) != 0:
67
 
                    continue
68
 
 
69
 
            if 'extra-templates' in menudata:
70
 
                extras = menudata['extra-templates']
71
 
                for extra in extras.split(' '):
72
 
                    if not extra.startswith('/'):
73
 
                        extra = os.path.join(menudir, extra)
74
 
                    if self.load_template(extra) != 0:
75
 
                        continue
76
 
 
77
 
            # If there is a test script, check that it succeeds.
78
 
            testscript = os.path.join(menudir, '%s.tst' % name)
79
 
            if os.access(testscript, os.X_OK):
80
 
                if os.spawnl(os.P_WAIT, testscript, testscript) != 0:
81
 
                    continue
82
 
 
83
 
            self.menus[name] = menudata
84
 
 
85
 
            # If there is an Asks: field, match it against the list of
86
 
            # question names in the debconf database.
87
 
            if 'asks' in self.menus[name]:
88
 
                asks_re = self.menus[name]['asks']
89
 
                asks = []
90
 
 
91
 
                # It isn't possible to use debconf-copydb after the debconf
92
 
                # frontend has started up, so we have to use
93
 
                # DebconfCommunicator to talk to a separate
94
 
                # debconf-communicate process rather than starting a proper
95
 
                # frontend.
96
 
                #
97
 
                # The best fix for this mess is to make debconf-copydb treat
98
 
                # its source database as read-only. Unfortunately, layering
99
 
                # issues inside debconf make this difficult for the time
100
 
                # being.
101
 
 
102
 
                # TODO: os.popen() doesn't take a list, so we have to
103
 
                # quote metacharacters by hand. Once we're entirely
104
 
                # comfortable with relying on Python 2.4, we can use
105
 
                # subprocess.call() instead.
106
 
                asks_re = re.sub(r'\W', r'\\\g<0>', asks_re)
107
 
                for line in os.popen(
108
 
                        'debconf-copydb configdb pipe' +
109
 
                        ' --config=Name:pipe --config=Driver:Pipe' +
110
 
                        ' --config=InFd:none --pattern=%s' % asks_re):
111
 
                    line = line.rstrip('\n')
112
 
                    if line.startswith('Name: '):
113
 
                        asks.append(line[6:])
114
 
                self.menus[name]['asks-questions'] = asks
115
 
 
116
 
        db = DebconfCommunicator('oem-config')
117
 
 
118
 
        for name in self.menus:
119
 
            self.menus[name]['description'] = \
120
 
                db.metaget('oem-config/menu/%s' % name, 'description')
121
 
 
122
 
        self.glades = {}
123
 
        for glade in [f for f in os.listdir(menudir) if f.endswith('.glade')]:
124
 
            name = '.'.join(glade.split('.')[:-1])
125
 
            self.glades[name] = os.path.join(menudir, glade)
126
 
 
127
 
        self.steps = {}
128
 
        for step in [f for f in os.listdir(menudir) if f.endswith('.py')]:
129
 
            name = '.'.join(step.split('.')[:-1])
130
 
            mod = getattr(__import__('menu.%s' % name), name)
131
 
            if hasattr(mod, 'stepname'):
132
 
                stepmethod = getattr(mod, mod.stepname)
133
 
                self.steps[name] = stepmethod(self.glades[name])
134
 
 
135
 
        self.widgets = {}
136
 
        for name in self.menus:
137
 
            if name in self.steps:
138
 
                self.widgets[self.menus[name]['asks']] = self.steps[name]
139
 
 
140
 
        db.shutdown()
141
 
 
142
 
        # Get the list of supported locales.
143
 
        self.supported_locales = {}
144
 
        for line in open('/usr/share/i18n/SUPPORTED'):
145
 
            (locale, charset) = line.split(None, 1)
146
 
            self.supported_locales[locale] = charset
147
 
 
148
 
    def debug(self, message):
149
 
        if self.debug_enabled:
150
 
            print >>sys.stderr, message
151
 
 
152
 
    def load_template(self, template):
153
 
        return os.spawnlp(os.P_WAIT, 'debconf-loadtemplate',
154
 
                          'debconf-loadtemplate', 'oem-config', template)
155
 
 
156
 
    # Get a list of the menu items, sorted by their Order: fields.
157
 
    def get_menu_items(self):
158
 
        def menu_sort(x, y):
159
 
            return cmp(int(self.menus[x]['order']),
160
 
                       int(self.menus[y]['order']))
161
 
 
162
 
        items = self.menus.keys()
163
 
        items.sort(menu_sort)
164
 
        return items
 
25
    def __init__(self, frontend_name=None):
 
26
        if frontend_name is None:
 
27
            frontend_names = ['gtk-ui']
 
28
        else:
 
29
            frontend_names = [frontend_name]
 
30
        mod = __import__('frontend', globals(), locals(), frontend_names)
 
31
        for f in frontend_names:
 
32
            if hasattr(mod, f):
 
33
                ui = getattr(mod, f)
 
34
                break
 
35
        else:
 
36
            raise AttributeError, ('No frontend available; tried %s' %
 
37
                                   ', '.join(frontend_names))
 
38
        self.frontend = ui.Frontend()
165
39
 
166
40
    def run(self):
167
 
        # Get initial language.
168
 
        db = DebconfCommunicator('oem-config')
169
 
        language = db.get('debian-installer/locale')
170
 
        if language not in self.supported_locales:
171
 
            language = db.get('debian-installer/fallbacklocale')
172
 
        if language != '':
173
 
            self.debug("oem-config: LANG=%s" % language)
174
 
            os.environ['LANG'] = language
175
 
            os.environ['LANGUAGE'] = language
176
 
        else:
177
 
            # LANGUAGE just confuses matters, so unset it.
178
 
            if 'LANGUAGE' in os.environ:
179
 
                del os.environ['LANGUAGE']
180
 
        language_changed = False
181
 
        db.shutdown()
182
 
 
183
 
        items = self.get_menu_items()
184
 
        index = 0
185
 
        while index >= 0 and index < len(items):
186
 
            item = items[index]
187
 
            self.debug("oem-config: Running menu item %s" % item)
188
 
 
189
 
            if language != '':
190
 
                if language != os.environ['LANG']:
191
 
                    self.debug("oem-config: LANG=%s" % language)
192
 
                    os.environ['LANG'] = language
193
 
                    os.environ['LANGUAGE'] = language
194
 
                    language_changed = True
195
 
            else:
196
 
                # LANGUAGE just confuses matters, so unset it.
197
 
                if 'LANGUAGE' in os.environ:
198
 
                    del os.environ['LANGUAGE']
199
 
 
200
 
            db = DebconfCommunicator('oem-config')
201
 
            debconffilter = DebconfFilter(db, self.widgets)
202
 
 
203
 
            if language_changed:
204
 
                # The language has just been changed, so we must be about to
205
 
                # re-run localechooser. Stop localechooser from thinking
206
 
                # that the change of language (which will be an incomplete
207
 
                # locale) indicates preseeding.
208
 
                db.set('debian-installer/locale', '')
209
 
                language_changed = False
210
 
 
211
 
            # Hack to allow a menu item to repeat on backup as long as the
212
 
            # value of any one of a named set of questions has changed. This
213
 
            # allows the locale question to back up when the language
214
 
            # changes and start a new debconf frontend, while still backing
215
 
            # up normally if the user cancels.
216
 
            if 'repeat-if-changed' in self.menus[item]:
217
 
                oldrepeat = {}
218
 
                for name in self.menus[item]['repeat-if-changed'].split():
219
 
                    oldrepeat[name] = db.get(name)
220
 
 
221
 
            # Set as unseen all questions that we're going to ask.
222
 
            if 'asks-questions' in self.menus[item]:
223
 
                for name in self.menus[item]['asks-questions']:
224
 
                    db.fset(name, 'seen', 'false')
225
 
 
226
 
            if item in self.steps:
227
 
                self.steps[item].prepare(db)
228
 
 
229
 
            # Run the menu item through a debconf filter, which may display
230
 
            # custom widgets as required.
231
 
            itempath = os.path.join(menudir, item)
232
 
            ret = debconffilter.run(itempath)
233
 
 
234
 
            language = db.get('debian-installer/locale')
235
 
            if language not in self.supported_locales:
236
 
                language = db.get('debian-installer/fallbacklocale')
237
 
 
238
 
            if (ret / 256) == 10:
239
 
                if 'repeat-if-changed' in self.menus[item]:
240
 
                    for name in self.menus[item]['repeat-if-changed'].split():
241
 
                        if oldrepeat[name] != db.get(name):
242
 
                            break
243
 
                    else:
244
 
                        index -= 1
245
 
                else:
246
 
                    index -= 1
247
 
                db.shutdown()
248
 
                continue
249
 
            elif ret != 0:
250
 
                # TODO: We should pop up a more visible error message here,
251
 
                # but it's too late to add UI for that. For now, we continue
252
 
                # because oem-config really wants to finish so that you get
253
 
                # a non-system user created.
254
 
                print >>sys.stderr, "Menu item %s exited %d" % (item, ret)
255
 
 
256
 
            db.shutdown()
257
 
 
258
 
            # Did this menu item finish the configuration process?
259
 
            if ('exit-menu' in self.menus[item] and
260
 
                self.menus[item]['exit-menu'] == 'true'):
261
 
                break
262
 
 
263
 
            index += 1
264
 
 
265
 
        if index >= 0:
266
 
            return 0
267
 
        else:
268
 
            return 10
 
41
        return self.frontend.run()
269
42
 
270
43
if __name__ == '__main__':
271
44
    parser = optparse.OptionParser()
272
 
    parser.add_option('-i', '--include', action='append', metavar='ITEM',
273
 
                      help="Display this menu item.")
274
 
    parser.add_option('-e', '--exclude', action='append', metavar='ITEM',
275
 
                      help="Don't display this menu item.")
 
45
    parser.add_option('-f', '--frontend', metavar='FRONTEND',
 
46
                      help="Use the given frontend (gtk-ui).")
 
47
    parser.set_defaults(frontend=None)
276
48
    (options, args) = parser.parse_args()
277
49
 
278
 
    wizard = Wizard(includes=options.include, excludes=options.exclude)
 
50
    wizard = Wizard(frontend_name=options.frontend)
279
51
    sys.exit(wizard.run())