#! /usr/bin/python3 # -*- coding:utf-8 -*- # # Copyright 2012-2013 "Korora Project" # Copyright 2013 "Manjaro Linux" # Copyright 2014 Antergos # Copyright 2015-2016 Martin Wimpress # Copyright 2015-2016 Luke Horwell # # Ubuntu MATE Welcome is free software: you can redistribute it and/or modify # it under the temms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ubuntu MATE Welcome 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 Ubuntu MATE Welcome. If not, see . # """ Welcome screen for Ubuntu MATE """ import gi gi.require_version("Gdk", "3.0") gi.require_version("Gtk", "3.0") gi.require_version("Notify", "0.7") gi.require_version("WebKit", "3.0") import apt import errno import gettext import glob import inspect import json import locale import logging import os import platform import random import shutil import signal import socket import subprocess import sys import tempfile import urllib.request import webbrowser import urllib.error import urllib.parse import urllib.request from aptdaemon.client import AptClient from aptdaemon.gtk3widgets import AptErrorDialog, AptConfirmDialog, \ AptProgressDialog import aptdaemon.errors from aptdaemon.enums import * from gi.repository import GLib, Gio, GObject, Gdk, Gtk, Notify, WebKit from ctypes import cdll, byref, create_string_buffer from threading import Thread from getpass import getuser # i18n - if no translation is available use the inline strings t = gettext.translation("ubuntu-mate-welcome", "./locale", fallback=True) _ = t.gettext def goodbye(a=None, b=None): # NOTE: _a_ and _b_ are passed via the close window 'delete-event'. ''' Closing the program ''' # Refuse to quit if operations are in progress. if dynamicapps.operations_busy: print('[Welcome] WARNING: Software changes are in progress!') title = _("Software Boutique") text_busy = _('Software changes are in progress. Please allow them to complete before closing Welcome.') ok_label = _("OK") messagebox = subprocess.Popen(['zenity', '--error', '--title=' + title, "--text=" + text_busy, "--ok-label=" + ok_label, '--window-icon=error', '--timeout=9']) return 1 else: print('[Welcome] Application Closed.') Gtk.main_quit() # Be quite forceful, particularly those child screenshot windows. exit() def set_proc_title(name=None): '''Set the process title''' if not name: name = os.path.basename(sys.argv[0]) libc = cdll.LoadLibrary('libc.so.6') buff = create_string_buffer(len(name)+1) buff.value = name.encode("UTF-8") ret = libc.prctl(15, byref(buff), 0, 0, 0) if ret != 0: print("Failed to set process title") return ret class SimpleApt(object): def __init__(self, packages, action): self._timeout = 100 self.packages = packages self.action = action self.source_to_update = None self.update_cache = False self.loop = GLib.MainLoop() self.client = AptClient() def on_error(self, error): dynamicapps.operations_busy = False if isinstance(error, aptdaemon.errors.NotAuthorizedError): # Silently ignore auth failures return elif not isinstance(error, aptdaemon.errors.TransactionFailed): # Catch internal errors of the client error = aptdaemon.errors.TransactionFailed(ERROR_UNKNOWN, str(error)) error_dialog = AptErrorDialog(error) error_dialog.run() error_dialog.hide() def on_finished_fix_incomplete_install(self, transaction, status): dynamicapps.operations_busy = False self.loop.quit() if status == 'exit-success': Notify.init(_('Fixing incomplete install succeeded')) apt_notify=Notify.Notification.new(_('Successfully fixed an incomplete install.'), _('Fixing the incomplete install was successful.'), 'dialog-information') apt_notify.show() return True else: Notify.init(_('Fixing incomplete install failed')) apt_notify=Notify.Notification.new(_('Failed to fix incomplete install.'), _('Fixing the incomplete install failed.'), 'dialog-error') apt_notify.show() return False def on_finished_fix_broken_depends(self, transaction, status): dynamicapps.operations_busy = False self.loop.quit() if status == 'exit-success': Notify.init(_('Fixing broken dependencies succeeded')) apt_notify=Notify.Notification.new(_('Successfully fixed broken dependencies.'), _('Fixing the broken dependencies was successful.'), 'dialog-information') apt_notify.show() return True else: Notify.init(_('Fixing broken dependencies failed')) apt_notify=Notify.Notification.new(_('Failed to fix broken dependencies.'), _('Fixing the broken dependencies failed.'), 'dialog-error') apt_notify.show() return False def on_finished_update(self, transaction, status): dynamicapps.operations_busy = False # If the action is only to update do not display notifcations if self.action == 'update': self.loop.quit() if status == 'exit-success': return True else: return False elif self.action == 'install': if status != 'exit-success': self.do_notify(status) self.loop.quit() return False GLib.timeout_add(self._timeout,self.do_install) return True elif self.action == 'upgrade': if status != 'exit-success': self.do_notify(status) self.loop.quit() return False GLib.timeout_add(self._timeout,self.do_upgrade) return True def on_finished_install(self, transaction, status): dynamicapps.operations_busy = False self.loop.quit() if status != 'exit-success': return False else: self.do_notify(status) def on_finished_remove(self, transaction, status): dynamicapps.operations_busy = False self.loop.quit() if status != 'exit-success': return False else: self.do_notify(status) def on_finished_upgrade(self, transaction, status): dynamicapps.operations_busy = False self.loop.quit() if status != 'exit-success': return False else: self.do_notify(status) def do_notify(self, status): print('Status: ' + status) if self.action == 'install': title = _('Install') noun = _('Installation of ') action = _('installed.') elif self.action == 'remove': title = _('Remove') noun = _('Removal of ') action = _('removed.') elif self.action == 'upgrade': title = _('Upgrade') noun = _('Upgrade of ') action = _('upgraded.') # Do not show notifications when updating the cache if self.action != 'update': if status == 'exit-success': Notify.init(title + ' ' + _('complete')) apt_notify=Notify.Notification.new(title + ' ' + _('complete'), ', '.join(self.packages) + ' ' + _('has been successfully ') +action, 'dialog-information') elif status == 'exit-cancelled': Notify.init(title + ' ' + _('cancelled')) apt_notify=Notify.Notification.new(title + ' ' + _('cancelled'), noun + ', '.join(self.packages) + ' ' + _('was cancelled.'), 'dialog-information') else: Notify.init(title + ' ' + _('failed')) apt_notify=Notify.Notification.new(title + ' ' + _('failed'), noun + ', '.join(self.packages) + ' ' + _('failed.'), 'dialog-error') apt_notify.show() def do_fix_incomplete_install(self): dynamicapps.operations_busy = True # Corresponds to: dpkg --configure -a apt_fix_incomplete = self.client.fix_incomplete_install() apt_fix_incomplete.connect("finished",self.on_finished_fix_incomplete_install) fix_incomplete_dialog = AptProgressDialog(apt_fix_incomplete) fix_incomplete_dialog.run(close_on_finished=True, show_error=True, reply_handler=lambda: True, error_handler=self.on_error, ) return False dynamicapps.operations_busy = False def do_fix_broken_depends(self): dynamicapps.operations_busy = True # Corresponds to: apt-get --fix-broken install apt_fix_broken = self.client.fix_broken_depends() apt_fix_broken.connect("finished",self.on_finished_fix_broken_depends) fix_broken_dialog = AptProgressDialog(apt_fix_broken) fix_broken_dialog.run(close_on_finished=True, show_error=True, reply_handler=lambda: True, error_handler=self.on_error, ) return False dynamicapps.operations_busy = False def do_update(self): if self.source_to_update: apt_update = self.client.update_cache(self.source_to_update) else: apt_update = self.client.update_cache() apt_update.connect("finished",self.on_finished_update) update_dialog = AptProgressDialog(apt_update) update_dialog.run(close_on_finished=True, show_error=True, reply_handler=lambda: True, error_handler=self.on_error, ) return False def do_install(self): apt_install = self.client.install_packages(self.packages) apt_install.connect("finished", self.on_finished_install) install_dialog = AptProgressDialog(apt_install) install_dialog.run(close_on_finished=True, show_error=True, reply_handler=lambda: True, error_handler=self.on_error, ) return False def do_remove(self): apt_remove = self.client.remove_packages(self.packages) apt_remove.connect("finished", self.on_finished_remove) remove_dialog = AptProgressDialog(apt_remove) remove_dialog.run(close_on_finished=True, show_error=True, reply_handler=lambda: True, error_handler=self.on_error, ) return False def do_upgrade(self): apt_upgrade = self.client.upgrade_system(True) apt_upgrade.connect("finished", self.on_finished_upgrade) upgrade_dialog = AptProgressDialog(apt_upgrade) upgrade_dialog.run(close_on_finished=True, show_error=True, reply_handler=lambda: True, error_handler=self.on_error, ) return False def install_packages(self): dynamicapps.operations_busy = True if self.update_cache: GLib.timeout_add(self._timeout,self.do_update) else: GLib.timeout_add(self._timeout,self.do_install) self.loop.run() dynamicapps.operations_busy = False def remove_packages(self): dynamicapps.operations_busy = True GLib.timeout_add(self._timeout,self.do_remove) self.loop.run() dynamicapps.operations_busy = False def upgrade_packages(self): dynamicapps.operations_busy = True if self.update_cache: GLib.timeout_add(self._timeout,self.do_update) else: GLib.timeout_add(self._timeout,self.do_upgrade) self.loop.run() dynamicapps.operations_busy = False def fix_incomplete_install(self): dynamicapps.operations_busy = True GLib.timeout_add(self._timeout,self.do_fix_incomplete_install) self.loop.run() dynamicapps.operations_busy = False def fix_broken_depends(self): dynamicapps.operations_busy = True GLib.timeout_add(self._timeout,self.do_fix_broken_depends) self.loop.run() dynamicapps.operations_busy = False def update_repos(): transaction = SimpleApt('', 'update') transaction.update_cache = True transaction.do_update() def fix_incomplete_install(): transaction = SimpleApt('', 'fix-incomplete-install') transaction.fix_incomplete_install() def fix_broken_depends(): transaction = SimpleApt('', 'fix-broken-depends') transaction.fix_broken_depends() def mkdir_p(path): try: os.makedirs(path) except OSError as exc: # Python >2.5 if exc.errno == errno.EEXIST and os.path.isdir(path): pass else: raise def get_aacs_db(): home_dir = GLib.get_home_dir() key_url = 'http://www.labdv.com/aacs/KEYDB.cfg' key_db = os.path.join(home_dir, '.config', 'aacs', 'KEYDB.cfg') mkdir_p(os.path.join(home_dir, '.config', 'aacs')) print('[AACS] Getting ' + key_url + ' and saving as ' + key_db) # Download the file from `key_url` and save it locally under `file_name`: try: with urllib.request.urlopen(key_url) as response, open(key_db, 'wb') as out_file: data = response.read() # a `bytes` object out_file.write(data) Notify.init(_('Blu-ray AACS database install succeeded')) aacs_notify=Notify.Notification.new(_('Successfully installed the Blu-ray AACS database.'), _('Installation of the Blu-ray AACS database was successful.'), 'dialog-information') aacs_notify.show() except: Notify.init(_('Blu-ray AACS database install failed')) aacs_notify=Notify.Notification.new(_('Failed to install the Blu-ray AACS database.'), _('Installation of the Blu-ray AACS database failed.'), 'dialog-error') aacs_notify.show() class PreInstallation(object): # # See the JSON Structure in the `DynamicApps` class on # how to specify pre-configuration actions in `applications.json` # def __init__(self): # Always ensure we have the correct variables. self.os_version = platform.dist()[1] self.codename = platform.dist()[2] arg.print_verbose("Pre-Install", "System is running Ubuntu " + self.os_version + " (" + self.codename + ")") def process_packages(self, program_id, action): simulating = arg.simulate_software_changes print(' ') # Get category for this program, which can be used to retrieve data later. category = dynamicapps.get_attribute_for_app(program_id, 'category') try: preconfig = dynamicapps.index[category][program_id]['pre-install'] except: print('[Pre-Install] Missing pre-configuration data for "' + program_id + '". Refusing to continue.') return try: if action == 'install': packages = dynamicapps.index[category][program_id]['install-packages'] print('[Apps] Packages to be installed:\n ' + packages) elif action == 'remove': packages = dynamicapps.index[category][program_id]['remove-packages'] print('[Apps] Packages to be removed:\n ' + packages) elif action == 'upgrade': packages = dynamicapps.index[category][program_id]['upgrade-packages'] print('[Apps] Packages to be upgraded:\n ' + packages) else: print('[Apps] ERROR: Invalid action was requested.') return except: print('[Apps] ERROR: No packages retrieved for requested action.') return # Validate that we have packages to work with. if len(packages): packages = packages.split(',') else: print('[Apps] ERROR: No package(s) supplied for "' + program_id + '".') return transaction = SimpleApt(packages, action) # Function to run privileged commands. def run_task(function): subprocess.call(['pkexec', '/usr/lib/ubuntu-mate/ubuntu-mate-welcome-repository-installer', os.path.abspath(os.path.join(app._data_path, 'js/applications.json')), function, category, program_id, target]) # Determine if any pre-configuration is specific to a codename. try: preinstall = dynamicapps.index[category][program_id]['pre-install'] codenames = list(preinstall.keys()) except: print('[Pre-Install] No pre-install data specified for "' + program_id + '". This application entry is invalid.') return arg.print_verbose('Pre-Install','Available configurations: ' + str(codenames)) target = None for name in codenames: if name == self.codename: target = name break if not target: target = 'all' arg.print_verbose('Pre-Install','Using "all" pre-configuration.') else: arg.print_verbose('Pre-Install','Using configuration for: "' + target + '".') methods = preinstall[target]['method'].split('+') if not methods: print('[Pre-Install] No pre-install method was specified. The index is invalid.') else: arg.print_verbose('Pre-Install','Configuration changes: ' + str(methods)) # Perform any pre-configuration, if necessary. if action == 'install' or action == 'upgrade': for method in methods: if method == 'skip': arg.print_verbose('Pre-Install','No need! The package is already in the archives.') continue elif method == 'partner-repo': print('[Pre-Install] Enabling the Ubuntu partner repository.') if not simulating: run_task('enable_partner_repository') transaction.update_cache = True elif method == 'ppa': try: ppa = preinstall[target]['enable-ppa'] except: print('[Pre-Install] Missing "enable-ppa" attribute. Cannot add PPA as requested.') return print('[Pre-Install] Adding PPA: "' + ppa + '" and updating cache.') if not simulating: run_task('enable_ppa') transaction.update_cache = True try: source_file = preinstall[target]['source-file'].replace('OSVERSION',self.os_version).replace('CODENAME',self.codename) print('[Pre-Install] Updating Apt Source: "' + source_file + '.list"') if not simulating: transaction.source_to_update = source_file + '.list' except: arg.print_verbose('Pre-Install','Updating entire cache as no source file was specified.') elif method == 'manual': # Do we get the apt key from a URL? try: apt_key_url = preinstall[target]['apt-key-url'] print('[Pre-Install] Getting Apt key from URL: "' + apt_key_url + '"') if not simulating: run_task('add_apt_key_from_url') except: arg.print_verbose('Pre-Install', 'No apt key to retrieve from a URL.') # Do we get the apt key from the server? try: apt_key_server = preinstall[target]['apt-key-server'][0] apt_key_key = preinstall[target]['apt-key-server'][1] print('[Pre-Install] Getting key "' + apt_key_key + '" from keyserver: "' + apt_key_server + '"') if not simulating: run_task('add_apt_key_from_keyserver') except: arg.print_verbose('Pre-Install', 'No apt key to retrieve from a key server.') # Do we need to add an apt source file? try: source = preinstall[target]['apt-sources'] source_file = preinstall[target]['source-file'].replace('OSVERSION',self.os_version).replace('CODENAME',self.codename) print('[Pre-Install] Writing source file: ' + source_file + '.list') print(' -------- Start of file ------') for line in source: print(' ' + line.replace('OSVERSION',self.os_version).replace('CODENAME',self.codename)) print(' -------- End of file ------') try: print('[Pre-Install] Updating Apt Source: ' + source_file + '.list') if not simulating: run_task('add_apt_sources') transaction.source_to_update = source_file + '.list' transaction.update_cache = True except: arg.print_verbose('Pre-Install','Failed to add apt sources!') except: arg.print_verbose('Pre-Install','No source data or source file to write.') elif action == 'remove': try: # The function uses wild cards, so we don't need to worry about being explict. listname = preinstall[target]['source-file'].replace('CODENAME','').replace('OSVERSION','') if simulating: print('[Simulation] Deleting Apt Source: ' + listname) else: run_task('del_apt_sources') except: print('[Pre-Install]', 'No apt source specified, so none will be removed.') # Pre-configuration complete. Now perform the operations, unless this was just a simulation. if simulating: print('[Pre-Install] Simulation flag active. No changes will be performed.') return else: if transaction.action == 'install': transaction.install_packages() elif transaction.action == 'remove': transaction.remove_packages() elif transaction.action == 'upgrade': transaction.upgrade_packages() class WelcomeConfig(object): """ Manages Welcome configuration """ def __init__(self): # store our base architecture self.os_version = platform.dist()[1] self.os_codename = platform.dist()[2] self.os_title = 'Ubuntu MATE ' + self.os_version self._arch = systemstate.arch # store full path to our binary self._welcome_bin_path = os.path.abspath(inspect.getfile(inspect.currentframe())) # directory for the configuration self._config_dir = os.path.expanduser('~/.config/ubuntu-mate/welcome/') # autostart directory self._autostart_dir = os.path.expanduser('~/.config/autostart/') # full path to the autostart symlink self._autostart_path = os.path.expanduser(os.path.join(self._autostart_dir, 'ubuntu-mate-welcome.desktop')) # ensure our config and autostart directories exists for _dir in [self._config_dir, self._autostart_dir]: if not os.path.exists(_dir): try: os.makedirs(_dir) except OSError as err: print(err) pass # does autostart symlink exist self._autostart = os.path.exists(self._autostart_path) @property def autostart(self): return self._autostart @autostart.setter def autostart(self, state): if state and not os.path.exists(self._autostart_path): # create the autostart symlink try: os.symlink('/usr/share/applications/ubuntu-mate-welcome.desktop', self._autostart_path) except OSError as err: print(err) pass elif not state and os.path.exists(self._autostart_path): # remove the autostart symlink try: os.unlink(self._autostart_path) except OSError as err: print(err) pass # determine autostart state based on absence of the disable file self._autostart = os.path.exists(self._autostart_path) class AppView(WebKit.WebView): def __init__(self, slide_list = None): """ Args: slide_list : A list of tuples containing the filenames of translated html files or, if no translation is available, the filename of the original untranslated slide """ WebKit.WebView.__init__(self) WebKit.WebView.__init__(self) self._config = WelcomeConfig() self._apt_cache = apt.Cache() self.connect('load-finished', self._load_finished_cb) self.connect('navigation-policy-decision-requested', self._nav_request_policy_decision_cb) self.l_uri = None self._slide_list = slide_list self.set_zoom_level(systemstate.zoom_level) print('[Welcome] Setting zoom level to: ' + str(systemstate.zoom_level)) # Disable right-click context menu as it isn't needed. self.props.settings.props.enable_default_context_menu = False # Perform a smooth transition for footer icons. self.do_smooth_footer = False def _push_config(self): ### Global - On all pages ### self.execute_script("$('#os_title').html('%s')" % self._config.os_title) self.execute_script("$('#os_version').html('%s')" % self._config.os_version) self.execute_script("$('#autostart').toggleClass('fa-check-square', %s).toggleClass('fa-square', %s)" % (json.dumps(self._config.autostart), json.dumps(not self._config.autostart))) # If this is a Live session (booted from ISO) show the # 'Install OS' button, if running on an installed system show # the 'Install Software' button. if systemstate.session_type == 'live': self.execute_script("$('#install').show();") self.execute_script("$('#software').hide();") self.execute_script("$('.live-session').hide();") self.execute_script("$('.live-session-only').show();") else: self.execute_script("$('#install').hide();") self.execute_script("$('#software').show();") self.execute_script("$('.live-session').show();") self.execute_script("$('.live-session-only').hide();") # If started from a Raspberry Pi. if systemstate.session_type == 'pi': self.execute_script("$('.rpi-only').show();") else: self.execute_script("$('.rpi-only').hide();") # Display warnings if the user is not connected to the internet. if systemstate.is_online: self.execute_script("$('.offline').hide();") self.execute_script("$('.online').show();") else: self.execute_script("$('.offline').show();") self.execute_script("$('.online').hide();") ## Social Links ## footer_left = '
\ \ \ \
' ## Boutique Footer ## str_subscribed = _("Set to retrieve the latest software listings.") str_subscribe_link = _("Retrieve the latest software listings.") str_subscribing = _("Please wait while the application is being updated...") str_listing_version = _("Version:") boutique_footer = '' # Do not show footer links on splash or software page. if not arg.jump_software_page: if not self.current_page == 'splash.html' and not self.current_page == 'software.html': self.execute_script("$('#footer-global-left').html('" + footer_left + "');") # Show the button depending on context. footer_close = '' + _("Close") + '‌' footer_skip = '' + _("Skip") + '' if self.current_page == 'splash.html': self.execute_script("$('#footer-global-right').html('" + footer_skip + "');") elif arg.jump_software_page: # Do not show a "Close" button for the Boutique. pass else: self.execute_script("$('#footer-global-right').html('" + footer_close + "');") # Smoothly fade in the footer links between pages. # splash → index # index ← → software if self.do_smooth_footer or self.current_page == 'software.html': self.do_smooth_footer = False self.execute_script("$('#footer-left').hide();") self.execute_script("$('#footer-left').fadeIn();") # Individual Page Actions ### Main Menu ### if self.current_page == 'index.html': if systemstate.session_type == 'guest': # Disable features that are unavailable to guests. self.execute_script("$('#gettingstarted').hide();") self.execute_script("$('#software').hide();") self.execute_script("$('#introduction').addClass('btn-success');") self.execute_script("$('#community').addClass('btn-success');") # Check whether the system is subscribed for receiving more up-to-date versions of Welcome. self.execute_script('$("#update-subscribing").hide()') if not systemstate.updates_subscribed: if systemstate.is_online: self.execute_script('$("#update-notification").fadeIn("slow")') else: self.execute_script('$("#update-notification").hide()') # Disable confetti on machines that may suffer performance issues. if systemstate.arch == 'armhf': self.execute_script('var disable_confetti = true;') elif systemstate.arch == 'powerpc': self.execute_script('var disable_confetti = true;') else: self.execute_script('var disable_confetti = false;') # Special event strings. self.execute_script('var days_in = "‌in‌"') self.execute_script('var future_days = "‌days.‌"') self.execute_script('var days_ago = "‌days ago.‌"') self.execute_script('var yesterday = "‌yesterday.‌"') self.execute_script('var tomorrow = "‌tomorrow.‌"') self.execute_script('var years_ago = "‌years ago today.‌"') self.execute_script('var today_string = "‌today.‌"') self.execute_script('var years_old = "‌years old‌"') self.execute_script('var flavour_anniversary_future = "‌Ubuntu MATE\'s official flavour anniversary‌"') self.execute_script('var flavour_anniversary_present = "‌Ubuntu MATE become an official flavour‌"') self.execute_script('var flavour_anniversary_past = "‌Ubuntu MATE\'s official flavour anniversary was‌"') self.execute_script('var project_birthday_future = "‌Ubuntu MATE will be‌"') self.execute_script('var project_birthday_present = "‌Ubuntu MATE is‌"') self.execute_script('var project_birthday_past = "‌Ubuntu MATE turned‌"') self.execute_script('var project_birthday = "‌Happy Birthday!‌"') self.execute_script('var celebrate_new_year = "‌Happy New Year from Ubuntu MATE!‌"') self.execute_script('var project_release_future = "‌will be released‌"') self.execute_script('var project_release_present = "‌is released today!‌"') self.execute_script('var project_release_past = "‌was released‌"') self.execute_script('var project_release_thanks = "‌Thank you for testing‌"') self.execute_script('checkDates();') ### Splash ### if self.current_page == 'splash.html': self.do_smooth_footer = True # Determine which screen to show after the splash screen. if systemstate.session_type == 'live': self.execute_script('var splashNextPage = "hellolive"') elif systemstate.session_type == 'guest': self.execute_script('var splashNextPage = "helloguest"') else: self.execute_script('var splashNextPage = "index"') # Smoothly fade footer when entering main menu. self.splash_finished = True ### Chat Page ### if self.current_page == 'chatroom.html': if self._apt_cache['hexchat'].is_installed: self.execute_script("$('.hexchat').show();") self.execute_script("$('.webchat').hide();") else: self.execute_script("$('.hexchat').hide();") self.execute_script("$('.webchat').show();") ### Getting Started Page ### if self.current_page == 'gettingstarted.html': # Display information tailored to graphics vendor (Getting Started / Drivers) self.execute_script('var graphicsVendor = "' + systemstate.graphics_vendor + '";') self.execute_script('var graphicsGrep = "' + systemstate.graphics_grep + '";') self.execute_script('$("#boot-mode").html("' + systemstate.boot_mode + '")') # Update any applications featured on these pages. dynamicapps.update_app_status(self, 'hardinfo') dynamicapps.update_app_status(self, 'gparted') dynamicapps.update_app_status(self, 'gnome-disk-utility') dynamicapps.update_app_status(self, 'mate-disk-usage-analyzer') dynamicapps.update_app_status(self, 'mate-system-monitor') dynamicapps.update_app_status(self, 'psensor') dynamicapps.update_app_status(self, 'boot-repair') dynamicapps.update_app_status(self, 'codecs') dynamicapps.update_app_status(self, 'firmware') dynamicapps.update_app_status(self, 'hp-printer') dynamicapps.update_app_status(self, 'keyboard-chinese') dynamicapps.update_app_status(self, 'keyboard-japanese') dynamicapps.update_app_status(self, 'keyboard-korean') ### Software Page ### if self.current_page == 'software.html': dynamicapps.hide_non_free = False self.do_smooth_footer = True # If loading a minimal "Get More Software" only page. if arg.jump_software_page: self.execute_script('$("#menu-button").hide()') self.execute_script('$("#navigation-title").html("Curated software collection")') self.execute_script('$("#navigation-sub-title").css("color","#DED9CB")') # Pass 'Servers' variable used for one-click server links. self.execute_script('var server_string = "' + _("Servers") + '"') # Dynamically load application lists. dynamicapps.populate_categories(self) dynamicapps.update_all_app_status(self) dynamicapps.populate_featured_apps(self) # Show a different footer in the Boutique. self.execute_script("$('#footer-global-left').html('" + boutique_footer + "');") # Set version and subscription details. self.execute_script('$("#boutique-version").html("' + systemstate.welcome_version + '")') if systemstate.updates_subscribed: self.execute_script('$("#update-subscribed").show()') else: self.execute_script('$("#update-notification").show()') ### Raspberry Pi Page ### if self.current_page == 'rpi.html': # Check file system resize flag. systemstate.rpi_resize('check', self) ### Donate ### if self.current_page == 'donate.html': # Pass translatable short-hand month strings for the supporters grid. self.execute_script('short_jan = "' + _("Jan") + '"') self.execute_script('short_feb = "' + _("Feb") + '"') self.execute_script('short_mar = "' + _("Mar") + '"') self.execute_script('short_apr = "' + _("Apr") + '"') self.execute_script('short_may = "' + _("May") + '"') self.execute_script('short_jun = "' + _("Jun") + '"') self.execute_script('short_jul = "' + _("Jul") + '"') self.execute_script('short_aug = "' + _("Aug") + '"') self.execute_script('short_sep = "' + _("Sep") + '"') self.execute_script('short_oct = "' + _("Oct") + '"') self.execute_script('short_nov = "' + _("Nov") + '"') self.execute_script('short_dec = "' + _("Dec") + '"') def _load_finished_cb(self, view, frame): self._push_config() def _nav_request_policy_decision_cb(self, view, frame, net_req, nav_act, pol_dec): uri = net_req.get_uri() self.current_page = uri.rsplit('/', 1)[1] try: if uri.index('#') > 0: uri = uri[:uri.index('#')] except ValueError: pass if uri == self.l_uri: pol_dec.use() return True if uri.startswith('cmd://'): self._do_command(uri) return True self.l_uri = uri if self._slide_list is None: # no translations have to been specified, so we can just load the specified page.. page = urllib.request.urlopen(uri) else: # find the slide in slide_list head, slide_name = os.path.split(uri) found = False for slide in self._slide_list: head, trans_slide_name = os.path.split(slide) if slide_name == trans_slide_name: found = True # load the translated html trans_uri = urllib.parse.urljoin('file:', urllib.request.pathname2url(slide)) page = urllib.request.urlopen(trans_uri) break if not found: # this should never happen, but if it does, recover by loading the originally specified page arg.print_verbose('Translation','Couldn''t find slide %s when getting translation' %uri) page = urllib.request.urlopen(uri) # use UTF-8 encoding as fix for   chars in translated html # # When loading the html, for the base_uri, use the uri of the originally specified # page (which will be in _data_path) rather than the uri of any translated html we may be using instead. # Doing this allows the js, css, fonts etc. directories to be located by the translated page, frame.load_string(page.read().decode(), "text/html", "UTF-8", uri); pol_dec.ignore() return True def _do_command(self, uri): if uri.startswith('cmd://'): uri = uri[6:] try: if uri.startswith('install-appid?'): dynamicapps.modify_app(self, 'install', uri[14:]) elif uri.startswith('remove-appid?'): dynamicapps.modify_app(self, 'remove', uri[13:]) elif uri.startswith('upgrade-appid?'): dynamicapps.modify_app(self, 'upgrade', uri[14:]) elif uri.startswith('launch-appid?'): dynamicapps.launch_app(uri[13:]) elif uri.startswith('filter-apps?'): filter_name = uri.split('?')[1] nonfree_toggle = uri.split('?')[2] if nonfree_toggle == 'toggle': dynamicapps.apply_filter(self, filter_name, True) else: dynamicapps.apply_filter(self, filter_name) elif uri.startswith('app-info-show?'): appid = uri.split('?')[1] self.execute_script('$("#info-show-' + appid + '").hide()') self.execute_script('$("#info-hide-' + appid + '").show()') self.execute_script('$("#details-' + appid + '").fadeIn("fast")') elif uri.startswith('app-info-hide?'): appid = uri.split('?')[1] self.execute_script('$("#info-show-' + appid + '").show()') self.execute_script('$("#info-hide-' + appid + '").hide()') self.execute_script('$("#details-' + appid + '").fadeOut("fast")') elif uri.startswith('screenshot?'): filename = uri.split('?')[1] dynamicapps.show_screenshot(filename) elif uri == 'apt-update': update_repos() self._apt_cache.close() self._apt_cache = apt.Cache() self._push_config() elif uri == 'fix-incomplete-install': fix_incomplete_install() self._apt_cache.close() self._apt_cache = apt.Cache() self._push_config() elif uri == 'fix-broken-depends': fix_broken_depends() self._apt_cache.close() self._apt_cache = apt.Cache() self._push_config() elif uri == 'get-aacs-db': self.execute_script('$(".bluray-applying").show()') get_aacs_db() self.execute_script('$(".bluray-applying").hide()') elif uri == 'autostart': self._config.autostart ^= True self._push_config() elif uri == 'install': subprocess.Popen(['ubiquity','gtk_ui']) elif uri == 'backup': subprocess.Popen(['deja-dup-preferences']) elif uri == 'chatroom': subprocess.Popen(['hexchat','IRC://irc.freenode.net/ubuntu-mate']) elif uri == 'control': subprocess.Popen(['mate-control-center']) elif uri == 'drivers': subprocess.Popen(['software-properties-gtk','--open-tab=4']) elif uri == 'firewall': subprocess.Popen(['gufw']) elif uri == 'language': subprocess.Popen(['gnome-language-selector']) elif uri == 'users': subprocess.Popen(['users-admin']) elif uri == 'quit': goodbye() elif uri == 'tweak': subprocess.Popen(['mate-tweak']) elif uri == 'update': subprocess.Popen(['update-manager']) elif uri == 'printers': subprocess.Popen(['system-config-printer']) elif uri == 'gparted': subprocess.Popen(['gparted-pkexec']) elif uri == 'sysmonitor': subprocess.Popen(['mate-system-monitor']) elif uri.startswith('run?'): subprocess.Popen([uri[4:]]) elif uri.startswith('link?'): webbrowser.open_new_tab(uri[5:]) elif uri == 'checkInternetConnection': systemstate.check_internet_connection() if systemstate.is_online: self.execute_script("$('.offline').hide();") self.execute_script("$('.online').show();") else: self.execute_script("$('.offline').show();") self.execute_script("$('.online').hide();") elif uri == 'resize-rpi': systemstate.rpi_resize('do-resize', self) elif uri == 'reboot-rpi': systemstate.rpi_resize('reboot') elif uri == 'subscribe-updates': print('[Welcome] Subscribing to Ubuntu MATE Welcome Updates...') self.execute_script("$('#update-notification').hide()") self.execute_script("$('#update-subscribing').show()") dynamicapps.modify_app(self, 'install', 'ubuntu-mate-welcome') # Verify if the PPA was successfully added. if os.path.exists(systemstate.welcome_ppa_file): if os.path.getsize(systemstate.welcome_ppa_file) > 0: print('[Welcome] Success, PPA added! Application restarting...') os.execv(__file__, sys.argv) else: print('[Welcome] Failed, PPA not detected!') self.execute_script('$("#update-subscribing").hide()') self.execute_script('$("#update-notification").show()') elif uri == 'init-system-info': systemstate.get_system_info(self) else: print('[Error] Unknown command: ', uri) except Exception as e: print('[Error] Failed to execute command: ', uri) print('[Error] Exception: ', e) class WelcomeApp(object): def __init__(self): # establish our location self._location = os.path.dirname( os.path.abspath(inspect.getfile(inspect.currentframe())) ) # check for relative path if( os.path.exists( os.path.join(self._location, 'data/' ) ) ): print('[Debug] Using relative path for data source. Non-production testing.') self._data_path = os.path.join(self._location, 'data/') elif( os.path.exists('/usr/share/ubuntu-mate-welcome/') ): print('Using /usr/share/ubuntu-mate-welcome/ path.') self._data_path = '/usr/share/ubuntu-mate-welcome/' else: print('Unable to source the ubuntu-mate-welcome data directory.') sys.exit(1) self._build_app() def _get_translated_slides(self): """ If a locale has been specified on the command line, get translated slides for that. If not, get translated slides for the current locale Do not assume that every slide has a translation, check each file individually Returns: a list of filenames of translated slides - if there is no translated version of a slide, the filename of the untranslated version from the _data_path directory is used instead """ if (arg.locale is not None): locale_to_use = arg.locale else: locale_to_use = str(locale.getlocale()[0]) def set_locale_dir(this_locale): # check for relative path if (os.path.exists(os.path.join(self._location, 'i18n', this_locale))): locale_dir = os.path.join(self._location, 'i18n', this_locale) print('[i18n] Using ' + this_locale + '. Non-production testing.') elif (os.path.exists(os.path.join('/usr/share/ubuntu-mate-welcome/i18n/', this_locale))): locale_dir = os.path.join('/usr/share/ubuntu-mate-welcome/i18n/', this_locale) print('[i18n] Using ' + this_locale) else: locale_dir = '' print('[i18n] Locale ' + this_locale + ' not available.') return locale_dir # if no locale exists, try a generic locale. locale_dir = set_locale_dir(locale_to_use) if locale_dir == '': locale_generic = locale_to_use.split('_')[0] print('[i18n] Trying ' + locale_generic + '...') locale_dir = set_locale_dir(locale_generic) results = [] slides = glob.glob(os.path.join(self._data_path, '*.html')) for slide in slides: # get the slide name and see if a translated version exists head, slide_name = os.path.split(slide) trans_slide = os.path.join(locale_dir, slide_name) if os.path.exists(trans_slide): results.append(trans_slide) arg.print_verbose("i18n","Will use %s translation of %s" %(locale_to_use, slide_name)) else: results.append(slide) arg.print_verbose("i18n","No %s translation of %s found. Will use version in _data_path" %(locale_to_use, slide)) return results def _build_app(self): # Slightly different attributes if "--software-only" is activated. if arg.jump_software_page: title = _("Software Boutique") width = 900 height = 600 load_file = 'software-only.html' else: title = _("Welcome") width = 800 height = 552 load_file = 'splash.html' # Enlarge the window should the text be any larger. if systemstate.zoom_level == 1.1: width = width + 20 height = height + 20 elif systemstate.zoom_level == 1.2: width = width + 60 height = height + 40 elif systemstate.zoom_level == 1.3: width = width + 100 height = height + 60 elif systemstate.zoom_level == 1.4: width = width + 130 height = height + 100 elif systemstate.zoom_level == 1.5: width = width + 160 height = height + 120 # Jump to a specific page for testing purposes. if arg.jump_to: load_file = arg.jump_to + '.html' # build window w = Gtk.Window() w.set_position(Gtk.WindowPosition.CENTER) w.set_wmclass('Ubuntu MATE Welcome', 'Ubuntu MATE Welcome') w.set_title(title) # http://askubuntu.com/questions/153549/how-to-detect-a-computers-physical-screen-size-in-gtk s = Gdk.Screen.get_default() if s.get_height() <= 600: w.set_size_request(768, 528) else: w.set_size_request(width, height) icon_dir = os.path.join(self._data_path, 'img', 'welcome', 'ubuntu-mate-icon.svg') w.set_icon_from_file(icon_dir) #get the translated slide list trans_slides = self._get_translated_slides() # build webkit container mv = AppView(trans_slides) # load our index file file = os.path.abspath(os.path.join(self._data_path, load_file)) uri = 'file://' + urllib.request.pathname2url(file) mv.open(uri) # build scrolled window widget and add our appview container sw = Gtk.ScrolledWindow() sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw.add(mv) # build an autoexpanding box and add our scrolled window b = Gtk.VBox(homogeneous=False, spacing=0) b.pack_start(sw, expand=True, fill=True, padding=0) # add the box to the parent window and show w.add(b) w.connect('delete-event', goodbye) w.show_all() self._window = w self._appView = mv def run(self): signal.signal(signal.SIGINT, signal.SIG_DFL) Gtk.main() def close(self, p1, p2): Gtk.main_quit(p1, p2); class SystemState(object): def __init__(self): # Set initial variables self.is_online = False self.user_name = getuser() self.updates_subscribed = False self.welcome_version = 'Unknown' self.rpi_resize_pending = False # Get current architecture of system. # Outputs 'i386', 'amd64', etc - Based on packages instead of kernel (eg. i686, x86_64). self.arch = str(subprocess.Popen(['dpkg','--print-architecture'], stdout=subprocess.PIPE).communicate()[0]).strip('\\nb\'') # Get current codename of Ubuntu MATE in use. # Uses first word in lowercase, such as : trusty, wily, xenial self.codename = platform.dist()[2] # Determine which type of session we are in. if os.path.exists('/usr/share/glib-2.0/schemas/zubuntu-mate-live.gschema.override'): self.session_type = 'live' elif self.user_name[:6] == 'guest-': self.session_type = 'guest' elif os.path.isfile(os.path.join('/','boot/','kernel7.img')): self.session_type = 'pi' else: self.session_type = 'normal' # To inform the user if they are running in BIOS or UEFI mode. if os.path.exists("/sys/firmware/efi"): self.boot_mode = 'UEFI' elif self.session_type == 'pi': self.boot_mode = 'Raspberry Pi' elif self.arch == 'powerpc': self.boot_mode = 'Yaboot' else: self.boot_mode = 'BIOS' # Create, then spawn threads thread1 = Thread(target=self.check_internet_connection) thread2 = Thread(target=self.detect_graphics) thread1.start() thread2.start() # Check whether Welcome is subscribed for updates. self.welcome_ppa_file = '/etc/apt/sources.list.d/ubuntu-mate-dev-ubuntu-welcome-' + self.codename + '.list' if os.path.exists(self.welcome_ppa_file): if os.path.getsize(self.welcome_ppa_file) > 0: self.updates_subscribed = True # Accessibility - Enlarge/shrink text based on Font DPI set by the user. if arg.font_dpi_override: font_dpi = arg.font_dpi_override else: try: font_gsettings = Gio.Settings.new('org.mate.font-rendering') font_value = font_gsettings.get_value('dpi') font_dpi = int(float(str(font_value))) arg.print_verbose('Welcome', 'Font DPI is: ' + str(font_dpi)) except: font_dpi = 96 print('[Welcome] Couldn\'t retrieve font DPI. Using default value of ' + str(font_dpi)) if font_dpi < 50: print('[Welcome] DPI below 50. Out of range..') font_dpi = 96 elif font_dpi > 500: print('[Welcome] DPI over 500. Out of range.') font_dpi = 96 zoom_level = 1.0 if font_dpi <= 80: zoom_level = 0.75 elif font_dpi <= 87: zoom_level = 0.85 elif font_dpi <= 94: zoom_level = 0.9 elif font_dpi <= 101: zoom_level = 1.0 # Default DPI is usually 96. elif font_dpi <= 108: zoom_level = 1.1 elif font_dpi <= 115: zoom_level = 1.2 elif font_dpi <= 122: zoom_level = 1.3 elif font_dpi <= 129: zoom_level = 1.4 elif font_dpi >= 130: zoom_level = 1.5 self.dpi = font_dpi self.zoom_level = zoom_level def check_internet_connection(self): print('[Network Test] Checking for internet connectivity... ') url = "http://archive.ubuntu.com/" if arg.simulate_no_connection: print('[Network Test] Simulation argument override. Retrying will reset this.') arg.simulate_no_connection = False self.is_online = False return if arg.simulate_force_connection: print('[Network Test] Simulation argument override. Forcing connection presence.') print('[Network Test] WARNING: Do not attempt to install/remove software offline as this may lead to errors later!') arg.simulate_connection = False self.is_online = True return try: response = urllib.request.urlopen(url, timeout=2).read().decode('utf-8') except socket.timeout: print("[Network Test] -- Socket timed out to URL {0}".format(url)) self.is_online = False except: print("[Network Test] -- Could not establish a connection to '{0}'. ".format(url)) self.is_online = False else: print("[Network Test] Successfully pinged '{0}' ".format(url)) self.is_online = True def detect_graphics(self): # If we're the Raspberry Pi, there is nothing to output. if self.session_type == 'pi': self.graphics_grep = 'Raspberry Pi' self.graphics_vendor = 'Raspberry Pi' return # TODO: Support dual graphic cards. arg.print_verbose('Graphics','Detecting graphics vendor... ') try: output = subprocess.Popen('lspci | grep VGA', stdout=subprocess.PIPE, shell='True').communicate()[0] output = output.decode(encoding='UTF-8') except: # When 'lspci' does not find a VGA controller (this is the case for the RPi 2) arg.print_verbose("Graphics","Couldn't detect a VGA Controller on this system.") output = 'Unknown' # Scan for and set known brand name. if output.find('NVIDIA') != -1: self.graphics_vendor = 'NVIDIA' elif output.find('AMD') != -1: self.graphics_vendor = 'AMD' elif output.find('Intel') != -1: self.graphics_vendor = 'Intel' elif output.find('VirtualBox') != -1: self.graphics_vendor = 'VirtualBox' else: self.graphics_vendor = 'Unknown' self.graphics_grep = repr(output) self.graphics_grep = self.graphics_grep.split("controller: ",1)[1] self.graphics_grep = self.graphics_grep.split("\\n",1)[0] arg.print_verbose("Graphics","Detected: {0}".format(self.graphics_grep)) def get_system_info(self, webkit): print('[System Specs] Gathering system specifications...') # Prefixes for translation mb_prefix = _("MB") mib_prefix = _("MiB") gb_prefix = _("GB") gib_prefix = _("GiB") # Start collecting advanced system information in the background. # (Python can do other things while this command completes) arg.print_verbose('System Specs', 'Running "inxi" for advanced system information...') try: inxi_raw = subprocess.Popen(['inxi','-c','0','-v','5','-p','-d','-xx'], stdout=subprocess.PIPE) except: print('[System Specs] Failed to execute collect advanced information. Is "inxi" no longer installed?') # Append a failure symbol beforehand in event something goes horribly wrong. stat_error_msg = _("Could not gather data.") html_tag = '' for element in ['distro', 'kernel', 'motherboard', 'boot-mode', 'cpu-model', 'cpu-speed', 'arch-use', 'arch-supported', 'memory', 'graphics', 'filesystem', 'capacity', 'allocated-space', 'free-space']: webkit.execute_script('$("#spec-' + element + '").html("' + html_tag + '")') # Collect basic system information def run_external_command(command, with_shell=False): if with_shell: raw = str(subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0]) else: raw = str(subprocess.Popen(command, stdout=subprocess.PIPE).communicate()[0]) output = raw.replace("b'","").replace('b"',"").replace("\\n'","").replace("\\n","") return output ## Distro try: arg.print_verbose('System Specs', 'Gathering data: Distribution') distro_description = run_external_command(['lsb_release','-d','-s']) distro_codename = run_external_command(['lsb_release','-c','-s']) webkit.execute_script('$("#spec-distro").html("' + distro_description + '")') except: print('[System Specs] Failed to retrieve data: Distribution') ## Kernel try: arg.print_verbose('System Specs', 'Gathering data: Kernel') kernel = run_external_command(['uname','-r']) webkit.execute_script('$("#spec-kernel").html("' + kernel + '")') except: print('[System Specs] Failed to retrieve data: Kernel') ## Motherboard try: arg.print_verbose('System Specs', 'Gathering data: Motherboard') motherboard_name = run_external_command(['cat','/sys/devices/virtual/dmi/id/board_name']) webkit.execute_script('$("#spec-motherboard").html("' + motherboard_name + '")') except: print('[System Specs] Failed to retrieve data: Motherboard') ## CPU Details arg.print_verbose('System Specs', 'Gathering data: CPU') try: cpu_model = run_external_command(['lscpu | grep "name"'], True).split(': ')[1] webkit.execute_script('$("#spec-cpu-model").html("' + cpu_model + '")') except: print('[System Specs] Failed to retrieve data: CPU Model') try: try: # Try obtaining the maximum speed first. cpu_speed = int(run_external_command(['lscpu | grep "max"'], True).split(': ')[1].strip(' ').split('.')[0]) except: # Otherwise, fetch the CPU's MHz. cpu_speed = int(run_external_command(['lscpu | grep "CPU MHz"'], True).split(': ')[1].strip(' ').split('.')[0]) webkit.execute_script('$("#spec-cpu-speed").html("' + str(cpu_speed) + ' MHz")') except: print('[System Specs] Failed to retrieve data: CPU Speed') try: if self.arch == 'i386': cpu_arch_used = '32-bit' elif self.arch == 'amd64': cpu_arch_used = '64-bit' else: cpu_arch_used = self.arch webkit.execute_script('$("#spec-arch-use").html("' + cpu_arch_used + '")') except: print('[System Specs] Failed to retrieve data: CPU Arch in Use') try: cpu_arch_supported = run_external_command(['lscpu | grep "mode"'], True).split(': ')[1] webkit.execute_script('$("#spec-arch-supported").html("' + cpu_arch_supported + '")') except: print('[System Specs] Failed to retrieve data: CPU Supported Arch') ## Root partition (where Ubuntu MATE is installed) and the rest of that disk. try: if self.session_type == 'live': webkit.execute_script('$(".specs-hide-live-session").hide()') else: arg.print_verbose('System Specs', 'Gathering data: Storage') ## Gather entire disk data root_partition = run_external_command(['mount | grep "on / "'], True).split(' ')[0] if root_partition[:-2] == "/dev/sd": # /dev/sdXY root_dev = root_partition[:-1] if root_partition[:-2] == "/dev/hd": # /dev/hdXY root_dev = root_partition[:-1] if root_partition[:-3] == "/dev/mmcblk": # /dev/mmcblkXpY root_dev = root_partition[:-2] else: root_dev = root_partition[:-1] # Generic disk_dev_name = root_dev.split('/')[2] arg.print_verbose('System Specs', 'Ubuntu MATE is installed on disk: ' + root_dev) rootfs = os.statvfs('/') root_size = rootfs.f_blocks * rootfs.f_frsize root_free = rootfs.f_bavail * rootfs.f_frsize root_used = root_size - root_free entire_disk = run_external_command(['lsblk -b | grep "' + disk_dev_name + '" | grep "disk"'], True) entire_disk = int(entire_disk.split()[3]) ## Perform calculations across units capacity_GB = round(entire_disk/1000/1000/1000,1) capacity_GiB = round(entire_disk/1024/1024/1024,1) allocated_GB = round(root_size/1000/1000/1000,1) allocated_GiB = round(root_size/1024/1024/1024,1) used_GB = round(root_used/1000/1000/1000,1) used_GiB = round(root_used/1024/1024/1024,1) free_GB = round(root_free/1000/1000/1000,1) free_GiB = round(root_free/1024/1024/1024,1) other_GB = round((entire_disk-root_size)/1000/1000/1000,1) other_GiB = round((entire_disk-root_size)/1024/1024/1024,1) # Show megabytes/mebibytes (in red) if gigabytes are too small. if capacity_GB <= 1: capacity_GB = str(round(entire_disk/1000/1000,1)) + ' ' + mb_prefix capacity_GiB = str(round(entire_disk/1024/1024,1)) + ' ' + mib_prefix else: capacity_GB = str(capacity_GB) + ' ' + gb_prefix capacity_GiB = str(capacity_GiB) + ' ' + gib_prefix if allocated_GB <= 1: allocated_GB = str(round(root_size/1000/1000,1)) + ' ' + mb_prefix allocated_GiB = str(round(root_size/1024/1024,1)) + ' ' + mib_prefix else: allocated_GB = str(allocated_GB) + ' ' + gb_prefix allocated_GiB = str(allocated_GiB) + ' ' + gib_prefix if used_GB <= 1: used_GB = str(round(root_used/1000/1000,1)) + ' ' + mb_prefix used_GiB = str(round(root_used/1024/1024,1)) + ' ' + mib_prefix else: used_GB = str(used_GB) + ' ' + gb_prefix used_GiB = str(used_GiB) + ' ' + gib_prefix if free_GB <= 1: free_GB = str(round(root_free/1000/1000,1)) + ' ' + mb_prefix free_GiB = str(round(root_free/1024/1024,1)) + ' ' + mib_prefix webkit.execute_script('$("#spec-free-space").addClass("specs-error")') else: free_GB = str(free_GB) + ' ' + gb_prefix free_GiB = str(free_GiB) + ' ' + gib_prefix if other_GB <= 1: other_GB = str(round((entire_disk-root_size)/1000/1000,1)) + ' ' + mb_prefix other_GiB = str(round((entire_disk-root_size)/1024/1024,1)) + ' ' + mib_prefix else: other_GB = str(other_GB) + ' ' + gb_prefix other_GiB = str(other_GiB) + ' ' + gib_prefix ## Append data to HTML. webkit.execute_script('$("#spec-filesystem").html("' + root_partition + '")') webkit.execute_script('$("#spec-capacity").html("' + capacity_GB + ' (' + capacity_GiB + ')")') webkit.execute_script('$("#spec-allocated-space").html("' + allocated_GB + ' (' + allocated_GiB + ')")') webkit.execute_script('$("#spec-used-space").html("' + used_GB + ' (' + used_GiB + ')")') webkit.execute_script('$("#spec-free-space").html("' + free_GB + ' (' + free_GiB + ')")') webkit.execute_script('$("#spec-other-space").html("' + other_GB + ' (' + other_GiB + ')")') ## Calculate representation across physical disk disk_percent_UM_used = int(round(root_used / entire_disk * 100)) * 2 disk_percent_UM_free = int(round(root_free / entire_disk * 100)) * 2 disk_percent_other = (200 - disk_percent_UM_used - disk_percent_UM_free) arg.print_verbose('System Specs', ' --- Disk: ' + root_dev) arg.print_verbose('System Specs', ' --- * OS Used: ' + str(root_used) + ' bytes (' + str(disk_percent_UM_used/2) + '%)') arg.print_verbose('System Specs', ' --- * OS Free: ' + str(root_free) + ' bytes (' + str(disk_percent_UM_free/2) + '%)') arg.print_verbose('System Specs', ' --- = Other Partitions: ' + str(entire_disk - root_size) + ' bytes (' + str(disk_percent_other/2) + '%)') webkit.execute_script("$('#disk-used').width('" + str(disk_percent_UM_used) + "px');") webkit.execute_script("$('#disk-free').width('" + str(disk_percent_UM_free) + "px');") webkit.execute_script("$('#disk-other').width('" + str(disk_percent_other) + "px');") except: print('[System Specs] Failed to retrieve data: Storage') ## RAM try: arg.print_verbose('System Specs', 'Gathering Data: RAM') ram_bytes = run_external_command(['free -b | grep "Mem:" '], True) ram_bytes = float(ram_bytes.split()[1]) if round(ram_bytes / 1024 / 1024) < 1024: ram_xb = str(round(ram_bytes / 1000 / 1000, 1)) + ' ' + mb_prefix ram_xib = str(round(ram_bytes / 1024 / 1024, 1)) + ' ' + mib_prefix else: ram_xb = str(round(ram_bytes / 1000 / 1000 / 1000, 1)) + ' ' + gb_prefix ram_xib = str(round(ram_bytes / 1024 / 1024 / 1024, 1)) + ' ' + gib_prefix ram_string = ram_xb + ' (' + ram_xib + ')' webkit.execute_script('$("#spec-memory").html("' + ram_string + '")') except: print('[System Specs] Failed to retrieve data: RAM (Memory)') ## Graphics webkit.execute_script('$("#spec-graphics").html("' + self.graphics_grep + '")') ## Collect missing data differently for some architectures. if systemstate.arch == 'powerpc': ## Motherboard & Revision try: arg.print_verbose('System Specs', 'Gathering alternate data: PowerPC Motherboard') mb_model = run_external_command(['grep','motherboard','/proc/cpuinfo']).split(': ')[1] mb_rev = run_external_command(['grep','revision','/proc/cpuinfo']).split(': ')[1] webkit.execute_script('$("#spec-motherboard").html("' + mb_model + ' ' + mb_rev + '")') except: arg.print_verbose('System Specs', 'Failed to gather data: PowerPC Motherboard') ## CPU and Clock Speed try: arg.print_verbose('System Specs', 'Gathering alternate data: PowerPC CPU') cpu_model = run_external_command(['grep','cpu','/proc/cpuinfo']).split(': ')[1] cpu_speed = run_external_command(['grep','clock','/proc/cpuinfo']).split(': ')[1] webkit.execute_script('$("#spec-cpu-model").html("' + cpu_model + '")') webkit.execute_script('$("#spec-cpu-speed").html("' + str(cpu_speed) + '")') except: arg.print_verbose('System Specs', 'Failed to gather data: PowerPC CPU') ## Device Name try: arg.print_verbose('System Specs', 'Gathering alternate data: PowerPC Model Name') mb_name = run_external_command(['grep','detected','/proc/cpuinfo']).split(': ')[1] webkit.execute_script('$("#spec-motherboard").append(" / ' + mb_name + '")') except: arg.print_verbose('System Specs', 'Failed to gather data: PowerPC Model Name') ## Boot Mode / PowerMac Generation try: arg.print_verbose('System Specs', 'Gathering alternate data: PowerMac Generation') mac_generation = run_external_command(['grep','pmac-generation','/proc/cpuinfo']).split(': ')[1] webkit.execute_script('$("#spec-boot-mode").html("Yaboot (' + mac_generation + ')")') except: arg.print_verbose('System Specs', 'Failed to gather data: PowerMac Generation') # Append advanced system information try: arg.print_verbose('System Specs', 'Waiting for inxi process to finish...') inxi_output = str(inxi_raw.communicate()[0]) inxi_output = inxi_output.replace("b'","").replace("\\n","\n") webkit.execute_script("$('#specs-inxi').html('')") for line in inxi_output.split('\n'): webkit.execute_script("$('#specs-inxi').append('" + line.strip('"').strip("'") + "
')") print('[System Specs] Successfully appended advanced system information.') except: print('[System Specs] Failed to append advanced system information or communicate with "inxi" process.') # Check internet connectivity status. if self.is_online: webkit.execute_script('$("#specs-has-net").show()') webkit.execute_script('$("#specs-has-no-net").hide()') else: webkit.execute_script('$("#specs-has-net").hide()') webkit.execute_script('$("#specs-has-no-net").show()') # Change icon depending on what type of device we are using. if self.session_type == 'pi': webkit.execute_script('$("#specs-device-rpi").show()') webkit.execute_script('$(".specs-hide-pi").hide()') elif self.arch == 'powerpc': webkit.execute_script('$("#specs-device-powerpc").show()') webkit.execute_script('$(".specs-hide-ppc").hide()') elif self.graphics_vendor == 'VirtualBox': webkit.execute_script('$("#specs-device-vbox").show()') webkit.execute_script('$(".specs-hide-vbox").hide()') elif self.session_type == 'live': webkit.execute_script('$("#specs-live-session").show()') webkit.execute_script('$(".specs-hide-live").hide()') else: webkit.execute_script('$("#specs-device-normal").show()') # Display UEFI/BIOS boot mode. if systemstate.arch == 'i386' or systemstate.arch == 'amd64': webkit.execute_script('$("#spec-boot-mode").html("' + self.boot_mode + '")') # Hide root storage info if in a live session. if self.session_type == 'live': webkit.execute_script('$(".spec-3").hide()') # Data cached, ready to display. webkit.execute_script('$("#specs-loading").fadeOut("fast")') webkit.execute_script('$("#specs-tabs").fadeIn("fast")') webkit.execute_script('$("#specs-basic").fadeIn("medium")') webkit.execute_script('setCursorNormal()') def rpi_resize(self, action, webkit=None): if action == 'do-resize': subprocess.call(['pkexec', '/usr/lib/ubuntu-mate/ubuntu-mate-welcome-rpi2-partition-resize']) def notify(subject, body, icon): Notify.init(_('Raspberry Pi Partition Resize')) resize_notify=Notify.Notification.new(subject, body, icon) resize_notify.show() try: with open('/tmp/notify_rpi_status') as status_file: status_code = int(status_file.read()) except: status_code = 0 try: with open('/tmp/notify_rpi_text') as misc_file: misc_text = misc_file.read() except: misc_text = "" if status_code == 1: notify( _("Root partition has been resized."), _("The filesystem will be enlarged upon the next reboot."), 'dialog-information' ) self.rpi_resize_pending = True webkit.execute_script('$("#rpi-resized").hide()') webkit.execute_script('$("#rpi-not-resized").hide()') webkit.execute_script('$("#rpi-restart-now").show()') elif status_code == 2: notify( _("Don't know how to expand."), misc_text + ' ' + _("does not exist or is not a symlink."), 'dialog-error' ) elif status_code == 3: notify( _("Don't know how to expand."), misc_text + ' ' + _("is not an SD card."), 'dialog-error' ) elif status_code == 4: notify( _("Don't know how to expand."), _("Your partition layout is not currently supported by this tool."), 'dialog-error' ) elif status_code == 5: notify( _("Don't know how to expand."), misc_text + ' ' + _("is not the last partition."), 'dialog-error' ) else: notify( _("Failed to run resize script."), _("The returned error code is:") + str(status_code), 'dialog-error' ) print('[Welcome] Unrecognised return code for Raspberry Pi resize: ' + str(status_code)) app._appView._push_config() elif action == 'check': if os.path.exists('/.resized'): resized = True else: resized = False if resized: webkit.execute_script('$("#rpi-resized").show()') webkit.execute_script('$("#rpi-not-resized").hide()') else: webkit.execute_script('$("#rpi-resized").hide()') webkit.execute_script('$("#rpi-not-resized").show()') if self.rpi_resize_pending: webkit.execute_script('$("#rpi-resized").hide()') webkit.execute_script('$("#rpi-not-resized").hide()') webkit.execute_script('$("#rpi-restart-now").show()') elif action == 'reboot': subprocess.call(['mate-session-save','--shutdown-dialog']) class DynamicApps(object): def __init__(self): # Load JSON Index into Memory self.reload_index() # Variables to remember common details. self.all_categories = ['Accessories', 'Education', 'Games', 'Graphics', 'Internet', 'Office', 'Programming', 'Media', 'SysTools', 'UnivAccess', 'Servers', 'MoreApps'] self.hide_non_free = False # Reading the apt cache later. self._apt_cache = apt.Cache() # Indicate that operations are in progress. self.operations_busy = False # Get the version of Welcome in use. for pkgname in self._apt_cache.keys(): if 'ubuntu-mate-welcome' in pkgname: systemstate.welcome_version = self._apt_cache['ubuntu-mate-welcome'].installed.version break print('[Welcome] Version: ' + systemstate.welcome_version) ###### JSON Index Structure # # ===== Structure Overview ===== # { # "Category" { - Application category. # "application-id" { - Unique string identifier for application, apps sorted A-Z. Hyphens preferred. # "variable": "data", - Variable containing single data. # "list": ["This is a line.", # "The same line."] - Variable containing a 'list' of data. # "group": { "variable": "data" } - Group containing data. # } # } # } # # ** Standard JSON rules apply. Watch out for the commas. # ** Important!! Use ’ instead of ' for an apostrophe character. # ===== Variable Index ===== # Variable Type Required? Description # ----------------------- ---------- ---------- --------------------------------------------- # name string Yes Name of the application as displayed to the user. # img string Yes Name of image. Excluding ".png" extension. # main-package string Yes Package used to detect if it's installed. # launch-command string No Command to launch the installed program. Can be ignored for no launch option. # install-packages string * Packages to install/reinstall. Comma separated. # remove-packages string * Packages to remove. Comma separated. # upgradable boolean * This package is only for upgrading. # upgrade-packages string * Packages to upgrade. Comma separated. # description list Yes Description of the application. Use usual HTML tags for formatting. Can be left blank if unlisted. # alternate-to string No If the app is similar or has an alternate. Can be ignored. # subcategory string Yes Used for filtering applications within the category. Eg. "Partitioning", "Audio Production". # open-source boolean Yes Proprietary or Open Source? # url-info string Yes URL to the web page for more information. # url-android string No URL if there is an associated Android app. Can be ignored. # url-ios string No URL if there is an associated Android app. Can be ignored. # arch string Yes Supported architectures for this app. Comma seperated. # releases string Yes Supported versions of Ubuntu MATE to show this application. Comma seperated. # working boolean Yes Show/hide visibility of this application. # notes string No Optional developer notes for the application. # #### * Only if applicable to application. # ===== Pre-installation Index ===== # # "pre-install": { - Required group of data containing pre-installation procedures. # "trusty": { - Different releases may have different operations. # "variable": "data" - See table below for possible operations. # }, # "all": { - Use "all" to specify all other releases. This should be last. # "variable": "data" If there is only one instruction, # } # } # # method string Yes Pre-configuration methods. Multiple can be specified with a plus '+'. # "skip" = Package is already in archive. # "ppa" = Add a PPA. Specify (2), optionally (1). # "partner-repo" = Add the Ubuntu Partners Repo. # "manual" = Get keys and write a sources.list file. (3) # source-file (1) string No Source file to update, excluding the ".list" extension. # enable-ppa (2) string * Name of PPA to add, eg. "ppa:somebody/someapp". # apt-key-url (3) string * Retrieve the key from URL. # apt-key-server (3) list * Retrieve the key from a server. # "server-address" = Eg. "keyserver.ubuntu.com" # "key" = Eg. "D2C19886" # apt-sources (3) list * Contents for the sources file. Each variable is a new line. # #### These keys words can be given as placeholders: # # CODENAME = Current Ubuntu release, eg. "xenial". # OSVERSION = Current Ubuntu version, eg "16.04". # def reload_index(self): try: print('[Apps] Reading index...') json_path = os.path.abspath(os.path.join(app._data_path, 'js/applications.json')) with open(json_path) as data_file: self.index = json.load(data_file) print('[Apps] Successfully loaded index.') except Exception as e: self.index = None print("[Apps] ERROR: Software Index JSON is invalid or missing!") print("------------------------------------------------------------") print("Exception:") print(str(e)) print("------------------------------------------------------------") def set_app_info(self, category, program_id): self.app_name = self.index[category][program_id]['name'] self.app_img = self.index[category][program_id]['img'] self.app_main_package = self.index[category][program_id]['main-package'] self.app_launch_command = self.index[category][program_id]['launch-command'] self.app_upgrade_only = False try: if self.index[category][program_id]['upgradable']: self.app_upgrade_only = True self.app_upgrade_packages = self.index[category][program_id]['upgrade-packages'] except: self.app_upgrade_only = False if not self.app_upgrade_only: self.app_install_packages = self.index[category][program_id]['install-packages'] self.app_remove_packages = self.index[category][program_id]['remove-packages'] self.app_description = '' for line in self.index[category][program_id]['description']: self.app_description = self.app_description + ' ' + line self.app_alternate_to = self.index[category][program_id]['alternate-to'] self.app_subcategory = self.index[category][program_id]['subcategory'] self.app_open_source = self.index[category][program_id]['open-source'] self.app_url_info = self.index[category][program_id]['url-info'] self.app_url_android = self.index[category][program_id]['url-android'] self.app_url_ios = self.index[category][program_id]['url-ios'] self.app_arch = self.index[category][program_id]['arch'] self.app_releases = self.index[category][program_id]['releases'] self.app_working = self.index[category][program_id]['working'] def populate_categories(self, webkit): ''' List all of the applications supported on the current architecture. ''' total_added = 0 total_skipped = 0 total_unsupported = 0 # Don't attempt to continue if the index is missing/incorrectly parsed. if not self.index: print('[Apps] ERROR: Application index not loaded. Cannot populate categories.') return # Strings str_nothing_here = _("Sorry, Welcome could not feature any software for this category that is compatible on this system.") str_upgraded = _("This application is set to receive the latest updates.") str_alternate_to = _('Alternative to:') str_hide = _("Hide") str_show = _("Show") str_install = _("Install") str_reinstall = _("Reinstall") str_remove = _("Remove") str_upgrade = _("Upgrade") str_launch = _("Launch") str_license = _("License") str_platform = _("Platform") str_category = _("Category") str_website = _("Website") str_screenshot = _("Screenshot") str_source = _("Source") str_source_ppa = ' ' str_source_manual = ' ' str_source_partner = ' ' + _('Canonical Partner Repository') str_source_skip = ' ' + _('Ubuntu Repository') str_unknown = _('Unknown') # Get the app data from each category and list them. for category in self.all_categories: arg.print_verbose('Apps', ' ------ Processing: ' + category + ' ------') # Convert to a list to work with. Sort alphabetically. category_items = list(self.index[category].keys()) category_items.sort() # Keep a count of apps in case there are none to list. apps_here = 0 # Keep track of the subcategories of the apps in this category so we can filter them. subcategories = [] # Enumerate each program in this category. for program_id in category_items: self.set_app_info(category, program_id) # Only list the program if it's working. if not self.app_working: arg.print_verbose('Apps', ' Unlisted: ' + self.app_name) total_skipped = total_skipped + 1 continue # Only list the program if it supports the current architecture in use. supported = False supported_arch = False supported_release = False for architecture in self.app_arch.split(','): if architecture == systemstate.arch: supported_arch = True # Only list the program if it's available for the current release. for release in self.app_releases.split(','): if release == systemstate.codename: supported_release = True if supported_arch and supported_release: supported = True if not supported: arg.print_verbose('Apps', ' Unsupported: ' + self.app_name + ' (Only for architectures: ' + self.app_arch + ' and releases: ' + self.app_releases + ')' ) total_unsupported = total_unsupported + 1 continue # If the app has made it this far, it can be added to the grid. # CSS breaks with dots (.), so any must become hyphens (-). arg.print_verbose('Apps', ' Added: ' + self.app_name) subcategories.append(self.app_subcategory) html_buffer = '' css_class = program_id.replace('.','-') css_subcategory = self.app_subcategory.replace(' ','-') # "Normal" packages that can be installed/removed by the user. if self.app_open_source: html_buffer = html_buffer + '
' else: html_buffer = html_buffer + '
' html_buffer = html_buffer + '
' html_buffer = html_buffer + '
' html_buffer = html_buffer + '' html_buffer = html_buffer + '' html_buffer = html_buffer + '
' html_buffer = html_buffer + '

