#!/usr/bin/env python # -*- coding: utf-8 -*- '''GUI (PyQt4) Installer for Scratchbox1 based Maemo SDK''' # This file is part of Maemo SDK # # Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). # # Contact: juha-pekka.jokela@tieto.com # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 2 of the License, or (at your option) any later # version. This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. You should have received a copy of the GNU General Public License # along with this program. If not, see . import os import re import sys import pwd import grp import time import socket import urllib import tempfile import random import signal import traceback import subprocess import platform from optparse import OptionParser def Which(program): '''Returns path to given binary, if it can be found.''' def is_exe(fpath): return os.path.exists(fpath) and os.access(fpath, os.X_OK) fpath, fname = os.path.split(program) if fpath: if is_exe(program): return program else: for path in os.environ["PATH"].split(os.pathsep): exe_file = os.path.join(path, program) if is_exe(exe_file): return exe_file return None def install_package(package, askuser = True): '''Tries to install specified debian package (or defined rpm equivalent), returns True on success''' # Will ask user for confirmation, depending on "askuser" value rpm_pkg_name = {'wget': 'wget', 'python-qt4': 'PyQt4', 'xserver-xephyr': 'xorg-x11-server-Xephyr'} if askuser: reply = raw_input('Do you want to install missing package "%s"? (Type "y" to install) ' % package) else: reply = 'y' if reply.lower() in ['y', 'yes']: if Which('apt-get'): command = ['apt-get', 'install', package] elif Which('yum'): if package in rpm_pkg_name: command = ['yum', 'install', rpm_pkg_name[package]] else: print 'No rpm equivalent defined for debian package "%s", not installing' % packacge return False else: print 'No compatible package manager found (only apt-get and yum are supported)' return False else: return False print 'Executing command:', command p = subprocess.Popen(command) p.wait() if p.returncode: print 'Automatic installation of package "%s" failed.' % command[2] return False return True askedinstall = False while (True): try: from PyQt4 import QtGui from PyQt4 import QtCore from PyQt4 import QtNetwork except ImportError, e: # Can raise AttributeError if the version of PyQt4 is prior to Qt 4.3, # since according to # http://doc.trolltech.com/4.4/porting4-overview.html#wizard-dialogs-qwizard # QWizard and siblings were added to that version of Qt4. Systems that have # too old Qt and bindings by default: Debian Etch. print "Python Qt4 bindings are not found!" if not askedinstall: askedinstall = True if install_package('python-qt4'): print "Successfully installed package python-qt4" else: print "Failed to install Python Qt4 bindings" sys.exit(1) else: break try: from PyQt4 import QtWebKit except ImportError, e: # Only WebKit is missing, if it was available, it would have been installed # with Qt Python bindings package. Run installation without WebKit functionality. HAVE_WEBKIT = False else: HAVE_WEBKIT = True if not Which('wget'): print "wget not found!" if not install_package('wget'): sys.exit(1) # command line options and arguments OPTIONS = None ARGS = None OPT_PARSER = None # logger, initialized in main LOG = None XEPHYR_SHORTCUT_FILENAME = "start_xephyr.sh" # names shown to the user (what is installed/removed and installer's name) PRODUCT_NAME = 'Maemo %s SDK' % ("5.0") PRODUCT_NAME_SHORT = 'SDK' SB_NAME = 'Scratchbox' MY_NAME = '%s Installer' % PRODUCT_NAME MY_VERSION = "0.1.1 ($Revision: 1078 $)" # SDK time & space consumption: these are very rough INST_CONS_TIME = '20 minutes' INST_CONS_SPACE = "3GB" # where scratchbox is found SB_PATH = "/scratchbox" # scratchbox group SB_GROUP = "sbox" # window size WINDOW_WIDTH = 720 WINDOW_HEIGHT = 540 USE_IMAGES = True MAEMO_LINKS_FILENAME = "Maemo 5 links.html" XEPHYR_DESKTOP_ENTRY_FILENAME = "xephyr.desktop" # Base URL's INSTALLER_BASE_URL = "http://repository.maemo.org/stable/fremantle-update2" EULA_BASE_URL = "http://tablets-dev.nokia.com/eula-update2" # URL's of installers (can be set to local file) SB_INSTALLER_URL = "%s/maemo-scratchbox-install_5.0.sh" % INSTALLER_BASE_URL SDK_INSTALLER_URL = "%s/maemo-sdk-install_5.0.sh" % INSTALLER_BASE_URL EULA_URL = "%s/index.php" % EULA_BASE_URL TOKEN_URL = "%s/token.php" % EULA_BASE_URL ALLOW_UPGRADE = False IMAGES = ['http://tablets-dev.nokia.com/sdk_installer/applications.png', 'http://tablets-dev.nokia.com/sdk_installer/desktop.png', 'http://tablets-dev.nokia.com/sdk_installer/TaskSwitcher.png'] INSTALL_OPTIONS = [ ["&Minimal rootstraps only", "none"], ["&Runtime environment", "maemo-sdk-runtime"], ["&Development environment", "maemo-sdk-dev"], ["Debu&g environment", "maemo-sdk-debug"]] DEFAULT_INSTALL_OPTION = 2 # checked by default BINPATHS = ["/usr/local/bin", "usr/bin"] # default target names for this release of SDK, user can choose different # prefix if default targets already exist in scratchbox TARGET_PREFIX = "FREMANTLE" TARGET_POSTFIX_X86 = "_X86" TARGET_POSTFIX_ARMEL = "_ARMEL" TARGET_X86 = TARGET_PREFIX + TARGET_POSTFIX_X86 TARGET_ARMEL = TARGET_PREFIX + TARGET_POSTFIX_ARMEL # license text for the SDK (non-ASCII quotes replaced with ASCII) SDK_LICENSE = \ '''1) IMPORTANT: READ CAREFULLY BEFORE INSTALLING, DOWNLOADING, OR USING THE SOFTWARE DEVELOPMENT KIT ("SDK" AS DEFINED BELOW) AND/OR SOFTWARE INCLUDED INTO THE SDK 2) The SDK comprises of a) some software copyrighted by Nokia Corporation or third parties in binary form (collectively "Licensed Software") and/or b) Open Source Software in binary and source code form. 3) Licensed Software (including, without limitation, the downloading, installation and/or the use thereof) is subject to, and licensed to you under, the Nokia Software Development Kit Agreement, which you have to accept if you choose to download the Licensed Software. Licensed Software is distributed to you only in binary form. 4) The SDK is provided to you "AS IS" and Nokia, its affiliates and/or its licensors do not make any representations or warranties, express or implied, including, without any limitation, the warranties of merchantability or fittness for a particular purpose, or that the SDK will not infringe any any third party patents, copyrights, trademarks or other rights, or that the SDK will meet your requirements or that the operation of the SDK will be uninterrupted and/or error-free. By downloading and/or using the SDK you accept that installation, any use and the results of the installation and/or happens and is solely at your own risk and that Nokia assumes no liability whatsoever for any damages that you may incur or suffer in connection with the SDK and/or the installation or use thereof. 5) The Open Source Software is licensed and distributed under the GNU General Public License (GPL), the GNU lesser General Public License (LGPL, aka. The GNU Library General Public License) and/or other copyright licenses, permissions, notices or disclaimers containing obligation or permission to provide the source code of such software with the binary / executable form delivery of the said software. Any source code of such software that is not part of this delivery is made available to you in accordance with the referred license terms and conditions on http://www.maemo.org. Alternatively, Nokia offers to provide any such source code to you on CD-ROM for a charge covering the cost of performing such distribution, such as the cost of media, shipping and handling, upon written request to Nokia at: Source Code Requests Nokia Corporation P.O.Box 407 FIN00045 Nokia Group Finland. This offer is valid for a period of three (3) years. The exact license terms of GPL, LGPL and said certain other licenses, as well as the required copyright and other notices, permissions and acknowledgements are reproduced in and delivered to you as part of the referred source code. ''' class ImageHandler(object): '''Class for handling images.''' def __init__(self, imagelist): '''Constructor''' self.imagedata = None self.imagelist = imagelist def __del__(self): '''Destructor''' def downloadImage(self, i): '''Download specified image, currently blocks untill finished''' proxy = get_proxy('http') try: handle = urllib.urlopen(self.imagelist[i], proxies = proxy) self.imagedata = handle.read() except Exception, e: self.imagedata = None return False else: return True def getImage(self): '''Return image pixmap, if already loaded''' if self.imagedata: pixmap = QtGui.QPixmap() pixmap.loadFromData(self.imagedata) return pixmap else: return None def get_all_usernames(): '''Returns non-system usernames if can get the system limits for normal user's UID. If not then returns all of the usernames in the system. The usernames are returned in a list.''' user_conf_file = "/etc/adduser.conf" # inclusive range of UIDs for normal (non-system) users # by default this is the maximum possible range, that includes all users first_uid = 0 last_uid = sys.maxint found_first_uid = False found_last_uid = False # try to get the UID range if os.path.isfile(user_conf_file): for l in open(user_conf_file): l = l.strip() if l.startswith("FIRST_UID"): first_uid = int(l.split("=")[1]) found_first_uid = True if l.startswith("LAST_UID"): last_uid = int(l.split("=")[1]) found_last_uid = True # both found if found_first_uid and found_last_uid: break usernames = [] for i in pwd.getpwall(): # if UID not within the range, then skip this user if not first_uid <= i.pw_uid <= last_uid: continue usernames.append(i.pw_name) usernames.sort() return usernames def get_default_username(): '''Returns username of the user that invoked sudo or su to run this script. Returns None if couldn't get such entry.''' pwd_ent = None # try to get the user that ran sudo username = os.getenv('SUDO_USER') # or user that ran su if not username: username = os.getenv('USERNAME') if username: # installing SDK as root is not allowed, also the list of users does # not show system users if username == 'root': LOG("Omitting default user 'root'") return None # verify that this user actually exists try: pwd_ent = pwd.getpwnam(username) except KeyError: LOG("User %s not in password file!" % username) return None return username def get_scratchbox_users(): #Returns list of scratchbox users userlist = [] userdir = '%s/users' % SB_PATH #If user directory doesn't exist, there can be no users. if os.path.isdir(userdir): p = subprocess.Popen('ls %s' % userdir, stdout = subprocess.PIPE, shell = True) userlist = p.communicate()[0].split("\n") #Remove invalid entries, if any for user in userlist: if not os.path.isdir("%s/%s" % (userdir, user)): userlist.remove(user) #Remove last empty line userlist.pop() return userlist def uninstall_scratchbox(has_64bit): '''Tries to uninstall scratchbox from the system.''' #Uninstallation needs to be performed differently on 64bit if has_64bit: packages_64bit = ['scratchbox-devkit-doctools', 'scratchbox-devkit-perl', 'scratchbox-devkit-debian', 'scratchbox-devkit-svn', 'scratchbox-devkit-git', 'scratchbox-devkit-apt-https', 'scratchbox-devkit-qemu', 'scratchbox-core'] for package in packages_64bit: exec_cmd('dpkg --purge %s' % package) else: #Easier on 32bit exec_cmd('apt-get remove -y --purge scratchbox-libs') def get_user_targets(user): '''Return list of targets for specified user''' targets = [] preexec = lambda: set_guid(pwd.getpwnam(user), True) p = subprocess.Popen('%s/tools/bin/sb-conf list --targets' % SB_PATH, stdout = subprocess.PIPE, preexec_fn = preexec, shell = True) targets = p.communicate()[0].split("\n") #Remove lines which do not point to valid directory (including errors / warnings) for target in targets: if not os.path.isdir("%s/users/%s/targets/%s" % (SB_PATH, user, target)): targets.remove(target) #Remove last empty line, as it's not removed by previous test targets.pop() return targets def has_user_active_sessions(user): '''Returns True if user has active scratchbox sessions''' lines = 0 if os.path.isfile("%s/tools/bin/sb-conf" % SB_PATH): p = subprocess.Popen('su %s -c "%s/tools/bin/sb-conf list --sessions"' % (user, SB_PATH), stdout = subprocess.PIPE, shell = True) sessions = p.communicate()[0].split("\n") for session in sessions: if re.match("^/dev/pts/[0-9]+:\s+[0-9]+", session): lines += 1 return lines > 1 def has_user_group(user, group): '''Returns True if user already is part of specified group''' '''group is checked from /etc/group instead of "groups" command''' p = subprocess.Popen('grep %s /etc/group' % group, stdout = subprocess.PIPE, shell = True) output = p.communicate()[0] groupusers = output[output.rfind(':') + 1:len(output) - 1].split(',') for groupuser in groupusers: if groupuser == user: return True return False def add_user_to_group(user, group): '''Adds specified user to specified group''' p = subprocess.Popen('usermod -a -G %s %s' % (group, user), shell = True) p.wait() def remove_scratchbox_target(user, target): '''Removes specified target from specified user, if it exists''' #Doesn't try to remove targets, for which directory is not found, so #error messages, and empty target strings will be ignored. if target and os.path.isdir("%s/users/%s/targets/%s" % (SB_PATH, user, target)): LOG("Removing target %s" % (target)) exec_cmd('%s/tools/bin/sb-conf remove -f %s' % (SB_PATH, target), username = user, set_sbox_gid = True) def get_user_home_dir(user): '''returns home directory for the specified user, or None''' homedir = os.path.expanduser('~%s' % (user)) if os.path.isdir(homedir): return homedir else: return None def get_user_desktop_dir(user): '''returns desktop directory for the specified user, or None''' desktopdir = None homedir = get_user_home_dir(user) if homedir: xdg_fn = "%s/.config/user-dirs.dirs" % (homedir) if os.path.isfile(xdg_fn): xdg_file = open(xdg_fn, "r") xdg_text = xdg_file.read().splitlines() xdg_file.close() for line in xdg_text: line = line.strip() if not line.startswith("#"): index = line.find("XDG_DESKTOP_DIR") if index is not -1: splitline = line[index:].split("=") desktopdir = splitline[1].strip("\" ") desktopdir = desktopdir.replace("$HOME", homedir) else: if os.path.isdir("%s/Desktop" % (homedir)): desktopdir = "%s/Desktop" % (homedir) if desktopdir: if os.path.isdir(desktopdir): return desktopdir else: if homedir: return homedir return None def parse_options(): '''Parses the command line options''' global OPTIONS global ARGS global OPT_PARSER usage = 'Usage: %prog [options]' + \ ''' %s. Installs %s.''' % (MY_NAME, PRODUCT_NAME) OPT_PARSER = OptionParser(usage = usage, version = "%prog " + MY_VERSION) OPT_PARSER.add_option("-a", dest="sourceslistfile", help="Specify alternative sources.list file for both targets.", metavar="FILE") (OPTIONS, ARGS) = OPT_PARSER.parse_args() def set_proxy(proxy): '''Sets http_proxy to specified''' global PROXY PROXY = dict([('http', proxy)]) def get_proxy(protocol): '''returns proxy for specified protocol. On some systems PROXY['http'] can be None, and it can cause problems''' global PROXY proxy = None if PROXY: if protocol in PROXY: if PROXY[protocol]: proxy = PROXY return proxy def download_file(url, chown_username = None, set_exec_bit = True): '''Downloads a file into temporary location. Returns that location. url = URL to download from chown_username = if set, the ownership of the downloaded file will be given to this user set_exec_bit = if True execute mode bit of the downloaded file will be set''' LOG("Downloading %s" % url) filename = None try: split = os.path.splitext(url) suf = split[len(split)-1] proxy = get_proxy('http') fd = tempfile.mkstemp(suffix = suf) filehandle = os.fdopen(fd[0], "w") filename = fd[1] LOG("Filename:%s" % filename) urlhandle = urllib.urlopen(url, proxies = proxy) filehandle.write(urlhandle.read()) urlhandle.close() filehandle.close() except Exception, e: LOG("Error downloading file %s: %s" % (url, e)) raise e else: LOG("Saved download into %s" % filename) if set_exec_bit: os.chmod(filename, 0700) if chown_username: pwd_ent = pwd.getpwnam(chown_username) os.chown(filename, pwd_ent.pw_uid, pwd_ent.pw_gid) return filename def bool_to_yesno(bool_value): '''Return yes if Boolean value is true, no if it is false''' if bool_value: return 'Yes' else: return 'No' def file_append_lines(filename, lines): '''Appends list of lines into a file. Newlines are added to each appended line. filename = name of the file lines = list of lines''' if not os.path.isfile(filename): LOG("WARNING! Appending to non-existing file (%s), will be created!" % (filename)) LOG("Appending into file %s lines %s" % (filename, lines)) fo = open(filename, "a") try: for line in lines: fo.write(line + '\n') finally: fo.close() def set_guid(pwd_ent, set_sbox_gid = False): '''Changes the effective, real and saved set-user-ID user and group IDs, should be used to permanently drop root privileges. Also sets HOME environment variable since maemo-sdk command extensively uses that variable. pwd_ent = Password database entry of the user whose credentials will be used set_sbox_gid = The GID will be set to that of the sbox. This is required to run the SDK installer and any other scratchbox related command. sg cannot be used since it does not return the exit status of the executed process. Another option is newgrp command.''' if set_sbox_gid: gid = grp.getgrnam(SB_GROUP).gr_gid else: gid = pwd_ent.pw_gid os.setgid(gid) os.setuid(pwd_ent.pw_uid) os.environ['HOME'] = pwd_ent.pw_dir os.environ['USER'] = pwd_ent.pw_name # SDK installer needs this class CmdExecError(Exception): '''Exception raised when execution of a command fails.''' pass def LoadUrl(url): '''Loads specified URL in browser. Tries to figure out prefered one.''' #If we just let Qt open browser, it will run as root, and we don't want that. browsers = ['x-www-browser', 'firefox', 'iceweasel', 'opera', 'konqueror'] exe = Which('xdg-open') if exe: command = exe + ' ' + str(url) user = get_default_username() exec_cmd(command, username = user, donotwait = True) return #xdg isn't installed as default on some distros, so we'll check if user has one #of listed browsers, and use that. for browser in browsers: exe = Which(browser) if exe: command = exe + ' ' + str(url) user = get_default_username() exec_cmd(command, username = user, donotwait = True) return def exec_cmd(command, username = None, set_sbox_gid = False, send_text = None, donotwait = False): '''Executes a command and raises exception if command fails. username = if set, will run command with the credentials (UID & GID) of that user. set_sbox_gid = sets the GID of executed command to sbox, so that scratchbox commands can be executed. This is naturally only used when the username is specified, since root is not allowed to run scratchbox commands. send_text = Text to send to stdin of the process before waiting for it''' pwd_ent = None if username: LOG("Executing as user %s: %s" % (username, command)) if set_sbox_gid: #Check if user is already part of sbox group (in which case we don't have to use the newgrp hack) p = subprocess.Popen('groups %s|grep %s' % (username, SB_GROUP), stdout = subprocess.PIPE, shell = True) output = p.communicate()[0] if not output: #Use newgrp as user isn't part of sbox group yet #First newgrp sets sbox as users default group, second one restores the original one. LOG("User not part of %s group, using newgrp" % SB_GROUP) command = "newgrp %s << 'END1'\nnewgrp << 'END2'\n%s\nEND2\nEND1\n" % (SB_GROUP, command) #Execute as user with su command = "su %s -c \"%s\"" % (username, command) LOG("Final command:%s" % command) if send_text: p_stdin = subprocess.PIPE else: p_stdin = None # as default in Popen p = subprocess.Popen(command, stdin = p_stdin, stdout = LOG.fo_log, stderr = subprocess.STDOUT, shell = True) if send_text: p.communicate(send_text) if donotwait: return else: p.wait() if p.returncode: raise CmdExecError("Giving up, because failed to: %s" % command) def scratchbox_target_exists(username, target): '''Returns True if specified target for specified username exists in scratchbox.''' return os.path.isdir("%s/users/%s/targets/%s" % (SB_PATH, username, target)) def scratchbox_prefix_exist(username, prefix): '''Returns True if either armel or x86 target with specified prefix exists in scratchbox. The SDK installer creates targets by taking prefix and appending architecture.''' return scratchbox_target_exists(username, prefix + TARGET_POSTFIX_X86) or \ scratchbox_target_exists(username, prefix + TARGET_POSTFIX_ARMEL) class Logger(object): '''Class for logging.''' def __init__(self): '''Constructor''' script_name = os.path.basename(sys.argv[0]) # file name of the log file: script name, with extenstion replaced self.fn_log = "/tmp/%s.log" % script_name[:script_name.rfind('.')] self.fo_log = None try: self.fo_log = open(self.fn_log, 'w') except: print ("Could not open log file %s!" % (self.fn_log,)) raise self.log("Python version: %s" % repr(sys.version)) self.log("Installer version: %s" % MY_VERSION) def __del__(self): '''Destructor''' if self.fo_log: self.fo_log.close() def log(self, msg): """Writes a log message.""" self.fo_log.write("V [%s]: %s\n" % (time.strftime("%H:%M:%S %d.%m.%Y"), msg)) self.fo_log.flush() def __call__(self, *args, **kwds): '''Shortcut for log''' self.log(*args, **kwds) def log_exc(self): '''Writes current exception information into the log file''' fmt = '-' * 5 + " %s " + '-' * 5 self.log(fmt % "Begin logging exception") traceback.print_exc(file = self.fo_log) self.log(fmt % "End logging exception") class HostInfo(object): '''Information about host''' def __init__(self): '''Constructor''' # whether host already has scratchbox installed self.__has_scratchbox = os.path.isfile("%s/etc/scratchbox-version" %(SB_PATH)) self.__scratchbox_op_name = self.__get_scratchbox_op_name() machines_64bit = ["x86_64"] machine = os.uname()[4] if machine in machines_64bit: self.__has_64bit = True else: self.__has_64bit = False # whether current VDSO settings are unsupported self.__has_unsupported_vdso = self.__check_vdso_unsupported(self.__has_64bit) self.__has_xephyr = os.path.exists("/usr/bin/Xephyr") if Which('apt-get'): self.__has_apt_get = True else: self.__has_apt_get = False if Which('yum'): self.__has_yum = True else: self.__has_yum = False LOG("Got host info: %s" % self) @property def has_scratchbox(self): return self.__has_scratchbox @property def has_xephyr(self): return self.__has_xephyr @property def scratchbox_op_name(self): return self.__scratchbox_op_name @property def has_unsupported_vdso(self): return self.__has_unsupported_vdso @property def has_64bit(self): return self.__has_64bit @property def has_apt_get(self): return self.__has_apt_get @property def has_yum(self): return self.__has_yum def __check_vdso_unsupported(self, __has_64bit): '''Returns True if current VDSO setting is not supported by scratchbox''' if __has_64bit: return True vdso_str = subprocess.Popen(["sysctl", "-n", "vm.vdso_enabled"], stdout = subprocess.PIPE ).communicate()[0].strip() LOG("Found current VDSO value: %s" % vdso_str) supported_vdso_list = [0, 2] return not int(vdso_str) in supported_vdso_list def __get_scratchbox_op_name(self): '''Returns string of what operation is needed to get scratchbox to this host: install or upgrade''' if self.has_scratchbox: return "Upgrade" else: return "Install" def __str__(self): '''String representation method.''' return str(self.__dict__) # field names for wizard and its siblings FNLicenseAccept = "license_accept" # bool FNInstallSDK = "install_sdk" # bool FNUpgradeSDK = "upgrade_sdk" # bool FNInstallSB = "install_sb" # bool FNUninstall = "uninstall" # bool FNInstallNokiaApps = "install_nokia_apps" # bool FNSelectedUsername = "selected_username" # string FNTargetX86Exist = "target_x86_exist" # bool FNTargetArmelExist = "target_armel_exist" # bool FNRemoveTargets = "targets_remove" # bool FNTargetPrefix = "targets_prefix" # string FNSDKInstMOptArg = "sdk_m_opt_arg" # string FNSDKInstMOptArgText = "sdk_m_opt_arg_txt" # string FNVDSOSetPerm = "vdso_set_permanently" # bool FNInstallNokiaBins = "nokiabins_install" # bool FNNokiaBinsRepo = "nokiabins_repo" # string FNAcceptVDSO64bit = "vdso64bit_accept" # bool FNInstallXephyr = "install_xephyr" # bool FNInstallXephyrShortcut = "install_xephyr_shortcut" # bool FNInstallDesktopLinks = "install_desktop_links" # bool FNEasyInstall = "easy_install" # bool FNCreateSbHomeShortcut = "create_sb_home_shortcut" # bool class ProxyPage(QtGui.QWizardPage): '''Proxy settings page''' def __init__(self): '''Constructor''' QtGui.QWizardPage.__init__(self) self.layout = QtGui.QVBoxLayout() self.setLayout(self.layout) def initializePage(self): self.setCommitPage(True) self.setTitle('Proxy Settings') self.setSubTitle(" ") proxy_host_port = QtGui.QWidget() self.proxy_host = QtGui.QLineEdit() txt = "Enter hostname for your proxy" self.proxy_host.setToolTip(txt) self.proxy_host.setWhatsThis(txt) self.proxy_host_label = QtGui.QLabel("Proxy host:") self.proxy_port = QtGui.QLineEdit() txt = "Enter port number for your proxy" self.proxy_port.setToolTip(txt) self.proxy_port.setWhatsThis(txt) intvalidator = QtGui.QIntValidator(1, 65535, self) self.proxy_port.setValidator(intvalidator) self.proxy_port_label = QtGui.QLabel("Proxy port:") self.proxy_username = QtGui.QLineEdit() txt = "Enter username for your proxy" self.proxy_username.setToolTip(txt) self.proxy_username.setWhatsThis(txt) self.proxy_username_label = QtGui.QLabel("Proxy username:") self.proxy_password = QtGui.QLineEdit() txt = "Enter password for your proxy" self.proxy_password.setToolTip(txt) self.proxy_password.setWhatsThis(txt) self.proxy_password.setEchoMode(QtGui.QLineEdit.Password) self.proxy_password_label = QtGui.QLabel("Proxy password:") self.login_enable = QtGui.QCheckBox("Show login") self.proxy_button = QtGui.QPushButton("&Confirm") self.connect(self.proxy_button, QtCore.SIGNAL("clicked()"), self.confirmClicked) self.connect(self.proxy_button, QtCore.SIGNAL("clicked()"), self.confirmClicked) self.connect(self.login_enable, QtCore.SIGNAL("toggled(bool)"), self.loginEnableToggled) proxy_host_port_layout = QtGui.QGridLayout() proxy_host_port_layout.addWidget(self.proxy_host_label, 0, 0) proxy_host_port_layout.addWidget(self.proxy_host, 0, 1) proxy_host_port_layout.addWidget(self.proxy_port_label, 1, 0) proxy_host_port_layout.addWidget(self.proxy_port, 1, 1) proxy_host_port_layout.addWidget(self.proxy_username_label, 2, 0) proxy_host_port_layout.addWidget(self.proxy_username, 2, 1) proxy_host_port_layout.addWidget(self.proxy_password_label, 3, 0) proxy_host_port_layout.addWidget(self.proxy_password, 3, 1) proxy_host_port.setLayout(proxy_host_port_layout) self.loginEnableToggled(False) proxy_settings = QtGui.QWidget() proxy_settings_layout = QtGui.QVBoxLayout() proxy_settings_layout.addWidget(self.login_enable) proxy_settings_layout.addWidget(proxy_host_port) proxy_settings_layout.addWidget(self.proxy_button) proxy_settings.setLayout(proxy_settings_layout) self.layout.addWidget(proxy_settings) self.notetext = QtGui.QLabel("Failed to connect, please check your proxy settings.") self.notetext.setWordWrap(True) self.notetext.setVisible(False) self.layout.addWidget(self.notetext) def loginEnableToggled(self, checked): self.proxy_username_label.setVisible(checked) self.proxy_username.setVisible(checked) self.proxy_password_label.setVisible(checked) self.proxy_password.setVisible(checked) def confirmClicked(self): host_str = str(self.proxy_host.text()) port_str = str(self.proxy_port.text()) if self.login_enable.isChecked(): user_str = str(self.proxy_username.text()) pswd_str = str(self.proxy_password.text()) if user_str and pswd_str: loginstring = "%s:%s@" %(user_str, pswd_str) else: loginstring = "" else: loginstring = "" if port_str != '' and host_str != '': if host_str.startswith('http://'): #Remove http:// from beginning, we will add it with login info host_str = host_str[7:] proxystring = 'http://%s%s:%s' % (loginstring, host_str, port_str) os.environ['http_proxy'] = proxystring set_proxy(proxystring) else: set_proxy(None) os.environ['http_proxy'] = ('') if self.wizard().imageHandler.downloadImage(random.randint(0, len(IMAGES)-1)): self.wizard().next() self.emit(QtCore.SIGNAL("completeChanged()")) else: self.notetext.setVisible(True) def isComplete(self): '''Installer will automatically move to next page, when connection works.''' return False class IntroPage(QtGui.QWizardPage): '''Introduction page''' def __init__(self): '''Constructor''' QtGui.QWizardPage.__init__(self) self.setTitle('%s Installation Wizard' % PRODUCT_NAME) self.setSubTitle(" ") intro_label = QtGui.QLabel("This installer will guide you through the steps " "needed to install %s on your development " "machine. The installation will take approximately %s " "(depending on download speed) and about %s of disk space on system root. \n\n" "It will install Scratchbox cross compilation environment togther with Maemo 5 " "development files on your host system." % (PRODUCT_NAME, INST_CONS_TIME, INST_CONS_SPACE)) intro_label.setWordWrap(True) self.layout = QtGui.QVBoxLayout() self.layout.addWidget(intro_label) note_label = QtGui.QLabel("\nNOTE:\n" "You are installing version 1.2009.44-1 of Maemo 5.0 SDK. " "It is required to be able to compile software for devices, " "which haven't been updated with newer firmware upgrade. " "Software compiled with this version should work on newer " "firmware, but the SDK might be lacking some features.") note_label.setWordWrap(True) self.layout.addWidget(note_label) def initializePage(self): if USE_IMAGES: pixmap = self.wizard().imageHandler.getImage() if pixmap: label = QtGui.QLabel() label.setPixmap(pixmap) label.setAlignment(QtCore.Qt.AlignCenter) self.layout.addWidget(label) self.setLayout(self.layout) class LevelPage(QtGui.QWizardPage): '''Introduction page - select standard or custom installation''' def __init__(self, host_has_scratchbox, has_apt): '''Constructor''' QtGui.QWizardPage.__init__(self) self.setTitle('Install Mode') self.setSubTitle(" ") intro_label = QtGui.QLabel("Select install mode") intro_label.setWordWrap(True) self.button_group = QtGui.QButtonGroup() self.btn_easy = QtGui.QRadioButton("&Standard installation") self.btn_easy.setChecked(True) txt = "Most options well be left for defaults, so there are less question " \ "asked during installation." self.btn_easy.setToolTip(txt) self.btn_easy.setWhatsThis(txt) self.button_group.addButton(self.btn_easy, 0) self.btn_custom = QtGui.QRadioButton("&Custom installation") txt = "Allows you to select which parts to install." self.btn_custom.setToolTip(txt) self.btn_custom.setWhatsThis(txt) self.button_group.addButton(self.btn_custom, 1) self.btn_uninstall = QtGui.QRadioButton("&Uninstall") txt = "Existing Scratchbox installation will be removed from the system." self.btn_uninstall.setToolTip(txt) self.btn_uninstall.setWhatsThis(txt) self.button_group.addButton(self.btn_uninstall, 2) self.layout = QtGui.QVBoxLayout() self.layout.addWidget(intro_label) self.layout.addWidget(self.btn_easy) self.layout.addWidget(self.btn_custom) if host_has_scratchbox and has_apt: #uninstall currently supported only when installed from debian packages. Not offered otherwise. self.layout.addWidget(self.btn_uninstall) self.connect(self.btn_uninstall, QtCore.SIGNAL("toggled(bool)"), self.uninstallToggled) self.warning = QtGui.QLabel("Warning: %s and %s will be uninstalled " "from the system. If there are any changes " "in your %s home directory, it will be " "preserved along with the desktop link if " "there is one. Before going forward, make sure " "you don't have any active logins." % (SB_NAME, PRODUCT_NAME, SB_NAME)) self.warning.setWordWrap(True) self.warning.setVisible(False) self.layout.addWidget(self.warning) self.registerField(FNEasyInstall, self.btn_easy) self.registerField(FNUninstall, self.btn_uninstall) self.setLayout(self.layout) def uninstallToggled(self, on): self.warning.setVisible(on) def nextId(self): '''Determines which page should be shown next''' if self.btn_uninstall.isChecked(): return PageIdSummary return PageIdLicense class LicensePage(QtGui.QWizardPage): '''EULA page''' def __init__(self, has_64bit): '''Constructor''' QtGui.QWizardPage.__init__(self) self.setTitle('Open Source License Agreement') self.setSubTitle(" ") self.has_64bit = has_64bit license_text_edit = QtGui.QTextEdit() license_text_edit.setPlainText(SDK_LICENSE) license_text_edit.setReadOnly(True) txt = "%s License. Use Ctrl+Wheel to zoom the text." % \ (PRODUCT_NAME_SHORT) license_text_edit.setToolTip(txt) license_text_edit.setWhatsThis(txt) accept_check_box = QtGui.QCheckBox("I &accept") accept_check_box.setCheckState(QtCore.Qt.Unchecked) self.registerField(FNLicenseAccept + '*', accept_check_box) layout = QtGui.QVBoxLayout() layout.addWidget(license_text_edit) layout.addWidget(accept_check_box) self.setLayout(layout) def nextId(self): '''Determines which page should be shown next''' if self.field(FNEasyInstall).toBool(): if self.has_64bit: return PageIdVDSO else: return PageIdNokiaBins else: #Custom install if self.has_64bit: return PageId64Bit return PageIdUsers class X86_64Page(QtGui.QWizardPage): '''X86_64 page''' def __init__(self): '''Constructor''' QtGui.QWizardPage.__init__(self) self.setTitle('X86_64 platform detected') self.setSubTitle(" ") self.install_check_box = QtGui.QCheckBox("&Install anyway") self.install_check_box.setCheckState(QtCore.Qt.Unchecked) self.connect(self.install_check_box, QtCore.SIGNAL("toggled(bool)"), self, QtCore.SIGNAL("completeChanged()")) layout = QtGui.QVBoxLayout() label = QtGui.QLabel("Using SDK on X86_64 is not supported, but you can install it anyway.") layout.addWidget(label) layout.addWidget(self.install_check_box) self.setLayout(layout) self.emit(QtCore.SIGNAL("completeChanged()")) def isComplete(self): '''Overrides the method of QWizardPage, to disable next button if nothing is selected for installation.''' return self.install_check_box.isChecked() class InstallOptsPage(QtGui.QWizardPage): '''Installation options page''' def __init__(self, host_has_scratchbox, scratchbox_op_name, has_xephyr, has_supported_package_manager, show_vdso_page): '''Constructor''' QtGui.QWizardPage.__init__(self) self.setTitle('Install Options') self.setSubTitle(" ") self.has_supported_package_manager = has_supported_package_manager self.show_vdso_page = show_vdso_page self.has_scratchbox = host_has_scratchbox self.install_sb_checkbox = QtGui.QCheckBox( scratchbox_op_name + " &" + SB_NAME) self.install_sb_checkbox.setChecked(True) if host_has_scratchbox: info_str = ("A previous %s installation has been detected " "in %s. You have an option to upgrade that " "installation." % (SB_NAME, SB_PATH)) else: info_str = ("Previous installation of %(sb_name)s was not " "detected. This installer can only detect previous " "installation of %(sb_name)s in %(sb_path)s. If you " "have previously installed %(sb_name)s in some other " "path, new version of %(sb_name)s will be installed " "in %(sb_path)s by this installer." % { "sb_name" : SB_NAME, "sb_path" : SB_PATH}) info_label = QtGui.QLabel(info_str) info_label.setWordWrap(True) self.install_sdk_checkbox = QtGui.QCheckBox( "Install &%s" % PRODUCT_NAME) self.install_sdk_checkbox.setChecked(True) self.upgrade_sdk_checkbox = QtGui.QCheckBox( "Upgrade &%s" % PRODUCT_NAME) self.upgrade_sdk_checkbox.setChecked(False) self.upgrade_sdk_checkbox.hide() self.registerField(FNInstallSB, self.install_sb_checkbox) self.registerField(FNInstallSDK, self.install_sdk_checkbox) self.registerField(FNUpgradeSDK, self.upgrade_sdk_checkbox) self.connect(self.install_sb_checkbox, QtCore.SIGNAL("toggled(bool)"), self, QtCore.SIGNAL("completeChanged()")) self.connect(self.install_sdk_checkbox, QtCore.SIGNAL("toggled(bool)"), self.sdkInstallCheckboxToggled) self.layout = QtGui.QVBoxLayout() self.layout.addWidget(info_label) self.layout.addWidget(self.install_sb_checkbox) self.layout.addWidget(self.install_sdk_checkbox) self.xephyr_install_checkbox = QtGui.QCheckBox("Install Xephyr") self.xephyr_desktop_checkbox = QtGui.QCheckBox("Add Xephyr desktop shortcut") self.xephyr_desktop_checkbox.setChecked(True) if not has_xephyr: if self.has_supported_package_manager: self.xephyr_install_checkbox.setChecked(True) self.layout.addWidget(self.xephyr_install_checkbox) else: print "TODO: Add error label or something" else: self.xephyr_install_checkbox.setChecked(False) self.layout.addWidget(self.xephyr_desktop_checkbox) self.sb_home_shortcut_checkbox = QtGui.QCheckBox("Add shortcut to Scratchbox home directory") self.sb_home_shortcut_checkbox.setChecked(True) self.layout.addWidget(self.sb_home_shortcut_checkbox) self.desktop_links_checkbox = QtGui.QCheckBox("Add Maemo related links to Desktop") self.desktop_links_checkbox.setChecked(True) self.layout.addWidget(self.desktop_links_checkbox) self.registerField(FNCreateSbHomeShortcut, self.sb_home_shortcut_checkbox) self.registerField(FNInstallXephyr, self.xephyr_install_checkbox) self.registerField(FNInstallXephyrShortcut, self.xephyr_desktop_checkbox) self.registerField(FNInstallDesktopLinks, self.desktop_links_checkbox) self.connect(self.xephyr_install_checkbox, QtCore.SIGNAL("toggled(bool)"), self.installXephyrCheckboxToggled) self.connect(self.xephyr_desktop_checkbox, QtCore.SIGNAL("toggled(bool)"), self, QtCore.SIGNAL("completeChanged()")) self.connect(self.sb_home_shortcut_checkbox, QtCore.SIGNAL("toggled(bool)"), self, QtCore.SIGNAL("completeChanged()")) self.connect(self.desktop_links_checkbox, QtCore.SIGNAL("toggled(bool)"), self, QtCore.SIGNAL("completeChanged()")) self.setLayout(self.layout) def installXephyrCheckboxToggled(self, on): if on: self.xephyr_desktop_checkbox.setDisabled(False) else: self.xephyr_desktop_checkbox.setChecked(False) self.xephyr_desktop_checkbox.setDisabled(True) self.emit(QtCore.SIGNAL("completeChanged()")) def initializePage(self): '''Create list of detected upgradeable SDK targets''' targets = [] if ALLOW_UPGRADE and self.has_scratchbox: self.sdk_upgrade = QtGui.QWidget() self.sdk_upgrade_layout = QtGui.QVBoxLayout(self.sdk_upgrade) self.sdk_upgrade.setLayout(self.sdk_upgrade_layout) self.sdk_upgrade.setDisabled(True) self.do_sdk_upgrade = False user = self.field(FNSelectedUsername).toString() targets = self.getTargets(user) if targets != []: info_str = ("Following scratchbox targets were detected. " "Check those you want to upgrade. You cannot perform upgrade if doing SDK installation.") info_label = QtGui.QLabel(info_str, self.sdk_upgrade) info_label.setWordWrap(True) self.sdk_upgrade_layout.addWidget(info_label) for target in targets: checkbox = QtGui.QCheckBox(target, self.sdk_upgrade) self.sdk_upgrade_layout.addWidget(checkbox) if not self.isTargetUpgradeable(user, target): checkbox.setDisabled(True) else: self.connect(checkbox, QtCore.SIGNAL("toggled(bool)"), self, QtCore.SIGNAL("completeChanged()")) else: info_str = ("No upgradeable scratchbox targets detected") info_label = QtGui.QLabel(info_str, self.sdk_upgrade) info_label.setWordWrap(True) self.sdk_upgrade_layout.addWidget(info_label) self.layout.addWidget(self.sdk_upgrade) def cleanupPage(self): '''Remove current checkboxes if user presses back. We recreate new list with new user selected on previous page''' if ALLOW_UPGRADE and self.has_scratchbox: self.sdk_upgrade.deleteLater() def sdkInstallCheckboxToggled(self, on): if ALLOW_UPGRADE: self.sdk_upgrade.setDisabled(on) self.do_sdk_upgrade = not on self.emit(QtCore.SIGNAL("completeChanged()")) def isTargetUpgradeable(self, user, target): isupgradeable = False sources = '%s/users/' % SB_PATH + user + '/targets/' + target + '/etc/apt/sources.list' try: sourcesfile = file(sources,'r') except: return False lines = sourcesfile.readlines() sourcesfile.close() expression = re.compile(r" fremantle/") for line in lines: if expression.search(line): if not isupgradeable: newsourcesfile = file('/tmp/maemo-sdk-install-wizard_' + target + '_sources.list', 'wt') newsourcesfile.write('# Automatically created by Nokia fremantle SDK installer\n') isupgradeable = True newsourcesfile.write(line) if isupgradeable: newsourcesfile.write('\n') newsourcesfile.close() return isupgradeable def getTargets(self, user): directory='%s/users/' % SB_PATH + user +'/targets' dirs = [] #list of directories if os.path.exists(directory): """Returns a list of directories.""" #list of directories and files listing = os.listdir(directory) #get just the directories for direntry in listing: if os.path.isdir(directory+os.sep+direntry) and direntry != 'links': dirs.append(direntry) return dirs def validatePage(self): self.do_sdk_upgrade = False if ALLOW_UPGRADE: user = self.field(FNSelectedUsername).toString() skipitems = 2 upgradescript = "" for child in self.sdk_upgrade.children(): if skipitems: skipitems -= 1 else: if child.isChecked(): #Target selected for upgrade, append it to our upgrade script text = child.text() self.do_sdk_upgrade = True upgradescript += '%s/tools/bin/sb-conf select %s\n' % (SB_PATH, text) upgradescript += '%s/login fakeroot apt-get -o Dir::Etc::sourcelist="/tmp/maemo-sdk-install-wizard_%s_sources.list" update && ' \ '%s/login fakeroot apt-get -o Dir::Etc::sourcelist="/tmp/maemo-sdk-install-wizard_%s_sources.list -o Acquire::Retries=5" ' \ '--force-yes -y dist-upgrade\n' % (SB_PATH, text, SB_PATH, text) upgradescript += '%s/login fakeroot apt-get update\n\n' % (SB_PATH) if self.do_sdk_upgrade: upgradescriptfile = file('/tmp/maemo-sdk-install-wizard_upgrade.sh', 'wt') upgradescriptfile.write('#!/bin/sh\n') upgradescriptfile.write('# Automatically generated shell script to upgrade Nokia Maemo SDK targets\n') upgradescriptfile.write(upgradescript) upgradescriptfile.close() os.chmod('/tmp/maemo-sdk-install-wizard_upgrade.sh', 0555) self.upgrade_sdk_checkbox.setChecked(self.do_sdk_upgrade) return True def isComplete(self): '''Overrides the method of QWizardPage, to disable next button if nothing is selected for installation.''' if not self.has_scratchbox and not self.install_sb_checkbox.isChecked(): return False if self.xephyr_install_checkbox.isChecked() or \ self.xephyr_desktop_checkbox.isChecked() or \ self.install_sb_checkbox.isChecked() or \ self.install_sdk_checkbox.isChecked() or \ self.sb_home_shortcut_checkbox.isChecked() or \ self.desktop_links_checkbox.isChecked(): return True if self.has_scratchbox and ALLOW_UPGRADE: #SDK upgrade selected skipitems = 2 for child in self.sdk_upgrade.children(): # Skip non-checkbox items if skipitems: skipitems -= 1 else: if child.isChecked(): return True return False def nextId(self): '''Determines which page should be shown next''' if self.field(FNInstallSDK).toBool() or self.field(FNUpgradeSDK).toBool(): if self.show_vdso_page: return PageIdVDSO if self.field(FNTargetX86Exist).toBool() or self.field(FNTargetArmelExist).toBool(): return PageIdTargets return PageIdPkg return PageIdSummary class UsersPage(QtGui.QWizardPage): '''User selection page''' def __init__(self): '''Constructor''' QtGui.QWizardPage.__init__(self) self.setTitle('User Selection') self.setSubTitle(" ") all_usernames = get_all_usernames() default_username = get_default_username() if not default_username: # in case default not found use first one default_username = all_usernames[0] users_list_widget = QtGui.QListWidget() txt = ("List of users on the system. %s will be installed for the " "selected user." % PRODUCT_NAME) users_list_widget.setToolTip(txt) users_list_widget.setWhatsThis(txt) # will be set in following signal handler self.__selected_username = None self.__target_x86_exist = False # targets for selected user self.__target_armel_exist = False self.connect(users_list_widget, QtCore.SIGNAL("currentTextChanged(const QString &)"), self.selectedUsernameChanged) # add usernames to the list for index, username in enumerate(all_usernames): users_list_widget.addItem(username) if username == default_username: users_list_widget.setCurrentRow(index) self.registerField(FNSelectedUsername, self, "selectedUsername") self.registerField(FNTargetX86Exist, self, "targetX86Exist") self.registerField(FNTargetArmelExist, self, "targetArmelExist") layout = QtGui.QVBoxLayout() label = QtGui.QLabel("Install the SDK for the following user:") layout.addWidget(label) layout.addWidget(users_list_widget) self.setLayout(layout) def getSelectedUsername(self): '''Returns selected username''' return self.__selected_username selectedUsername = QtCore.pyqtProperty("QString", getSelectedUsername) targetX86Exist = QtCore.pyqtProperty( "bool", lambda self: self.__target_x86_exist) targetArmelExist = QtCore.pyqtProperty( "bool", lambda self: self.__target_armel_exist) def selectedUsernameChanged(self, new_name): '''Signal handler for list widgets currentTextChanged. Sets the selected username to the current selection. Also updates related info.''' # segmentation faults without cast self.__selected_username = QtCore.QString(new_name) self.__target_x86_exist = scratchbox_target_exists( self.__selected_username, TARGET_X86) self.__target_armel_exist = scratchbox_target_exists( self.__selected_username, TARGET_ARMEL) class TargetsPage(QtGui.QWizardPage): '''Targets page, shown only if targets exist for selected user.''' def __init__(self): '''Constructor''' QtGui.QWizardPage.__init__(self) self.setTitle('Installation Targets') self.setSubTitle(" ") self.button_group = QtGui.QButtonGroup() self.btn_remove = QtGui.QRadioButton("&Overwrite existing targets") self.button_group.addButton(self.btn_remove, 0) self.btn_new = QtGui.QRadioButton("Create &new targets") self.button_group.addButton(self.btn_new, 1) self.group_box = QtGui.QGroupBox( "To &create new targets using different name prefix:") info_str = ("Specify a prefix to be used for the new targets. " "Ensure that it is not the same as any of the existing " "%s targets (eg: %s). When the new targets are created, " "the architecture (X86 or ARMEL) will be automatically added to the prefix." % \ (SB_NAME, TARGET_PREFIX)) info_label = QtGui.QLabel(info_str) info_label.setWordWrap(True) self.target_prefix_line_edit = QtGui.QLineEdit() group_box_layout = QtGui.QVBoxLayout() group_box_layout.addStretch() group_box_layout.addWidget(info_label) group_box_layout.addWidget(self.target_prefix_line_edit) self.group_box.setLayout(group_box_layout) self.connect(self.btn_remove, QtCore.SIGNAL("toggled(bool)"), self.removeTargetsCheckboxToggled) self.btn_new.setChecked(True) self.registerField(FNRemoveTargets, self.btn_remove) self.registerField(FNTargetPrefix, self.target_prefix_line_edit) layout = QtGui.QVBoxLayout() self.label = QtGui.QLabel() self.label.setWordWrap(True) layout.addWidget(self.label) layout.addWidget(self.btn_remove) layout.addWidget(self.btn_new) layout.addStretch() layout.addWidget(self.group_box) self.setLayout(layout) def initializePage(self): '''Overrides the method of QWizardPage, to initialize the page with some dynamic text.''' target_x86_exist = self.field(FNTargetX86Exist).toBool() target_armel_exist = self.field(FNTargetArmelExist).toBool() selected_username = self.field(FNSelectedUsername).toString() assert target_armel_exist or target_x86_exist, \ "At least one target should exist for this page to be usable!" # both exist if target_x86_exist and target_armel_exist: targets_str = "The targets %s and %s already exist for user %s." % \ (TARGET_X86, TARGET_ARMEL, selected_username) # either one exist else: if target_x86_exist: targets_str = "The target %s" % TARGET_X86 elif target_armel_exist: targets_str = "The target %s" % TARGET_ARMEL targets_str += " already exists for user %s." % selected_username self.label.setText("%s Please choose appropriate action." % (targets_str)) # set the line edits text to some non-existing target prefix nonexistent_prefix = TARGET_PREFIX + time.strftime("_%Y%m%d") while True: if scratchbox_prefix_exist(selected_username, nonexistent_prefix): nonexistent_prefix += "_" else: break self.target_prefix_line_edit.setText(nonexistent_prefix) def validatePage(self): '''Overrides the method of QWizardPage, verify valid input from the user''' # not removing targets, verify that prefix is not used in sb if not self.btn_remove.isChecked(): selected_username = self.field(FNSelectedUsername).toString() # prefix exist if scratchbox_prefix_exist(selected_username, self.target_prefix_line_edit.text()): QtGui.QMessageBox.critical( self, "Target exists!", "A %s target with the prefix %s already exists for user %s. " "Please specify a new prefix." % (SB_NAME, self.target_prefix_line_edit.text(), selected_username)) return False return True def removeTargetsCheckboxToggled(self, on): '''Handler for toggled signal of remove_targets_checkbox . Enables target name prefix group box if checkbox is unchecked, and disables the group box otherwise. When the group box is enabled the focus is set to line edit''' self.group_box.setDisabled(on) if not on: # group box is enabled self.target_prefix_line_edit.setFocus() class PkgPage(QtGui.QWizardPage): '''SDK package selection page''' def __init__(self): '''Constructor''' QtGui.QWizardPage.__init__(self) self.setTitle('Environment Selection') self.setSubTitle(" ") self.button_group = QtGui.QButtonGroup() self.registerField(FNSDKInstMOptArg, self, "mOptArgument") self.registerField(FNSDKInstMOptArgText, self, "mOptArgumentText") layout = QtGui.QVBoxLayout() label = QtGui.QLabel("Select the environment you wish to install.") layout.addWidget(label) # add all of the buttons for index, item in enumerate(INSTALL_OPTIONS): btn = QtGui.QRadioButton(item[0]) layout.addWidget(btn) self.button_group.addButton(btn, index) if index == DEFAULT_INSTALL_OPTION: # check the default button btn.setChecked(True) self.setLayout(layout) def getMOptArgument(self): '''Returns the argument of the -m option to be used with the SDK installer according to current selection''' return INSTALL_OPTIONS[self.button_group.checkedId()][1] def getMOptArgumentText(self): '''Returns the description of the -m option, with mnemonics removed''' return INSTALL_OPTIONS[self.button_group.checkedId()][0].replace('&', '') mOptArgument = QtCore.pyqtProperty( "QString", getMOptArgument, doc = "Argument to be passed to the -m option of the SDK installer") mOptArgumentText = QtCore.pyqtProperty( "QString", getMOptArgumentText, doc = "Description (UI text) of argument to be passed to the -m " "option of the SDK installer") def nextId(self): '''Determines which page should be shown next''' if self.field(FNSDKInstMOptArg).toString() != INSTALL_OPTIONS[0][1]: return PageIdNokiaBins return PageIdSummary class VDSOPage(QtGui.QWizardPage): '''VDSO support page, should be shown only if system has unsupported VDSO.''' def __init__(self): '''Constructor''' QtGui.QWizardPage.__init__(self) self.setTitle('VDSO Support') self.setSubTitle(" ") info_str = ("Current VDSO settings of this host are not " "supported by %s.\n\n" "To enable the installation of %s and %s this installer " "will set VDSO kernel parameter to %s supported " "value, which is 0. By default VDSO parameter will be " "set for this session only, which means the setting " "will persist until the next boot. If you would like the " "installer to set VDSO parameter permanently please " "check the checkbox below." % (SB_NAME, SB_NAME, PRODUCT_NAME, SB_NAME)) info_label = QtGui.QLabel(info_str) info_label.setWordWrap(True) set_perm_vdso_checkbox = QtGui.QCheckBox( "Permanently set &VDSO kernel parameter to 0") txt = ("If checked the VDSO kernel parameter will be set to %s " "compatible value" % (SB_NAME)) set_perm_vdso_checkbox.setToolTip(txt) set_perm_vdso_checkbox.setWhatsThis(txt) self.registerField(FNVDSOSetPerm, set_perm_vdso_checkbox) layout = QtGui.QVBoxLayout() layout.addWidget(info_label) layout.addSpacing(40) layout.addWidget(set_perm_vdso_checkbox) self.setLayout(layout) def nextId(self): '''Determines which page should be shown next''' if self.field(FNInstallSDK).toBool() or self.field(FNUpgradeSDK).toBool(): if self.field(FNTargetX86Exist).toBool() or self.field(FNTargetArmelExist).toBool(): return PageIdTargets return PageIdPkg return PageIdSummary class VDSO64BitPage(QtGui.QWizardPage): '''VDSO support page for 64bit X86 platforms.''' def __init__(self): '''Constructor''' QtGui.QWizardPage.__init__(self) self.setTitle('VDSO Support') self.setSubTitle(" ") info_str = ("Current VDSO settings of this host might not be " "supported by %s.\n\n" "To use %s and %s, VDSO must be disabled for 32bit programs. " "This can only be specified using a kernel boot command line option vdso32=0 " "You need to set this manually.\n" % (SB_NAME, SB_NAME, PRODUCT_NAME)) info_label = QtGui.QLabel(info_str) info_label.setWordWrap(True) layout = QtGui.QVBoxLayout() layout.addWidget(info_label) accept_check_box = QtGui.QCheckBox("I &accept") accept_check_box.setCheckState(QtCore.Qt.Unchecked) layout.addWidget(accept_check_box) self.registerField(FNAcceptVDSO64bit + '*', accept_check_box) self.setLayout(layout) def nextId(self): '''Determines which page should be shown next''' if self.field(FNEasyInstall).toBool(): return PageIdNokiaBins if self.field(FNInstallSDK).toBool() or self.field(FNUpgradeSDK).toBool(): if self.field(FNTargetX86Exist).toBool() or self.field(FNTargetArmelExist).toBool(): return PageIdTargets return PageIdPkg return PageIdSummary class NokiaBinsPage(QtGui.QWizardPage): '''Nokia Binaries page''' def __init__(self): '''Constructor''' QtGui.QWizardPage.__init__(self) self.setTitle('Nokia Binaries') label = QtGui.QLabel("Some of the Maemo APIs and Nokia applications are provided " "by Nokia proprietary binary packages. In order to install them, " "it is required to accept the End User License Agreement (EULA).") label.setWordWrap(True) self.setSubTitle(" ") # Checkbox to toggle installation of Nokia Bins. Will also toggle visibility of # components that require it. self.nokia_bins_checkbox = QtGui.QCheckBox("Install Nokia &Bins") txt = "Install Nokia Bins. Required for Maemo development." self.nokia_bins_checkbox.setToolTip(txt) self.nokia_bins_checkbox.setWhatsThis(txt) # Contains all items that will be hidden when nokia bins checkbox is unchecked self.nokia_bins_options = QtGui.QWidget() nokia_bins_options_layout = QtGui.QVBoxLayout() spacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.nokia_apps_checkbox = QtGui.QCheckBox( "Install Nokia &Apps") txt = "Install Nokia Apps. Requires Nokia Binaries." self.nokia_apps_checkbox.setToolTip(txt) self.nokia_apps_checkbox.setWhatsThis(txt) self.nokia_apps_checkbox.setChecked(True) self.registerField(FNInstallNokiaApps, self.nokia_apps_checkbox) self.loadinglabel = QtGui.QLabel("Loading EULA page, this may take a while.") self.loadinglabel.setWordWrap(True) self.loadingprogress = QtGui.QProgressBar() self.loadingprogress.setMinimum(0) self.loadingprogress.setMaximum(0) info_str = ('Please copy the Nokia Binaries sources.list entry from page below. ' 'Then paste that entry into the edit box below and press confirm button') self.info_label = QtGui.QLabel(info_str) self.info_label.setWordWrap(True) self.info_label.setOpenExternalLinks(True) self.info_label.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse or QtCore.Qt.LinksAccessibleByKeyboard) self.info_label.hide() self.timeout_timer = QtCore.QTimer() self.timeout_timer.setSingleShot(True) self.connect(self.timeout_timer, QtCore.SIGNAL("timeout()"), self.pageLoadTimeout) # Create line edit widget for repository in case, automatic copying # doesn't work on current PyQt version. self.repo_line = QtGui.QLineEdit() self.repo_line.setReadOnly(True) self.repo_line.hide() self.web = QtWebKit.QWebView() self.webpage = QtWebKit.QWebPage() self.confirm_button = QtGui.QPushButton("confirm") self.confirm_button.hide() self.connect(self.confirm_button, QtCore.SIGNAL("clicked()"), self, QtCore.SIGNAL("completeChanged()")) self.webpage.setLinkDelegationPolicy(QtWebKit.QWebPage.DelegateAllLinks) self.web.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding); self.connect(self.webpage, QtCore.SIGNAL("loadProgress(int)"), self.loadProgress) self.connect(self.webpage.mainFrame(), QtCore.SIGNAL("urlChanged(QUrl)"), self.urlChanged) self.connect(self.webpage, QtCore.SIGNAL("loadFinished(bool)"), self.pageLoaded) nokia_bins_options_layout.addWidget(self.nokia_apps_checkbox) nokia_bins_options_layout.addWidget(self.loadinglabel) nokia_bins_options_layout.addWidget(self.loadingprogress) nokia_bins_options_layout.addItem(spacer) nokia_bins_options_layout.addWidget(self.info_label) nokia_bins_options_layout.addWidget(self.web) nokia_bins_options_layout.addWidget(self.repo_line) nokia_bins_options_layout.addWidget(self.confirm_button) self.nokia_bins_options.setLayout(nokia_bins_options_layout) self.nokia_bins_checkbox.setChecked(True) self.connect(self.nokia_bins_checkbox, QtCore.SIGNAL("toggled(bool)"), self.groupBoxToggled) self.connect(self.nokia_bins_checkbox, QtCore.SIGNAL("toggled(bool)"), self, QtCore.SIGNAL("completeChanged()")) self.registerField(FNInstallNokiaBins, self.nokia_bins_checkbox) self.registerField(FNNokiaBinsRepo, self.repo_line) layout = QtGui.QVBoxLayout() layout.addWidget(label) layout.addWidget(self.nokia_bins_checkbox) layout.addWidget(self.nokia_bins_options) self.setLayout(layout) def initializePage(self): if not self.isRepositoryValid(): #Repository is invalid, probably empty. Erase it just in case. self.repo_line.setText("") proxy = self.getSystemProxySettings() if proxy != None: self.webpage.networkAccessManager().setProxy(proxy) #Show web page only if repository is not valid, otherwise #we will get just install nokia bins & apps checkboxes. self.loadEulaPage() def loadEulaPage(self): self.webpage.mainFrame().load(QtCore.QUrl(EULA_URL)) self.timeout_timer.start(90000) def loadProgress(self, progress): '''Update progress bar according to load progress''' self.loadingprogress.setMaximum(100) self.loadingprogress.setValue(progress) def urlChanged(self, url): '''Decides what to do when url changes''' if url.toString() == TOKEN_URL: self.web.hide() self.connect(self.webpage, QtCore.SIGNAL("loadFinished(bool)"), self.getRepositoryLine) else: self.web.show() def getRepositoryLine(self, ok): '''Copies the repository line from the fully loaded web page''' if ok: if self.web.page().findText("deb "): self.web.page().triggerAction(QtWebKit.QWebPage.SelectEndOfLine) self.repo_line.setText(self.webpage.selectedText()) if self.isRepositoryValid(): self.emit(QtCore.SIGNAL("completeChanged()")) self.wizard().next() return #Failed to get repository line automatically self.info_label.show() self.web.show() self.confirm_button.show() self.repo_line.setText("") self.repo_line.setReadOnly(False) self.repo_line.show() def pageLoaded(self, ok): '''Page loaded, stop timeout timer''' self.timeout_timer.stop() if ok: #Loaded page successfully, show page & hide loading text self.loadinglabel.hide() self.loadingprogress.hide() self.web.setPage(self.webpage) else: #Loading failed (or timeout), try reload self.loadingprogress.setMinimum(0) self.loadingprogress.setMaximum(0) self.web.reload() def pageLoadTimeout(self): '''Executed when page isn't loaded in specified time''' self.web.reload() def groupBoxToggled(self, on): '''Handler for group box's toggled signal. The goal is to focus on line edit when group box is enabled. The focus is removed from disabled line edit because if it has focus it steals all keyboard events, making keyboard unusable. Also, if line edit is disabled the focus is moved to the group box's checkbox, without this the focus would jump to the Next button.''' if on: # group box is enabled self.nokia_apps_checkbox.show() if not self.isRepositoryValid(): self.web.show() self.loadEulaPage() else: self.web.hide() self.nokia_apps_checkbox.hide() def cleanupPage(self): '''Overridden, not to clean-up the repo text edit as the default implementation does.''' return def validatePage(self): '''Overrides the method of QWizardPage, verify valid input from the user''' # make sure the specified repo seems valid if self.nokia_bins_checkbox.isChecked(): if not self.isRepositoryValid(): QtGui.QMessageBox.critical( self, "Invalid repository!", "Specified repository is invalid, please provide valid "\ "one!", QtGui.QMessageBox.Ok) return False else: self.nokia_apps_checkbox.setChecked(False) return True def isRepositoryValid(self): '''make sure the specified repo is close to valid''' repo = str(self.repo_line.text()) pat = r"deb .* nokia-binaries$" if re.match(pat, repo): return True return False def getSystemProxySettings(self): '''Returns QNetworkProxy for WebKit based on system proxy settings''' proxy = get_proxy('http') if proxy: http_proxy = proxy['http'] username_password = [] index = http_proxy.find('@') if index != -1: # We have username & password split_proxy = http_proxy.split('@') if len(split_proxy) == 2: # We have username specified if split_proxy[0].lower().startswith('http://'): username_password = split_proxy[0][7:].split(':') host_port_str = split_proxy[1] else: return None else: if http_proxy.lower().startswith('http://'): host_port_str = http_proxy[7:] else: return None host_port = host_port_str.split(':') if len(host_port) == 2: port = host_port[1].rstrip('/ ') try: port_num = int(port) except: return None proxy = QtNetwork.QNetworkProxy() proxy.setHostName(host_port[0]) proxy.setPort(port_num) proxy.setType(QtNetwork.QNetworkProxy.HttpProxy) if username_password != []: proxy.setUser(username_password[0]) proxy.setPassword(username_password[1]) return proxy return None def isComplete(self): '''Overrides the method of QWizardPage, to disable next button if installation is selected, but repository is not valid.''' if self.nokia_bins_checkbox.isChecked(): return self.isRepositoryValid() else: return True class NokiaBinsPageNoWebkit(QtGui.QWizardPage): '''Nokia Binaries page''' def __init__(self): '''Constructor''' QtGui.QWizardPage.__init__(self) self.setTitle('Nokia Binaries') self.setSubTitle(" ") label = QtGui.QLabel("Some of the Maemo APIs and Nokia applications are provided " "by Nokia proprietary binary packages. In order to install them, " "it is required to accept the End User License Agreement (EULA).") label.setWordWrap(True) self.nokia_bins_checkbox = QtGui.QCheckBox( "Install Nokia &Bins") txt = "Install Nokia Bins. Required for Maemo development." self.nokia_bins_checkbox.setToolTip(txt) self.nokia_bins_checkbox.setWhatsThis(txt) # Contains all items that will be hidden when nokia bins checkbox is unchecked self.nokia_bins_options = QtGui.QWidget() nokia_bins_options_layout = QtGui.QVBoxLayout() self.nokia_apps_checkbox = QtGui.QCheckBox("&Install Nokia Apps") self.nokia_apps_checkbox.setChecked(True) self.registerField(FNInstallNokiaApps, self.nokia_apps_checkbox) info_str = ('Please visit this ' 'webpage to ' 'obtain Nokia Binaries sources.list entry. Then paste that entry into ' 'the edit box below in order for Nokia Binaries to be installed into ' 'targets by this installer' % (EULA_URL,)) info_label = QtGui.QLabel(info_str) info_label.setWordWrap(True) info_label.setOpenExternalLinks(True) info_label.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse or QtCore.Qt.LinksAccessibleByKeyboard) txt = "Use link to visit %s to get sources.list entry" % EULA_URL info_label.setToolTip(txt) info_label.setWhatsThis(txt) self.repo_line_edit = QtGui.QLineEdit() nokia_bins_options_layout.addWidget(self.nokia_apps_checkbox) nokia_bins_options_layout.addWidget(info_label) nokia_bins_options_layout.addWidget(self.repo_line_edit) self.nokia_bins_options.setLayout(nokia_bins_options_layout) self.nokia_bins_checkbox.setChecked(True) self.connect(self.nokia_bins_checkbox, QtCore.SIGNAL("toggled(bool)"), self.groupBoxToggled) self.registerField(FNInstallNokiaBins, self.nokia_bins_checkbox) self.registerField(FNNokiaBinsRepo, self.repo_line_edit) layout = QtGui.QVBoxLayout() layout.addWidget(label) layout.addWidget(self.nokia_bins_checkbox) layout.addWidget(self.nokia_bins_options) layout.addStretch() self.setLayout(layout) def groupBoxToggled(self, on): '''Handler for group box's toggled signal. The goal is to focus on line edit when group box is enabled. The focus is removed from disabled line edit because if it has focus it steals all keyboard events, making keyboard unusable. Also, if line edit is disabled the focus is moved to the group box's checkbox, without this the focus would jump to the Next button.''' if on: # group box is enabled self.nokia_bins_options.setFocusProxy(self.repo_line_edit) self.nokia_bins_options.show() else: self.nokia_bins_options.setFocusProxy(None) self.nokia_bins_options.hide() self.nokia_bins_options.setFocus() def cleanupPage(self): '''Overridden, not to clean-up the repo text edit as the default implementation does.''' return def validatePage(self): '''Overrides the method of QWizardPage, verify valid input from the user''' # make sure the specified repo is close to valid if self.nokia_bins_checkbox.isChecked(): repo = str(self.repo_line_edit.text()) pat = r"deb .* nokia-binaries$" m = re.match(pat, repo) if not m: QtGui.QMessageBox.critical( self, "Invalid repository!", "Specified repository is invalid, please provide valid "\ "one!", QtGui.QMessageBox.Ok) return False else: self.nokia_apps_checkbox.setChecked(False) return True class SummaryPage(QtGui.QWizardPage): '''Summary page''' def __init__(self): '''Constructor''' QtGui.QWizardPage.__init__(self) self.setTitle('Summary') self.setSubTitle(" ") # pages after this will have back button disabled self.setCommitPage(True) self.summary_label = QtGui.QLabel() self.summary_label.setWordWrap(True) self.summary_label.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) # scroll area is used to add a scrolling capability in case the # text in the label becomes too long scroll_area = QtGui.QScrollArea(self) scroll_area.setWidgetResizable(True) scroll_area.setFrameStyle(QtGui.QFrame.NoFrame) scroll_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) scroll_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) # widget placed in scroll area sa_widget = QtGui.QWidget() # layout for the widget sa_widget_layout = QtGui.QVBoxLayout(sa_widget) sa_widget_layout.addWidget(self.summary_label) scroll_area.setWidget(sa_widget) # layout for the page layout = QtGui.QVBoxLayout() layout.addWidget(scroll_area) self.setLayout(layout) def initializePage(self): '''Overrides the method of QWizardPage, to initialize the page with summary text.''' summary = "" # create the summary text if self.field(FNUninstall).toBool(): self.wizard().setButtonText(QtGui.QWizard.CommitButton, "&Uninstall") summary += "Uninstall %s: Yes

" % SB_NAME else: self.wizard().setButtonText(QtGui.QWizard.CommitButton, "&Install") # scratchbox easy_install = self.field(FNEasyInstall).toBool() install_sb = self.field(FNInstallSB).toBool() if install_sb: summary += "%s %s: %s

" % \ (self.wizard().hostInfo.scratchbox_op_name, SB_NAME, bool_to_yesno(self.field(FNInstallSB).toBool())) # user summary += "Username: %s

" % \ (self.field(FNSelectedUsername).toString()) # SDK install_sdk = self.field(FNInstallSDK).toBool() upgrade_sdk = self.field(FNUpgradeSDK).toBool() install_desktop_links = self.field(FNInstallDesktopLinks).toBool() summary += "Install %s: %s

" % \ (PRODUCT_NAME, bool_to_yesno(install_sdk)) # VDSO if self.wizard().hostInfo.has_64bit: summary += "Permanently set VDSO: User sets manually

" else: if self.wizard().hostInfo.has_unsupported_vdso: summary += "Permanently set VDSO: %s

" % \ (bool_to_yesno(self.field(FNVDSOSetPerm).toBool() or self.field(FNEasyInstall).toBool())) # these are only available if SDK is installed if install_sdk: # targets target_x86_exist = self.field(FNTargetX86Exist).toBool() target_armel_exist = self.field(FNTargetArmelExist).toBool() if target_x86_exist or target_armel_exist: # they exist # remove targets remove_targets = self.field(FNRemoveTargets).toBool() or self.field(FNEasyInstall).toBool() summary += "Overwrite targets: %s