' + self.app_name + '

' html_buffer = html_buffer + '

' + self.app_description + '

' # Check any "Upgrade" packages if the PPA has already been added. upgraded = False if self.app_upgrade_only: try: listname = dynamicapps.index[category][program_id]['pre-install']['all']['source-file'] listname = listname.replace('OSVERSION',preinstallation.os_version).replace('CODENAME',preinstallation.codename) if os.path.exists(os.path.join('/', 'etc', 'apt', 'sources.list.d', listname+'.list')): upgraded = True html_buffer = html_buffer + '
' + str_upgraded + '
' except: pass if not self.app_alternate_to == None: html_buffer = html_buffer + '
  • ' + str_alternate_to + ' ' + self.app_alternate_to + '
' html_buffer = html_buffer + '

' html_buffer = html_buffer + ' ' + str_show + ' ' html_buffer = html_buffer + ' ' # "Regular" packages - can be installed or removed with one-click by the user. if not self.app_upgrade_only: html_buffer = html_buffer + '  ' html_buffer = html_buffer + '  ' + str_install + ' ' html_buffer = html_buffer + ' ' html_buffer = html_buffer + ' ' # "Upgradable" packages - usually pre-installed but have a more up-to-date repository. if self.app_upgrade_only: arg.print_verbose('Apps', 'Upgrade: ' + self.app_name) if not upgraded: html_buffer = html_buffer + '  ' + str_upgrade + ' ' if not self.app_launch_command == None: html_buffer = html_buffer + '  ' + str_launch + ' ' # More details section. html_buffer = html_buffer + '