" % \ (bool_to_yesno(remove_targets)) # target name prefix if not remove_targets: summary += "Target name prefix: %s

" % \ (self.field(FNTargetPrefix).toString()) # packages summary += "Packages to install: %s

" % \ (self.field(FNSDKInstMOptArgText).toString()) # Nokia Binaries install_nokia_bins = self.field(FNInstallNokiaBins).toBool() and \ self.field(FNSDKInstMOptArg).toString() != INSTALL_OPTIONS[0][1] summary += "Install Nokia Binaries: %s

" % \ (bool_to_yesno(install_nokia_bins)) # Nokia Apps if install_sdk or upgrade_sdk: install_nokia_apps = self.field(FNInstallNokiaApps).toBool() summary += "Install Nokia Apps: %s

" % bool_to_yesno(install_nokia_apps and self.field(FNSDKInstMOptArg).toString() != INSTALL_OPTIONS[0][1]) if upgrade_sdk: summary += "Upgrade %s: %s

" % \ (PRODUCT_NAME, bool_to_yesno(upgrade_sdk)) if self.field(FNInstallXephyr).toBool(): summary += "Install Xephyr: Yes

" summary += "Install Xephyr desktop shortcut: %s

" % \ (bool_to_yesno(self.field(FNInstallXephyrShortcut).toBool())) summary += "Install Scratchbox home directory shortcut: %s

" % \ (bool_to_yesno(self.field(FNCreateSbHomeShortcut).toBool())) summary += "Install Maemo-related Desktop links: %s

" % \ (bool_to_yesno(install_desktop_links)) # yes, I hate manual work dont_want_end = "

" if summary.endswith(dont_want_end): summary = summary[:-len(dont_want_end)] self.summary_label.setText(summary) class ExecutorThread(QtCore.QThread): '''Thread that does the actual installation. This is not done in the GUI thread not to freeze the GUI. Because in GUI applications, the main thread (a.k.a. the GUI thread) is the only thread that is allowed to perform GUI-related operations, signals are sent from this thread to the main thread to give some visual feedback of completed operations.''' # thread exit status ExitStatusNotStarted = "status_not_started" ExitStatusOK = "status_ok" ExitStatusAborted = "status_aborted" ExitStatusError = "status_error" def __init__(self, host_info, uninstall, install_scratchbox, install_sdk, upgrade_sdk, install_apps, sdk_inst_m_opt_arg, selected_username, targets_exist, remove_targets, target_prefix, vdso_set_perm, install_nokia_bins, nokia_bins_repo, install_xephyr, install_xephyr_icon, install_sb_home_shortcut, install_desktop_links): '''Constructor''' QtCore.QThread.__init__(self) self.__host_info = host_info self.__uninstall = uninstall self.__install_scratchbox = install_scratchbox self.__install_sdk = install_sdk self.__upgrade_sdk = upgrade_sdk self.__install_apps = install_apps self.__sdk_inst_m_opt_arg = sdk_inst_m_opt_arg self.__selected_username = selected_username self.__targets_exist = targets_exist self.__remove_targets = remove_targets self.__target_prefix = target_prefix self.__vdso_set_perm = vdso_set_perm self.__install_nokia_bins = install_nokia_bins and (self.__sdk_inst_m_opt_arg != INSTALL_OPTIONS[0][1]) self.__nokia_bins_repo = nokia_bins_repo self.__install_xephyr = install_xephyr self.__install_xephyr_icon = install_xephyr_icon self.__install_sb_home_shortcut = install_sb_home_shortcut self.__install_desktop_links = install_desktop_links # for progress indication (a task can have many ops) self.__ops_total_num = self.__calcOpsTotalNum() self.__ops_done_num = 0 # number of done so far # abort installation after current task is completed self.__abort = False self.__is_running_last_task = False self.__exit_status = self.__class__.ExitStatusNotStarted # signals emitted when new installation operation started/ended (the # GUI thread uses these signals to update the progress bar and text on # a label) self.sig_op_started = QtCore.SIGNAL("opStarted") self.sig_op_ended = QtCore.SIGNAL("opEnded") LOG("Executor created: %s" % self) opsTotalNum = property(lambda self: self.__ops_total_num) exitStatus = property(lambda self: self.__exit_status) def abort(self): '''Called by the main thread to stop this thread after current installation operation completes. If the thread is running last task, the request to abort will be ignored, since the installation will end after that task anyway. It is possible to have the abort dialog shown in the beginning while the executor will proceed to run last task, in that case the user will have "Abort later" button visible even though last task is executed.''' if not self.isRunningLastTask(): LOG("Executor accepted request to abort") self.__abort = True else: LOG("Executor ignoring abort request since running last task") def isAborting(self): '''Whether this thread is about to abort the installation.''' return self.__abort def isRunningLastTask(self): '''Whether this thread is executing last task.''' return self.__is_running_last_task def __str__(self): '''String representation method.''' return str(self.__dict__) def __calcOpsTotalNum(self): '''Returns number of total operations to complete the installation. Each task can have many operations.''' count = 0 if self.__vdso_set_perm: count += 1 if self.__uninstall: count += 3 else: if self.__install_scratchbox: count += 2 if self.__install_sdk: count += 2 if self.__upgrade_sdk: count += 1 if self.__install_nokia_bins: count += 2 # 1 for each target if self.__install_xephyr: count += 1 if self.__install_xephyr_icon: count += 1 if self.__install_sb_home_shortcut: count += 1 if self.__install_desktop_links: count += 1 return count def __taskSetVdso(self): '''Sets VDSO kernel parameter to scratchbox supported value. Even if setting permanently, will first try to set for current boot session only, since the VDSO is known to crash some systems (e.g. Ubuntu Gutsy). If this is not done the system might become unbootable because of permanent VDSO settings.''' if not self.__host_info.has_64bit: if self.__vdso_set_perm: self.__say("Setting VDSO permanently") else: self.__say("Setting VDSO for this session") # set it for this session only (will persist until the next boot) LOG("Attempting to set VDSO for this session") try: exec_cmd("sysctl -w vm.vdso_enabled=0") except: LOG("Failed to disable VDSO for this session") raise Exception("Failed to disable VDSO for this session") # now set it permanently if self.__vdso_set_perm: LOG("Permanently setting VDSO") file_append_lines("/etc/sysctl.conf", ["", "# This line was added by %s" % (MY_NAME), "vm.vdso_enabled = 0"]) try: #Fedora 12 seems to have some unknown keys, which we need to ignore with -e exec_cmd("sysctl -e -p") except: LOG("sysctl -e -p failed") raise Exception("sysctl -e -p failed") else: LOG("Running on 64bit platform, not setting VDSO") def __taskInstallScratchbox(self): '''Downloads scratchbox installer, installs scratchbox, removes the installer''' self.__say("Downloading %s installer" % (SB_NAME)) sb_installer_fn = download_file(SB_INSTALLER_URL) if os.path.isfile("%s/etc/scratchbox-version" %(SB_PATH)): self.__say("Upgrading %s" % (SB_NAME)) else: self.__say("Installing %s" % (SB_NAME)) #If using proxy, give it to the command aswell proxy = get_proxy('http') if proxy: proxystring = "http_proxy=%s " % proxy['http'] else: proxystring = "" opt = " -u %s " % self.__selected_username if self.__host_info.has_64bit: opt += "-F " # upgrade scratchbox - may change between tries if os.path.isfile("%s/etc/scratchbox-version" %(SB_PATH)): opt += "-U " if not self.__host_info.has_apt_get: #We need to set this if installing for tgz opt += "-s %s " % SB_PATH cmd = proxystring + sb_installer_fn + opt tries = 3 while (True): try: exec_cmd(cmd) except: tries -= 1 if tries: LOG("Failed to install Scratchbox, %d tries left" % tries) else: LOG("Installer execution failed") raise Exception("Installer execution failed") pass else: break LOG("Removing %s installer (%s)" % (SB_NAME, sb_installer_fn)) os.remove(sb_installer_fn) #Add user to group, if not already there if not has_user_group(self.__selected_username, SB_GROUP): add_user_to_group(self.__selected_username, SB_GROUP) def __taskInstallSdk(self): '''Downloads SDK installer, installs SDK, removes the installer''' global OPTIONS self.__say("Downloading %s installer" % (PRODUCT_NAME_SHORT)) sdk_installer_fn = download_file(SDK_INSTALLER_URL, self.__selected_username) # make the installer really non-interactive comment_line = "license\n" lines = open(sdk_installer_fn).readlines() for index, line in enumerate(lines): if line == comment_line: lines[index] = "# " + line LOG("Successfully patched the SDK installer") break else: raise Exception("SDK installer is strange, line %s not found!" % comment_line) open(sdk_installer_fn, "w").writelines(lines) # do the installation thing proxy = get_proxy('http') if proxy: cmd = "http_proxy=%s " % proxy['http'] else: cmd = "" cmd += "%s -d -m %s" % (sdk_installer_fn, self.__sdk_inst_m_opt_arg) if self.__targets_exist: if self.__remove_targets: cmd += " -y" else: cmd += " -n %s" % self.__target_prefix else: cmd += " -y" if OPTIONS.sourceslistfile: cmd += " -a %s" % OPTIONS.sourceslistfile self.__say("Installing %s targets" % PRODUCT_NAME_SHORT) tries = 3 while (True): try: exec_cmd(cmd, username = self.__selected_username, set_sbox_gid = True) except: tries -= 1 if tries: LOG("Failed to install SDK, %d tries left" % tries) else: LOG("Failed to install SDK") raise Exception("Failed to install SDK") pass else: break LOG("Removing %s installer (%s)" % (PRODUCT_NAME_SHORT, sdk_installer_fn)) os.remove(sdk_installer_fn) def __taskInstallNokiaBins(self): '''Installs Nokia Binaries into the targets''' # different target prefix was chosen if self.__targets_exist and not self.__remove_targets: armel_target = self.__target_prefix + TARGET_POSTFIX_ARMEL x86_target = self.__target_prefix + TARGET_POSTFIX_X86 # default target names else: armel_target = TARGET_ARMEL x86_target = TARGET_X86 for target in [armel_target, x86_target]: self.__say("Installing Nokia Binaries into target %s" % (target)) packages = "nokia-binaries " if self.__install_apps: packages += "nokia-apps " # select target LOG("Selecting target %s in scratchbox" % target) cmd = "%s/tools/bin/sb-conf select %s" % (SB_PATH, target) try: exec_cmd(cmd, username = self.__selected_username, set_sbox_gid = True) except: LOG("Failed to select target %s" % target) raise Exception("Failed to select target") # add repo to sources.list sources_list_fn = ( "%s/users/%s/targets/%s/etc/apt/sources.list" % (SB_PATH, self.__selected_username, target)) LOG("Adding Nokia Binaries repo (%s) to sources.list of the " "target (%s)" % (self.__nokia_bins_repo, sources_list_fn)) file_append_lines( sources_list_fn, ["", "# Repository for Nokia Binaries, added by %s" % (MY_NAME), self.__nokia_bins_repo]) # install the binaries LOG("Starting the installation of packages %s" % packages) proxy = get_proxy('http') if proxy: cmd = "http_proxy=%s " % proxy['http'] else: cmd = "" cmd += ("%s/login fakeroot apt-get update && %s/login " "fakeroot apt-get -o Acquire::Retries=5 install %s --force-yes -y" % (SB_PATH, SB_PATH, packages)) LOG("Executing command: %s" % cmd) tries = 3 while (True): try: exec_cmd(cmd, username = self.__selected_username, set_sbox_gid = True) except: tries -= 1 if tries: LOG("Failed to install Nokia Binaries, %d tries left" % tries) else: LOG("Failed to install Nokia Binaries, giving up") raise Exception("Failed to install Nokia Binaries, giving up") pass else: break def __taskUpgradeSdk(self): '''Upgrade selected targets''' self.__say("Upgrading SDK targets") LOG("Starting to upgrade targets") cmd = '/tmp/maemo-sdk-install-wizard_upgrade.sh' if os.path.exists(cmd): tries = 3 while (True): try: exec_cmd(cmd, username = self.__selected_username, set_sbox_gid = True) except: tries -= 1 if tries: LOG("Failed to upgrade, %d tries left" % tries) else: LOG("Failed to upgrade, giving up") raise Exception("Failed to upgrade, giving up") pass else: break def __taskInstallXephyr(self): '''apt-get install xserver-xephyr''' self.__say("Installing xephyr") command = "" if self.__host_info.has_apt_get: command = "apt-get -y install xserver-xephyr" exec_cmd(command) elif self.__host_info.has_yum: command = "yum -y install xorg-x11-server-Xephyr" if command: exec_cmd(command) else: LOG("No compatible package manager found. You must install Xephyr manually.") return def __taskInstallXephyrShortcut(self): self.__say("Installing xephyr desktop shortcut") LOG("Starting to install xephyr desktop shortcut") desktopentry_fn = XEPHYR_DESKTOP_ENTRY_FILENAME scriptpath = None for path in BINPATHS: if os.path.isdir(path): LOG("Using path %s" % path) scriptpath = path break if scriptpath: LOG("Installing Xephyr launcher %s to %s" % (XEPHYR_SHORTCUT_FILENAME, scriptpath)) filename = "%s/%s" % (scriptpath, XEPHYR_SHORTCUT_FILENAME) if self.__host_info.has_yum: #Version of Xephyr that comes with fedora doesn'n seem to support -kb argument #It should be investigated which distros have which version, some rpm based #distros probably support -kb while some deb based probably don't. kb_arc = '' else: kb_arc = ' -kb' script = ("#!/bin/sh\n" "# Automatically created by %s\n" "(Xephyr :2 -host-cursor -screen 800x480x16 -dpi 96 -ac%s; newgrp %s <<'END'\n" "%s/login af-sb-init.sh stop\n" "END\n" ") &\n" "newgrp %s <<'END'\n" "sleep 3\n" "%s/login sb-conf select %s\n" "%s/login af-sb-init.sh restart\n" "END\n" % (MY_NAME, kb_arc, SB_GROUP, SB_PATH, SB_GROUP, SB_PATH, TARGET_X86, SB_PATH)) launcherfile = file(filename, 'wt') launcherfile.write(script) launcherfile.close() p = subprocess.Popen(['chmod', '0755', '%s' % filename]) p.wait() if p.returncode: raise Exception("Failed to set file execution permission for %s" % filename) p = subprocess.Popen(['chown', 'root:%s' % SB_GROUP, '%s' % filename]) p.wait() if p.returncode: raise Exception("Failed to set file owner") desktopdir = get_user_desktop_dir(self.__selected_username) if desktopdir: desktopfilename = "%s/%s" % (desktopdir, desktopentry_fn) LOG("Installing Xephyr desktop entry %s to %s" % (desktopentry_fn, desktopdir)) desktopentry = ("[Desktop Entry]\n" "Version=5.0\n" "Encoding=UTF-8\n" "Name=Maemo5 SDK\n" "Comment=shortcut to maemo desktop\n" "Exec=%s\n" "Terminal=false\n" "Type=Application\n" "StartupNotify=true\n" % filename) desktopfile = file(desktopfilename, 'wt') desktopfile.write(desktopentry) desktopfile.close() p = subprocess.Popen(['chmod', '0750', '%s' % desktopfilename]) p.wait() if p.returncode: raise Exception("Failed to set file permissions for %s" % desktopfilename) p = subprocess.Popen(['chown', '%s' % (self.__selected_username), '%s' % desktopfilename]) p.wait() if p.returncode: raise Exception("Failed to set file owner for %s" % desktopfilename) def __taskInstallSbHomeShortcut(self): self.__say("Adding scratchbox home shortcut") LOG("Starting to add scratchbox home shortcut") shortcut_fn = "sbhome" desktopdir = get_user_desktop_dir(self.__selected_username) if desktopdir: filename = "%s/%s" % (desktopdir, shortcut_fn) if not os.path.islink(filename): sbhomedir = "%s/users/%s/home/%s/" % (SB_PATH, self.__selected_username, self.__selected_username) if os.path.isdir(sbhomedir): LOG("Adding scratchbox home shortcut %s to %s" % (shortcut_fn, desktopdir)) p = subprocess.Popen(['ln', '-s', sbhomedir, filename]) p.wait() if p.returncode: raise Exception("Failed to create symbolic link") else: LOG("Couldn't find directory %s, not adding link" % (sbhomedir)) def __taskInstallLinks(self): self.__say("Adding links to desktop") htmldata = ('' '' '' ' ' ' ' ' ' ' ' ' ' ' ' '' '' '