' # Append buffer to page webkit.execute_script('$("#' + category + '").append(\'' + html_buffer + '\')') webkit.execute_script('$("#info-hide-' + css_class + '").hide()') # Keep track of how many apps added. apps_here = apps_here + 1 total_added = total_added + 1 # Display a message if there is nothing for this category. if apps_here == 0: webkit.execute_script('$("#' + category + '").append("

  ' + str_nothing_here + '

")') # Post actions to page ## Colour the architecture currently in use. webkit.execute_script('$(".' + systemstate.arch + '").addClass("arch-in-use")') # Process filters for this category. filters = list(set(subcategories)) filters.sort() for string in filters: css_subcategory = string.replace(' ','-') webkit.execute_script('$("#Filter-' + category + '").append(\'\')') # "Stats for nerds" total_apps = total_added + total_skipped + total_unsupported arg.print_verbose('Apps','------------------') arg.print_verbose('Apps','Applications added: ' + str(total_added)) arg.print_verbose('Apps','Applications unsupported on this architecture: ' + str(total_unsupported)) arg.print_verbose('Apps','Applications that are broken or not suitable for inclusion: ' + str(total_skipped)) arg.print_verbose('Apps','Total number of applications: ' + str(total_apps)) arg.print_verbose('Apps','------------------') def populate_featured_apps(self, webkit): arg.print_verbose('Apps', '---- Populating Featured Apps Grid ----') # Randomly generate a list of apps to feature if supported on this architecture. possible_apps = [] for category in self.all_categories: category_items = list(self.index[category].keys()) for program_id in category_items: if systemstate.arch in self.index[category][program_id]['arch']: possible_apps.append(self.index[category][program_id]['img']) random.shuffle(possible_apps) for no in range(0,17): arg.print_verbose('Apps', str(no) + '. ' + possible_apps[no]) webkit.execute_script("addToGrid('" + possible_apps[no] + "');") webkit.execute_script("initGrid();") arg.print_verbose('Apps','------------------') def modify_app(self, webkit, action, program_id): ''' Installs, removes or upgrades an application. ''' # Indicate changes are in progress. css_class = program_id.replace('.','-') webkit.execute_script("$('." + css_class + "-applying').show();") webkit.execute_script("$('." + css_class + "-launch').hide();") webkit.execute_script("$('." + css_class + "-install').hide();") webkit.execute_script("$('." + css_class + "-reinstall').hide();") webkit.execute_script("$('." + css_class + "-remove').hide();") webkit.execute_script("$('." + css_class + "-upgrade').hide();") webkit.execute_script("$('." + css_class + "-text').css('color','#000');") # Text to display when applying changes. install_text = _("Installing...") remove_text = _("Removing...") upgrade_text = _("Upgrading...") # Asynchronous apt process if action == 'install': webkit.execute_script("$('." + css_class + "-applying-status').html('" + install_text + "');") preinstallation.process_packages(program_id, 'install') elif action == 'remove': webkit.execute_script("$('." + css_class + "-applying-status').html('" + remove_text + "');") preinstallation.process_packages(program_id, 'remove') elif action == 'upgrade': webkit.execute_script("$('." + css_class + "-applying-status').html('" + upgrade_text + "');") preinstallation.process_packages(program_id, 'upgrade') else: print('[Apps] An unknown action was requested.') # Refresh the page to reflect changes (if any). self._apt_cache.close() self._apt_cache = apt.Cache() self.update_app_status(webkit, program_id) def update_app_status(self, webkit, program_id): ''' Update the web page for an individual application. ''' # Don't attempt to continue if the index is missing/incorrectly parsed. if not self.index: print('[Apps] ERROR: Application index not loaded. Cannot update application status.') return # Check whether the application is installed or not. main_package = self.get_attribute_for_app(program_id, 'main-package') try: if self._apt_cache[main_package].is_installed: this_installed = True arg.print_verbose('Apps', ' Installed: ' + main_package) else: this_installed = False arg.print_verbose('Apps', 'Not present: ' + main_package) except: this_installed = False arg.print_verbose('Apps', 'Not present: ' + main_package) # Replace any dots with dashes, as they are unsupported in CSS. css_class = program_id.replace('.','-') # Update appearance on this page. webkit.execute_script("$('." + css_class + "-applying').hide();") if this_installed: webkit.execute_script("$('." + css_class + "-launch').show();") webkit.execute_script("$('." + css_class + "-install').hide();") webkit.execute_script("$('." + css_class + "-reinstall').show();") webkit.execute_script("$('." + css_class + "-remove').show();") webkit.execute_script("$('." + css_class + "-upgrade').show();") else: webkit.execute_script("$('." + css_class + "-launch').hide();") webkit.execute_script("$('." + css_class + "-install').show();") webkit.execute_script("$('." + css_class + "-reinstall').hide();") webkit.execute_script("$('." + css_class + "-remove').hide();") webkit.execute_script("$('." + css_class + "-upgrade').hide();") def update_all_app_status(self, webkit): ''' Update the webpage whether all indexed applications are installed or not. ''' # Don't attempt to continue if the index is missing/incorrectly parsed. if not self.index: print('[Apps] ERROR: Application index not loaded. Cannot update page.') return # Enumerate each program and check each one from the index. arg.print_verbose('Apps', '---- Checking cache for installed applications ----') for category in self.all_categories: category_items = list(self.index[category].keys()) for program_id in category_items: main_package = self.index[category][program_id]['main-package'] # Only check if it's supported on this architecture. if systemstate.arch in self.index[category][program_id]['arch']: self.update_app_status(webkit, program_id) else: continue arg.print_verbose('Apps', '----------------------------------------') def get_attribute_for_app(self, requested_id, attribute): ''' Retrieves a specific attribute from a listed application, without specifying its category. ''' for category in list(self.index.keys()): category_items = list(self.index[category].keys()) for program_id in category_items: if program_id == requested_id: if not attribute == 'category': return self.index[category][program_id][attribute] else: return category def launch_app(self, appid): ''' Launch an application directly from Welcome ''' program_name = self.get_attribute_for_app(appid, 'name') program_command = self.get_attribute_for_app(appid, 'launch-command') print('[Apps] Launched "' + program_name + '" (Command: "' + program_command + '").') try: subprocess.Popen(program_command.split(' ')) except: print('[Apps] Failed to launch command: ' + program_command) title = _("Software Boutique") ok_label = _("OK") text_error = _("An error occurred while launching PROGRAM_NAME. Please consider re-installing the application.").replace('PROGRAM_NAME', program_name) + \ '\n\n' + _("Command:") + ' "' + program_command + '"' messagebox = subprocess.Popen(['zenity', '--error', '--title=' + title, "--text=" + text_error, "--ok-label=" + ok_label, '--window-icon=error', '--timeout=15']) def apply_filter(self, webkit, filter_value, nonfree_toggle=False): sub_css_class = 'filter-' + filter_value # Toggle visibility of non-free software. if nonfree_toggle: if self.hide_non_free: self.hide_non_free = False webkit.execute_script('$("#nonFreeCheckBox").addClass("fa-square");') webkit.execute_script('$("#nonFreeCheckBox").removeClass("fa-check-square");') else: self.hide_non_free = True webkit.execute_script('$("#nonFreeCheckBox").removeClass("fa-square");') webkit.execute_script('$("#nonFreeCheckBox").addClass("fa-check-square");') if filter_value == 'none': arg.print_verbose('Apps','Filter reset.') webkit.execute_script('$(".app-entry").show();') if self.hide_non_free: arg.print_verbose('Apps','Hiding all proprietary software.') webkit.execute_script('$(".proprietary").hide();') return else: arg.print_verbose('Apps','Applying filter: ' + filter_value) webkit.execute_script('$(".app-entry").hide();') for category in self.all_categories: category_items = list(self.index[category].keys()) for program_id in category_items: app_subcategory = self.index[category][program_id]['subcategory'].replace(' ','-') app_open_source = self.index[category][program_id]['open-source'] # If the application is closed source and we're told to hide it. if not app_open_source and self.hide_non_free: webkit.execute_script('$("#' + program_id.replace('.','-') + '").hide();') continue # Only show if subcategory matches. if app_subcategory.replace(' ','-') == filter_value: webkit.execute_script('$("#' + program_id.replace('.','-') + '").show();') def show_screenshot(self, filename): ssw = ScreenshotWindow(filename) class ScreenshotWindow(Gtk.Window): ''' Displays a simple window when enlarging a screenshot. ''' # FIXME: Destroy this window when finished as it prevents the app from closing via the "Close" button and bloats memory. def __init__(self, filename): # Strings for this child window. title_string = 'Preview Screenshot' close_string = 'Close' path = app._data_path + '/img/applications/screenshots/' + filename + '.jpg' # Build a basic pop up window containing the screenshot at its full dimensions. Gtk.Window.__init__(self, title=title_string) self.overlay = Gtk.Overlay() self.add(self.overlay) self.background = Gtk.Image.new_from_file(path) self.overlay.add(self.background) self.grid = Gtk.Grid() self.overlay.add_overlay(self.grid) self.connect('button-press-event', self.destroy_window) # Click anywhere to close the window. self.connect('delete-event', Gtk.main_quit) self.set_position(Gtk.WindowPosition.CENTER) self.set_resizable(False) # FIXME: Set the cursor to a hand, like it was a link. #~ self.get_root_window().set_cursor(Gdk.Cursor(Gdk.CursorType.HAND1)) self.show_all() Gtk.main() def destroy_window(self, widget, dummy=None): self.destroy() class Arguments(object): '''Check arguments passed the application.''' def __init__(self): self.verbose_enabled = False self.simulate_arch = None self.simulate_session = None self.simulate_codename = None self.simulate_no_connection = False self.simulate_force_connection = False self.jump_software_page = False self.simulate_software_changes = False self.locale = None self.jump_to = None self.font_dpi_override = None for arg in sys.argv: if arg == '--help': print('\nUbuntu MATE Welcome Parameters\n Intended for debugging and testing purposes only!\n') print('\nUsage: ubuntu-mate-welcome [arguments]') print(' -v --verbose Show more details.') print(' --force-arch= Simulate a specific architecture.') print(' "i386", "amd64" or "armhf" or "powerpc"') print(' --force-session= Simulate a specific architecture.') print(' "guest", "live" or "pi" or "vbox"') print(' --force-codename= Simulate a specific Ubuntu MATE codename release.') print(' Examples: "trusty", "wily" or "xenial"') print(' --force-no-net Simulate no internet connection.') print(' --force-net Simulate a working internet connection.') print(' --software-only Open Welcome only for the software selections.') print(' --simulate-changes Simulate software package changes without modifying the system.') print(' --locale= Locale to use e.g. fr_FR.') print(' --jump-to= Open a specific page, excluding html extension.') print(' --font-dpi= Override the font size by specifying a font DPI.') print('') exit() if arg == '--verbose' or arg == '-v': print('[Debug] Verbose mode enabled.') self.verbose_enabled = True if arg.startswith('--force-arch'): try: self.simulate_arch = arg.split('--force-arch=')[1] if not self.simulate_arch == 'i386' and not self.simulate_arch == 'amd64' and not self.simulate_arch == 'armhf' and not self.simulate_arch == 'powerpc': print('[Debug] Unrecognised architecture: ' + self.simulate_arch) exit() else: print('[Debug] Simulating architecture: ' + self.simulate_arch) except: print('[Debug] Invalid arguments for "--force-arch"') exit() if arg.startswith('--force-session'): try: self.simulate_session = arg.split('--force-session=')[1] if not self.simulate_session == 'guest' and not self.simulate_session == 'live' and not self.simulate_session == 'pi' and not self.simulate_session == 'vbox': print('[Debug] Unrecognised session type: ' + self.simulate_session) exit() else: print('[Debug] Simulating session: ' + self.simulate_session) except: print('[Debug] Invalid arguments for "--force-session"') exit() if arg.startswith('--force-codename'): self.simulate_codename = arg.split('--force-codename=')[1] print('[Debug] Simulating Ubuntu MATE release: ' + self.simulate_codename) if arg == '--force-no-net': print('[Debug] Simulating the application without an internet connection.') self.simulate_no_connection = True if arg == '--force-net': print('[Debug] Forcing the application to think we\'re connected with an internet connection.') self.simulate_force_connection = True if arg == '--software-only': print('[Welcome] Starting in software selections only mode.') self.jump_software_page = True if arg == '--simulate-changes': print('[Debug] Any changes to software will be simulated without modifying the actual system.') self.simulate_software_changes = True if arg.startswith('--locale='): self.locale = arg.split('--locale=')[1] print('[Debug] Setting locale to: ' + self.locale) if arg.startswith('--jump-to='): self.jump_to = arg.split('--jump-to=')[1] print('[Debug] Opening page: ' + self.jump_to + '.html') if arg.startswith('--font-dpi='): try: self.font_dpi_override = int(arg.split('--font-dpi=')[1]) except: print('[Debug] Invalid Override Font DPI specified. Ignoring.') return print('[Debug] Overriding font DPI to ' + str(self.font_dpi_override) + '.') def print_verbose(self, feature, text): if self.verbose_enabled: print('[' + feature + '] ' + text) def override_arch(self): if not self.simulate_arch == None: systemstate.arch = self.simulate_arch def override_session(self): if not self.simulate_session == None: if self.simulate_session == 'vbox': systemstate.graphics_vendor = 'VirtualBox' systemstate.graphics_grep = 'VirtualBox' else: systemstate.session_type = self.simulate_session def override_codename(self): if not self.simulate_codename == None: systemstate.codename = self.simulate_codename if __name__ == "__main__": # Process any parameters passed to the program. arg = Arguments() # Application Initialization set_proc_title() systemstate = SystemState() app = WelcomeApp() dynamicapps = DynamicApps() preinstallation = PreInstallation() # Argument Overrides arg.override_arch() arg.override_session() arg.override_codename() print('[Welcome] Application Started.') app.run()