' '

' '

Maemo 5 ' '

' '



' '

' '

Getting Started

' '

Introduction

' '

Architecture

' '

Desktop Widget UI Guidelines

' '

Developer Guide

' '

Developer Tools

' '

API References

' '

Sample Example Code

' '



' '

' '

More Information

' '

Q & A SDK and Scratchbox

' '

Q & A Porting to Fremantle

' '

Accelerometers

' '

OpenGL-ES

' '



' '

' '

Community Application Repositories

' '

Extras-devel

' '

Extras-testing

' '

Uploading packages

' '' '\n') desktopdir = get_user_desktop_dir(self.__selected_username) if desktopdir: LOG("Adding links to %s" % desktopdir) filename = '%s/%s' % (desktopdir, MAEMO_LINKS_FILENAME) htmlfile = file(filename, 'wt') htmlfile.write(htmldata) htmlfile.close() p = subprocess.Popen(['chmod', '0750', '%s' % filename]) p.wait() if p.returncode: raise Exception("Failed to set file permissions for %s" % filename) p = subprocess.Popen(['chown', '%s' % (self.__selected_username), '%s' % filename]) p.wait() if p.returncode: raise Exception("Failed to set file owner for %s" % filename) def __taskUninstallSb(self): self.__say("Uninstalling %s targets" % (SB_NAME)) LOG("Starting to uninstall %s targets" % (SB_NAME)) #1)Remove targets userlist = get_scratchbox_users() for user in userlist: if has_user_active_sessions(user): LOG("User %s has active scratchbox session(s), which prevent " "%s uninstallation." % (SB_NAME, SB_NAME)) raise Exception ("User %s has active scratchbox session(s)" % user) else: #User doesn't have active sessions, remove targets targets = get_user_targets(user) if targets: LOG("Removing targets for user %s" % (user)) for target in targets: #LOG("Removing target %s" % (target)) remove_scratchbox_target(user, target) #2)Remove scratchbox self.__say("Uninstalling %s" % (SB_NAME)) LOG("Targets removed, starting to uninstall %s" % (SB_NAME)) uninstall_scratchbox(self.__host_info.has_64bit) #3)Remove scratchbox home directory symlink, if /scratchbox dir was removed if not os.path.isdir(SB_PATH): shortcut_fn = 'sbhome' filename = "%s/%s" % (desktopdir, shortcut_fn) if os.path.islink(filename): LOG("Removing desktop shortcut to %s, as directory no longer exists" % SB_PATH) exec_cmd('rm %s' % filename) #4)Remove xephyr launcher. self.__say("%s uninstalled, removing Xephyr launcher" % (SB_NAME)) LOG("%s uninstalled, removing Xephyr launcher" % (SB_NAME)) scriptpath = None for path in BINPATHS: if os.path.isdir(path): scriptpath = path break if scriptpath: filename = scriptpath + "/" + XEPHYR_SHORTCUT_FILENAME if os.path.isfile(filename): LOG("Removing Xephyr launcher") exec_cmd('rm %s' % filename) desktopdir = get_user_desktop_dir(self.__selected_username) if desktopdir: #5)Remove xephyr launcher desktop icon. filename = "%s/%s" % (desktopdir, 'xephyr.desktop') if os.path.isfile(filename): LOG("Removing Xephyr launcher icon") exec_cmd('rm %s' % filename) #6)Remove Maemo links. filename = '%s/%s' % (desktopdir, MAEMO_LINKS_FILENAME) if os.path.isfile(filename): LOG("Removing Maemo links") exec_cmd('rm "%s"' % filename) def __getTasks(self): '''Returns list of installation tasks to be executed in order''' tasks = [] #Needed by sb-conf used in uninstall aswell if self.__vdso_set_perm: tasks.append(self.__taskSetVdso) if self.__uninstall: tasks.append(self.__taskUninstallSb) else: if self.__install_scratchbox: tasks.append(self.__taskInstallScratchbox) if self.__install_sdk: tasks.append(self.__taskInstallSdk) if self.__upgrade_sdk: tasks.append(self.__taskUpgradeSdk) if self.__install_nokia_bins: tasks.append(self.__taskInstallNokiaBins) if self.__install_xephyr: tasks.append(self.__taskInstallXephyr) if self.__install_xephyr_icon: tasks.append(self.__taskInstallXephyrShortcut) if self.__install_sb_home_shortcut: tasks.append(self.__taskInstallSbHomeShortcut) if self.__install_desktop_links: tasks.append(self.__taskInstallLinks) LOG("Got tasks: %s" % tasks) return tasks def run(self): '''Runs the installation''' tasks = self.__getTasks() try: for task in tasks: if self.__abort: LOG("Executor aborting") self.__exit_status = self.__class__.ExitStatusAborted break else: LOG("Executor starting task %s" % (task)) if task == tasks[-1]: self.__is_running_last_task = True LOG("Started task is the last one") task() # all tasks successfully completed else: self.__exit_status = self.__class__.ExitStatusOK # for the last op self.emit(self.sig_op_ended, self.__ops_done_num) except: # CmdExecError, e: LOG.log_exc() self.__exit_status = self.__class__.ExitStatusError LOG("Executor set exit status to (%s)" % (self.__exit_status)) def __say(self, msg): '''Communicates with the main thread about installation progress using signals''' self.emit(self.sig_op_ended, self.__ops_done_num) # previous op ended self.__ops_done_num += 1 # new op started assert self.__ops_done_num <= self.__ops_total_num, "Too many ops!" msg = "%s/%s %s" % (self.__ops_done_num, self.__ops_total_num, msg) LOG("S: %s" % msg) self.emit(self.sig_op_started, msg, self.__ops_done_num) class ProgressPage(QtGui.QWizardPage): '''Progress page''' def __init__(self, tail_process): '''Constructor''' QtGui.QWizardPage.__init__(self) # whether the slider in the text widget has been scrolled to its # vertical scroll bars end, it will be scrolled only once at # initialization; when the slider is scrolled to the vertical end, the # newly appended text will cause automatic scrolling, so the new text # will always be visible self.scrolled_to_vend = False self.setSubTitle(" ") self.status_label = QtGui.QLabel("Installing...") self.status_label.setWordWrap(True) self.progress_bar = QtGui.QProgressBar() self.error_label = QtGui.QLabel("") txt = ('Log file %s' % (LOG.fn_log, LOG.fn_log)) self.logs_url_label = QtGui.QLabel(txt) self.logs_url_label.setWordWrap(True) self.logs_url_label.setOpenExternalLinks(True) self.logs_url_label.setTextInteractionFlags( QtCore.Qt.TextBrowserInteraction) # some systems have such bug that file can't be opened by clicking on # URL, so no hint on using the link, see # https://bugs.launchpad.net/ubuntu/+source/xdg-utils/+bug/362121 self.logs_text_edit = QtGui.QTextEdit() txt = "Logs from %s. Use Ctrl+Wheel to zoom the text." % LOG.fn_log self.logs_text_edit.setToolTip(txt) self.logs_text_edit.setWhatsThis(txt) # set proper coloring of the text edit # must be done before writing any text, otherwise black text # will not be visible once you change background to black # # new in Qt 4.4: not used now, since some systems won't have it # self.logs_text_edit.setTextBackgroundColor(QtCore.Qt.black) palette = QtGui.QPalette() palette.setColor(QtGui.QPalette.Active, QtGui.QPalette.Base, QtCore.Qt.black); palette.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Base, QtCore.Qt.black); self.logs_text_edit.setPalette(palette); self.logs_text_edit.setTextColor(QtCore.Qt.green) # there must be some text in the editor, otherwise the color of text # written with cursor will be black, why? go figure... self.logs_text_edit.setPlainText("logs from %s\n" % LOG.fn_log) self.logs_text_edit.setReadOnly(True) # to look like a proper terminal use fixed width fonts self.logs_text_edit.setFont(QtGui.QFont("Monospace", 10)) # cursor is used to append text to the end of the edit widget, there is # append method but it adds extra newline, and InsertPlainText method # inserts text at current cursor position, which can be changed by the # user just by clicking somewhere in the edit widget self.cursor = self.logs_text_edit.textCursor() self.cursor.movePosition(QtGui.QTextCursor.End) self.logs_button = QtGui.QPushButton("&Logs") txt = "Toggles visibility of the logs view" self.logs_button.setToolTip(txt) self.logs_button.setWhatsThis(txt) self.logs_button.setCheckable(True) self.connect(self.logs_button, QtCore.SIGNAL("toggled(bool)"), self.logsButtonToggled) # takes the space of text edit when it is hidden self.spacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.connect(tail_process, QtCore.SIGNAL("readyReadStandardOutput()"), self.tailProcessReadStdout) layout = QtGui.QVBoxLayout() layout.addWidget(self.status_label) layout.addWidget(self.progress_bar) layout.addWidget(self.error_label) # spacer will be removed in button's toggled signal handler layout.addItem(self.spacer) layout.addWidget(self.logs_url_label) layout.addWidget(self.logs_text_edit) self.setLayout(layout) self.logs_button.setChecked(True) # will emit toggled signal def initializePage(self): '''Overrides the method of QWizardPage, to initialize the page with some default settings.''' QtGui.QApplication.setOverrideCursor(QtCore.Qt.BusyCursor) setvdso = False if not self.wizard().hostInfo.has_64bit: if self.wizard().hostInfo.has_unsupported_vdso: if self.field(FNEasyInstall).toBool() or self.field(FNVDSOSetPerm).toBool(): setvdso = True if self.field(FNUninstall).toBool(): self.setTitle('Uninstalling') self.executor = ExecutorThread( self.wizard().hostInfo, True, False, False, False, False, '', get_default_username(), False, False, '', setvdso, False, '', False, False, False, False) else: self.setTitle('Installing') target_x86_exist = self.field(FNTargetX86Exist).toBool() target_armel_exist = self.field(FNTargetArmelExist).toBool() targets_exist = target_x86_exist or target_armel_exist if self.field(FNEasyInstall).toBool(): remove_targets = True install_sb = True install_sdk = True upgrade_sdk = False install_nokia_bins = True install_apps = True install_xephyr = not os.path.exists("/usr/bin/Xephyr") install_xephyr_shortcut = True create_sbhome_shortcut = True install_desktop_links = True selected_username = get_default_username() install_option = INSTALL_OPTIONS[DEFAULT_INSTALL_OPTION][1] else: remove_targets = self.field(FNRemoveTargets).toBool() install_sb = self.field(FNInstallSB).toBool() install_sdk = self.field(FNInstallSDK).toBool() upgrade_sdk = self.field(FNUpgradeSDK).toBool() install_option = str(self.field(FNSDKInstMOptArg).toString()) #if minimal rootstrap, don't install nokia bins if install_option != INSTALL_OPTIONS[0][1]: install_nokia_bins = self.field(FNInstallNokiaBins).toBool() else: install_nokia_bins = False #nokia apps depend on nokia bins if install_nokia_bins: install_apps = self.field(FNInstallNokiaApps).toBool() else: install_apps = False install_xephyr = self.field(FNInstallXephyr).toBool() install_xephyr_shortcut = self.field(FNInstallXephyrShortcut).toBool() create_sbhome_shortcut = self.field(FNCreateSbHomeShortcut).toBool() install_desktop_links = self.field(FNInstallDesktopLinks).toBool() selected_username = str(self.field(FNSelectedUsername).toString()) if remove_targets: target_prefix = "" else: target_prefix = str(self.field(FNTargetPrefix).toString()) if install_nokia_bins: nokia_bins_repo = str(self.field(FNNokiaBinsRepo).toString()) else: nokia_bins_repo = "" if remove_targets: target_prefix = "" else: target_prefix = str(self.field(FNTargetPrefix).toString()) if install_nokia_bins: nokia_bins_repo = str(self.field(FNNokiaBinsRepo).toString()) else: nokia_bins_repo = "" self.executor = ExecutorThread( self.wizard().hostInfo, False, install_sb, install_sdk, upgrade_sdk, install_apps, install_option, selected_username, targets_exist, targets_exist and remove_targets, target_prefix, setvdso, install_sdk and install_nokia_bins, nokia_bins_repo, install_xephyr, install_xephyr_shortcut, create_sbhome_shortcut, install_desktop_links) self.progress_bar.setRange(0, self.executor.opsTotalNum) self.progress_bar.setValue(0) # kinda redundant self.connect(self.executor, self.executor.sig_op_started, self.executorOpStarted) self.connect(self.executor, self.executor.sig_op_ended, self.executorOpEnded) self.connect(self.executor, QtCore.SIGNAL("finished()"), self.executorFinished) self.executor.start() # have a custom button to show/hide logs self.wizard().setOption(QtGui.QWizard.HaveCustomButton1, True) self.wizard().setButton(QtGui.QWizard.CustomButton1, self.logs_button) def validatePage(self): '''Overrides the method of QWizardPage, to remove the custom button.''' self.wizard().setOption(QtGui.QWizard.HaveCustomButton1, False) # QWizard deletes the old button when setButton is used self.wizard().setButton(QtGui.QWizard.CustomButton1, None) return True def isBusy(self): '''Returns True if installation/removal is in progress''' return self.executor.isRunning() def executorOpStarted(self, msg, op_num): '''Called when the thread has started executing an operation''' self.status_label.setText(msg) def executorOpEnded(self, op_num): '''Called when the thread has ended executing an operation''' self.progress_bar.setValue(op_num) def executorFinished(self): '''Called when executor thread has finished its jobs. This means installation or removal is complete at this point. So this routine reflects that to the UI''' if self.field(FNUninstall).toBool(): self.setTitle('Uninstallation Process Completed') else: self.setTitle('Installation Process Completed') # error if self.executor.exitStatus == ExecutorThread.ExitStatusError: if self.field(FNUninstall).toBool(): self.error_label.setText("Uninstallation was aborted by fatal error.") else: self.error_label.setText("Installation was aborted by fatal error.") # show the failed op the label self.status_label.setText( "Failed: %s" % self.status_label.text()) # abort elif self.executor.exitStatus == ExecutorThread.ExitStatusAborted: if self.field(FNUninstall).toBool(): self.error_label.setText("Uninstallation was aborted by the user.") else: self.error_label.setText("Installation was aborted by the user.") self.status_label.setText("Aborted") # ok elif self.executor.exitStatus == ExecutorThread.ExitStatusOK: if self.field(FNUninstall).toBool(): self.error_label.setText("Uninstallation completed successfully.") else: self.error_label.setText("Installation completed successfully.") self.status_label.setText("Done") else: assert False, "Illegal thread exit status (%s)!" % \ (self.executor.exitStatus) # enable the Next/Finish button self.emit(QtCore.SIGNAL("completeChanged()")) # Finish button will replace Next button if installation failed # setFinalPage works with Qt versions 4.5 and higher, see the bug: # http://www.qtsoftware.com/developer/task-tracker/index_html?method=entry&id=222140 if self.isLastPage(): if QtCore.QT_VERSION >= 0x040500: self.setFinalPage(True) else: finish_btn = self.wizard().button(QtGui.QWizard.FinishButton) finish_btn.setVisible(True) finish_btn.setEnabled(True) finish_btn.setDefault(True) next_btn = self.wizard().button(QtGui.QWizard.NextButton) next_btn.setVisible(False) # from this page on there is nothing to cancel, so disabled self.wizard().button(QtGui.QWizard.CancelButton).setEnabled(False) QtGui.QApplication.restoreOverrideCursor() def isLastPage(self): '''Returns True if this page is the last one to show (in which case Finish button will replace the Next button). This page will be the last if installation fails.''' return self.executor.exitStatus in \ [ExecutorThread.ExitStatusAborted, ExecutorThread.ExitStatusError] def nextId(self): '''Overrides the method of QWizardPage, not to show last page in case installation fails.''' # installation failed if self.isLastPage(): return -1 # installation succeeded else: return QtGui.QWizardPage.nextId(self) def isComplete(self): '''Overrides the method of QWizardPage, to disable next button when installation/removal is in progress.''' if self.isBusy(): return False else: return True def showAbortMsgBox(self): '''Shows abort message box. Returns tuple of buttons texts.''' msg_box = QtGui.QMessageBox( QtGui.QMessageBox.Question, "Really abort?", "Installation processes are still running!") # have not chosen to abort already in the past and not running last task have_abort_later_btn = not self.executor.isAborting() and \ not self.executor.isRunningLastTask() abort_now_btn_txt = "Abort now" abort_later_btn_txt = "Abort later" abort_now_info_text = ( "If you choose to '%s' all of the installation processes will be " "killed. NOTE! This could potentially make your system " "unstable. So use it at your own risk." % abort_now_btn_txt) if have_abort_later_btn: abort_later_info_text = ( "You can abort safely by choosing '%s', in which case " "currently running installation process will be allowed to " "complete its execution." % (abort_later_btn_txt)) # don't have the abort later button else: if self.executor.isRunningLastTask(): abort_later_info_text = ( "It appears that the last installation process is running. " "Hence, installation should be over any minute now! It is " "highly recommended to wait for the completion of " "installation instead of aborting.") else: abort_later_info_text = ( "You have previously selected '%s', so installation will " "be aborted after currently running installation process " "completes its execution." % (abort_later_btn_txt)) info_txt = (abort_later_info_text + "

" + abort_now_info_text) msg_box.setInformativeText(info_txt) cancel_btn = msg_box.addButton(QtGui.QMessageBox.Cancel) abort_now_btn = msg_box.addButton(abort_now_btn_txt, QtGui.QMessageBox.DestructiveRole) if have_abort_later_btn: abort_later_btn = msg_box.addButton(abort_later_btn_txt, QtGui.QMessageBox.AcceptRole) msg_box.setDefaultButton(cancel_btn) msg_box.exec_() clicked_btn = msg_box.clickedButton() LOG("Answer to abort dialog: %s" % clicked_btn.text()) # msg_box & clicked_btn will not exist after this function returns return (clicked_btn.text(), abort_now_btn_txt, abort_later_btn_txt, cancel_btn.text()) def onCancel(self): '''Called if user tries to close or cancel the wizard, shows the abort dialog and takes respective actions.''' (clicked_btn_txt, abort_now_btn_txt, abort_later_btn_txt, cancel_btn_txt) = self.showAbortMsgBox() # cannot abort if installation has already ended if clicked_btn_txt != cancel_btn_txt: if not self.isBusy(): QtGui.QMessageBox.information( self, "Sorry!", "Cannot '%s' since the installation has ended!" % \ (clicked_btn_txt), QtGui.QMessageBox.Ok) return # abort later if clicked_btn_txt == abort_later_btn_txt: if self.executor.isRunningLastTask(): QtGui.QMessageBox.information( self, "Sorry!", "Cannot '%s' since the last installation process is "\ "running!" % abort_later_btn_txt, QtGui.QMessageBox.Ok) else: self.executor.abort() # abort now: kill self & children (running installers) too elif clicked_btn_txt == abort_now_btn_txt: LOG("Committing suicide...") os.killpg(os.getpid(), signal.SIGTERM) def showEvent(self, event): '''Overriden method of QWidget. Scrolls the slider of the vertical scroll bar of the text edit to the end. This is done to enable automatic scrolling of the scrollbar upon addition of the new text. Done only once, when the page is shown for the first time.''' QtGui.QWizardPage.showEvent(self, event) if not self.scrolled_to_vend: self.logsTextEditScrollToVend() self.scrolled_to_vend = True def logsButtonToggled(self, checked): '''Slot for the toggled signal of the logs button. Hides/shows logs widgets.''' self.logs_text_edit.setVisible(checked) self.logs_url_label.setVisible(checked) if checked: # when text edit is visible spacer is removed, otherwise spacer # would visibly consume space if page is resized self.layout().removeItem(self.spacer) else: # when text edit is hidden the spacer is used to take its space to # prevent other widget from moving in the layout self.layout().insertItem(2, self.spacer) def logsTextEditScrollToVend(self): '''Scrolls vertical scroll bar of the text edit widget to the end''' vsb = self.logs_text_edit.verticalScrollBar() vsb.setValue(vsb.maximum()) def logsTextEditRemoveLastLine(self): '''Removes last line from the text edit''' cursor = self.logs_text_edit.textCursor() cursor.movePosition(QtGui.QTextCursor.End) cursor.select(QtGui.QTextCursor.BlockUnderCursor) cursor.deletePreviousChar() def splitStringWithMultiCR(self, txt): '''Splits a string containing multiple lines of text into list a of single-line strings. Character \r is assumed to mark the beginning of a line, whereas character \n is assumed to mark the end of a line. This routine is used to emulate terminal carriage return (\r) handling. returns a list of strings txt = a string containing multiple lines of text (including \n, \r etc) ''' txt_list = [] i_begin = 0 # index, where the next line should begin from for i in xrange(0, len(txt)): # \r is assumed to mark the beginning of a new line if txt[i] == '\r': # there could be \r just after line ending with \n or the whole # text could begin with \r: we don't want empty string in those # cases if i - i_begin > 0: txt_list.append(txt[i_begin:i]) i_begin = i # \n is assumed to mark the end of a current line, next char after # newline will be the start of the next line elif txt[i] == '\n': txt_list.append(txt[i_begin:i + 1]) i_begin = i + 1 # if text does not end with \n, get the last line if i_begin < len(txt): txt_list.append(txt[i_begin:len(txt)]) return txt_list def logsTextEditAppend(self, txt): '''Appends text into the text edit widget. If there are carriage return characters in the text does some processing in order to emulate terminal behavior. txt = a string containing multiple lines of text (including \n, \r etc) ''' vsb = self.logs_text_edit.verticalScrollBar() at_bottom = vsb.value() == vsb.maximum() # \r\n is just newline (apt uses it while selecting/unpacking) txt = txt.replace("\r\n", "\n") # number of carriage return characters in the text cr_count = txt.count('\r') # text without carriage return is just inserted if cr_count == 0: self.cursor.insertText(txt) # text has only one carriage return in the beginning, previous line # must be removed before the text is inserted elif cr_count == 1 and txt.startswith('\r'): self.logsTextEditRemoveLastLine() self.cursor.insertText(txt) # text has multiple carriage return characters else: txt_list = self.splitStringWithMultiCR(txt) for line in txt_list: if line.startswith('\r'): self.logsTextEditRemoveLastLine() self.cursor.insertText(line) # automatically scroll, if slider was at the end of scrollbar if at_bottom: self.logsTextEditScrollToVend() def tailProcessReadStdout(self): '''A slot that is called when the tail process has some text on its stdout. Appends that text to the text edit.''' txt = str(self.wizard().tail_process.readAllStandardOutput()) self.logsTextEditAppend(txt) class ConclusionPage(QtGui.QWizardPage): '''ConclusionPage page''' def __init__(self): '''Constructor''' QtGui.QWizardPage.__init__(self) def initializePage(self): '''Overrides the method of QWizardPage, to initialize the page with some default settings.''' self.setSubTitle(" ") if self.field(FNUninstall).toBool(): self.setTitle('Uninstallation of %s Completed' % PRODUCT_NAME) layout = QtGui.QHBoxLayout() label = QtGui.QLabel("Scratchbox successfully uninstalled from system.\n\n" "If you did any changes to your scratchbox home directory, " "it is still available in system root, along with desktop " "shortcut (if you created one when installing). You can later " "re-install scratchbox over the old directory") label.setWordWrap(True) layout.addWidget(label) else: self.setTitle('Installation of %s Completed' % PRODUCT_NAME) layout = QtGui.QGridLayout() info_text_edit = QtGui.QTextEdit() info_text_edit.setReadOnly(True) instruction_widget_layout = QtGui.QVBoxLayout() instruction_title = QtGui.QLabel("Starting SDK UI") layout.addWidget(instruction_title, 0, 0) info_text_edit.setPlainText("1. Start the Xephyr xserver and Maemo desktop by clicking icon on the Desktop.\n\n" "2. Log out and log back in OR execute the following command to " "run scratchbox in the current terminal session.\n" "$ newgrp %s\n\n" "3. Login to scratchbox.\n" "$ %s/login\n\n" "Welcome to Scratchbox, the cross-compilation toolkit!\n" "Use 'sb-menu' to change your compilation target.\n" "See /scratchbox/doc/ for documentation.\n" % (SB_GROUP, SB_PATH)) info_text_edit.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred); layout.addWidget(info_text_edit, 1, 0) link_title = QtGui.QLabel("Useful links") layout.addWidget(link_title, 0, 1) link_str = ('Get Started

' 'Maemo 5 Developer Guide

' 'Hildon 2.2 UI Style

' 'Hildon 2.2 Widget UI Specification

' 'Redesigning from Maemo 4 to Maemo 5

' 'Example code

' 'API References



') self.link_label = QtGui.QLabel() self.link_label.setText(link_str) self.link_label.setAlignment(QtCore.Qt.AlignTop) self.connect(self.link_label, QtCore.SIGNAL("linkActivated(QString)"), LoadUrl) layout.addWidget(self.link_label, 1, 1) self.setLayout(layout) PageIdProxy = 0 PageIdIntro = 1 PageIdLevel = 2 PageIdLicense = 3 PageId64Bit = 4 PageIdUsers = 5 PageIdInstallOpts = 6 PageIdVDSO = 7 PageIdTargets = 8 PageIdPkg = 9 PageIdNokiaBins = 10 PageIdSummary = 11 PageIdProgress = 12 PageIdConclusion = 13 class InstallWizard(QtGui.QWizard): '''Installation wizard''' def __init__(self): '''Constructor''' # create own group so can kill (Abort Now) self & children during # installation: this is redundant in shells but needed if script is # launched from desktop e.g. by using kdesudo global LOG os.setpgrp() QtGui.QWizard.__init__(self) self.setWindowTitle(MY_NAME) self.__host_info = HostInfo() set_proxy(None) # process that tails the log file self.tail_process = QtCore.QProcess(self) self.tail_process.start("tail -f " + LOG.fn_log) self.setOption(QtGui.QWizard.DisabledBackButtonOnLastPage) self.imageHandler = ImageHandler(IMAGES) #Check system proxy settings for param in os.environ.keys(): for key in ['http_proxy', 'HTTP_PROXY']: if param == key: set_proxy(os.environ[param]) break if not self.imageHandler.downloadImage(random.randint(0, len(IMAGES)-1)): self.setPage(PageIdProxy, ProxyPage()) self.setPage(PageIdIntro, IntroPage()) self.setPage(PageIdLevel, LevelPage(self.__host_info.has_scratchbox, self.__host_info.has_apt_get)) self.setPage(PageIdLicense, LicensePage(self.__host_info.has_64bit)) self.setPage(PageId64Bit, X86_64Page()) self.setPage(PageIdUsers, UsersPage()) self.setPage(PageIdInstallOpts, InstallOptsPage(self.__host_info.has_scratchbox, self.__host_info.scratchbox_op_name, self.__host_info.has_xephyr, self.__host_info.has_apt_get or self.__host_info.has_yum, self.__host_info.has_unsupported_vdso)) if self.__host_info.has_64bit: self.setPage(PageIdVDSO, VDSO64BitPage()) else: self.setPage(PageIdVDSO, VDSOPage()) self.setPage(PageIdTargets, TargetsPage()) self.setPage(PageIdPkg, PkgPage()) if HAVE_WEBKIT: self.setPage(PageIdNokiaBins, NokiaBinsPage()) else: self.setPage(PageIdNokiaBins, NokiaBinsPageNoWebkit()) self.setPage(PageIdSummary, SummaryPage()) self.setPage(PageIdProgress, ProgressPage(self.tail_process)) self.setPage(PageIdConclusion, ConclusionPage()) hostInfo = property(lambda self: self.__host_info) def __del__(self): '''destructor''' self.tail_process.kill() def reject(self): '''Overridden method of QDialog to disable wizard closing when installation/removal is in progress. Handles closing wizard by: - pressing Esc button - clicking Cancel button - clicking X button on the title bar of the window - any shortcut that can be used to close a window - probably any other means used to close a window''' if self.currentId() == PageIdProgress and self.currentPage().isBusy(): self.currentPage().onCancel() return # default behavior in other cases QtGui.QWizard.reject(self) def disabled_nextId(self): '''Returns ID of page to show when the user clicks the Next button. After the Users page if SDK not to be installed, returns the summary page. Targets page is not shown if targets don't exist for the selected user. ''' if QtGui.QWizard.nextId(self) == PageIdTargets: if self.field(FNEasyInstall).toBool(): return QtGui.QWizard.nextId(self) else: install_sdk = self.field(FNInstallSDK).toBool() if not install_sdk: return PageIdSummary else: # sdk will be installed target_x86_exist = self.field(FNTargetX86Exist).toBool() target_armel_exist = self.field(FNTargetArmelExist).toBool() if not target_x86_exist and not target_armel_exist: return PageIdTargets + 1 # next page if QtGui.QWizard.nextId(self) == PageIdNokiaBins: if self.field(FNSDKInstMOptArg).toString() == INSTALL_OPTIONS[0][1]: return PageIdNokiaBins + 1 return QtGui.QWizard.nextId(self) def run_checks(): '''Performs some system checks, raises exception if some check fails''' # this script must run with root privileges: installation of SDK only could # be done without root priveleges, but the SDK installer requires VDSO to # be set to scratchbox compatible value, which can only be done as root if os.geteuid() != 0: raise Exception("Root access is needed! Please run this application " "with root privileges!") # only 64 and 32 bit X86 systems are supported sup_machines = ["i386", "i686", "x86_64"] machine = os.uname()[4] if machine not in sup_machines: raise Exception("Operating system's machine or word size '%s' is not " "supported. Only 32- and 64bit X86 systems are supported %s." % (machine, sup_machines)) def main(): '''Main.''' app = QtGui.QApplication([]) parse_options() try: run_checks() except Exception, e: QtGui.QMessageBox.critical(None, MY_NAME, str(e), QtGui.QMessageBox.Ok) sys.exit(1) random.seed() global LOG LOG = Logger() # must have root acces to re-write previous log file socket.setdefaulttimeout(30) wizard = InstallWizard() wizard.setWizardStyle(QtGui.QWizard.ModernStyle) wizard.resize(WINDOW_WIDTH, WINDOW_HEIGHT) wizard.show() #Older PyQt versions don't seem to set window size before it's opened wizard.resize(WINDOW_WIDTH, WINDOW_HEIGHT) return app.exec_() if __name__ == "__main__": main()