source: lliurex-mate-welcome/trunk/fuentes/ubuntu-mate-welcome @ 3621

Last change on this file since 3621 was 3621, checked in by alviboi, 3 years ago

Upload ubuntu-mate-welcome package

  • Property svn:executable set to *
File size: 119.2 KB
Line 
1#! /usr/bin/python3
2# -*- coding:utf-8 -*-
3#
4# Copyright 2012-2013 "Korora Project" <dev@kororaproject.org>
5# Copyright 2013 "Manjaro Linux" <support@manjaro.org>
6# Copyright 2014 Antergos
7# Copyright 2015-2016 Martin Wimpress <code@flexion.org>
8# Copyright 2015-2016 Luke Horwell <lukehorwell37+code@gmail.com>
9#
10# Ubuntu MATE Welcome is free software: you can redistribute it and/or modify
11# it under the temms of the GNU General Public License as published by
12# the Free Software Foundation, either version 3 of the License, or
13# (at your option) any later version.
14#
15# Ubuntu MATE Welcome is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with Ubuntu MATE Welcome. If not, see <http://www.gnu.org/licenses/>.
22#
23
24""" Welcome screen for Ubuntu MATE """
25
26import gi
27gi.require_version("Gdk", "3.0")
28gi.require_version("Gtk", "3.0")
29gi.require_version("Notify", "0.7")
30gi.require_version("WebKit", "3.0")
31
32import apt
33import errno
34import gettext
35import glob
36import inspect
37import json
38import locale
39import logging
40import os
41import platform
42import random
43import shutil
44import signal
45import socket
46import subprocess
47import sys
48import tempfile
49import urllib.request
50import webbrowser
51
52import urllib.error
53import urllib.parse
54import urllib.request
55from aptdaemon.client import AptClient
56from aptdaemon.gtk3widgets import AptErrorDialog, AptConfirmDialog, \
57                                  AptProgressDialog
58import aptdaemon.errors
59from aptdaemon.enums import *
60from gi.repository import GLib, Gio, GObject, Gdk, Gtk, Notify, WebKit
61from ctypes import cdll, byref, create_string_buffer
62from threading import Thread
63from getpass import getuser
64
65# i18n - if no translation is available use the inline strings
66t = gettext.translation("ubuntu-mate-welcome", "./locale", fallback=True)
67_ = t.gettext
68
69
70def goodbye(a=None, b=None):
71    # NOTE: _a_ and _b_ are passed via the close window 'delete-event'.
72    ''' Closing the program '''
73
74    # Refuse to quit if operations are in progress.
75    if dynamicapps.operations_busy:
76        print('[Welcome] WARNING: Software changes are in progress!')
77        title = _("Software Boutique")
78        text_busy = _('Software changes are in progress. Please allow them to complete before closing Welcome.')
79        ok_label = _("OK")
80        messagebox = subprocess.Popen(['zenity',
81                                 '--error',
82                                 '--title=' + title,
83                                 "--text=" + text_busy,
84                                 "--ok-label=" + ok_label,
85                                 '--window-icon=error',
86                                 '--timeout=9'])
87        return 1
88
89    else:
90        print('[Welcome] Application Closed.')
91        Gtk.main_quit()
92        # Be quite forceful, particularly those child screenshot windows.
93        exit()
94
95
96def set_proc_title(name=None):
97    '''Set the process title'''
98
99    if not name:
100        name = os.path.basename(sys.argv[0])
101
102    libc = cdll.LoadLibrary('libc.so.6')
103    buff = create_string_buffer(len(name)+1)
104    buff.value = name.encode("UTF-8")
105    ret = libc.prctl(15, byref(buff), 0, 0, 0)
106
107    if ret != 0:
108        print("Failed to set process title")
109
110    return ret
111
112
113class SimpleApt(object):
114    def __init__(self, packages, action):
115        self._timeout = 100
116        self.packages = packages
117        self.action = action
118        self.source_to_update = None
119        self.update_cache = False
120        self.loop = GLib.MainLoop()
121        self.client = AptClient()
122
123    def on_error(self, error):
124        dynamicapps.operations_busy = False
125        if isinstance(error, aptdaemon.errors.NotAuthorizedError):
126            # Silently ignore auth failures
127            return
128        elif not isinstance(error, aptdaemon.errors.TransactionFailed):
129            # Catch internal errors of the client
130            error = aptdaemon.errors.TransactionFailed(ERROR_UNKNOWN,
131                                                       str(error))
132        error_dialog = AptErrorDialog(error)
133        error_dialog.run()
134        error_dialog.hide()
135
136    def on_finished_fix_incomplete_install(self, transaction, status):
137        dynamicapps.operations_busy = False
138        self.loop.quit()
139        if status == 'exit-success':
140            Notify.init(_('Fixing incomplete install succeeded'))
141            apt_notify=Notify.Notification.new(_('Successfully fixed an incomplete install.'), _('Fixing the incomplete install was successful.'), 'dialog-information')
142            apt_notify.show()
143            return True
144        else:
145            Notify.init(_('Fixing incomplete install failed'))
146            apt_notify=Notify.Notification.new(_('Failed to fix incomplete install.'), _('Fixing the incomplete install failed.'), 'dialog-error')
147            apt_notify.show()
148            return False
149
150    def on_finished_fix_broken_depends(self, transaction, status):
151        dynamicapps.operations_busy = False
152        self.loop.quit()
153        if status == 'exit-success':
154            Notify.init(_('Fixing broken dependencies succeeded'))
155            apt_notify=Notify.Notification.new(_('Successfully fixed broken dependencies.'), _('Fixing the broken dependencies was successful.'), 'dialog-information')
156            apt_notify.show()
157            return True
158        else:
159            Notify.init(_('Fixing broken dependencies failed'))
160            apt_notify=Notify.Notification.new(_('Failed to fix broken dependencies.'), _('Fixing the broken dependencies failed.'), 'dialog-error')
161            apt_notify.show()
162            return False
163
164    def on_finished_update(self, transaction, status):
165        dynamicapps.operations_busy = False
166        # If the action is only to update do not display notifcations
167        if self.action == 'update':
168            self.loop.quit()
169            if status == 'exit-success':
170                return True
171            else:
172                return False
173        elif self.action == 'install':
174            if status != 'exit-success':
175                self.do_notify(status)
176                self.loop.quit()
177                return False
178
179            GLib.timeout_add(self._timeout,self.do_install)
180            return True
181        elif self.action == 'upgrade':
182            if status != 'exit-success':
183                self.do_notify(status)
184                self.loop.quit()
185                return False
186
187            GLib.timeout_add(self._timeout,self.do_upgrade)
188            return True
189
190    def on_finished_install(self, transaction, status):
191        dynamicapps.operations_busy = False
192        self.loop.quit()
193        if status != 'exit-success':
194            return False
195        else:
196            self.do_notify(status)
197
198    def on_finished_remove(self, transaction, status):
199        dynamicapps.operations_busy = False
200        self.loop.quit()
201        if status != 'exit-success':
202            return False
203        else:
204            self.do_notify(status)
205
206    def on_finished_upgrade(self, transaction, status):
207        dynamicapps.operations_busy = False
208        self.loop.quit()
209        if status != 'exit-success':
210            return False
211        else:
212            self.do_notify(status)
213
214    def do_notify(self, status):
215        print('Status: ' + status)
216        if self.action == 'install':
217            title = _('Install')
218            noun = _('Installation of ')
219            action = _('installed.')
220        elif self.action == 'remove':
221            title = _('Remove')
222            noun = _('Removal of ')
223            action = _('removed.')
224        elif self.action == 'upgrade':
225            title = _('Upgrade')
226            noun = _('Upgrade of ')
227            action = _('upgraded.')
228
229        # Do not show notifications when updating the cache
230        if self.action != 'update':
231            if status == 'exit-success':
232                Notify.init(title + ' ' + _('complete'))
233                apt_notify=Notify.Notification.new(title + ' ' + _('complete'), ', '.join(self.packages) + ' ' + _('has been successfully ') +action, 'dialog-information')
234            elif status == 'exit-cancelled':
235                Notify.init(title + ' ' + _('cancelled'))
236                apt_notify=Notify.Notification.new(title + ' ' + _('cancelled'), noun + ', '.join(self.packages) + ' ' + _('was cancelled.'), 'dialog-information')
237            else:
238                Notify.init(title + ' ' + _('failed'))
239                apt_notify=Notify.Notification.new(title + ' ' + _('failed'), noun + ', '.join(self.packages) + ' ' + _('failed.'), 'dialog-error')
240
241            apt_notify.show()
242
243    def do_fix_incomplete_install(self):
244        dynamicapps.operations_busy = True
245        # Corresponds to: dpkg --configure -a
246        apt_fix_incomplete = self.client.fix_incomplete_install()
247        apt_fix_incomplete.connect("finished",self.on_finished_fix_incomplete_install)
248
249        fix_incomplete_dialog = AptProgressDialog(apt_fix_incomplete)
250        fix_incomplete_dialog.run(close_on_finished=True, show_error=True,
251                reply_handler=lambda: True,
252                error_handler=self.on_error,
253                )
254        return False
255        dynamicapps.operations_busy = False
256
257    def do_fix_broken_depends(self):
258        dynamicapps.operations_busy = True
259        # Corresponds to: apt-get --fix-broken install
260        apt_fix_broken = self.client.fix_broken_depends()
261        apt_fix_broken.connect("finished",self.on_finished_fix_broken_depends)
262
263        fix_broken_dialog = AptProgressDialog(apt_fix_broken)
264        fix_broken_dialog.run(close_on_finished=True, show_error=True,
265                reply_handler=lambda: True,
266                error_handler=self.on_error,
267                )
268        return False
269        dynamicapps.operations_busy = False
270
271    def do_update(self):
272        if self.source_to_update:
273            apt_update = self.client.update_cache(self.source_to_update)
274        else:
275            apt_update = self.client.update_cache()
276        apt_update.connect("finished",self.on_finished_update)
277
278        update_dialog = AptProgressDialog(apt_update)
279        update_dialog.run(close_on_finished=True, show_error=True,
280                reply_handler=lambda: True,
281                error_handler=self.on_error,
282                )
283        return False
284
285    def do_install(self):
286        apt_install = self.client.install_packages(self.packages)
287        apt_install.connect("finished", self.on_finished_install)
288
289        install_dialog = AptProgressDialog(apt_install)
290        install_dialog.run(close_on_finished=True, show_error=True,
291                        reply_handler=lambda: True,
292                        error_handler=self.on_error,
293                        )
294        return False
295
296    def do_remove(self):
297        apt_remove = self.client.remove_packages(self.packages)
298        apt_remove.connect("finished", self.on_finished_remove)
299
300        remove_dialog = AptProgressDialog(apt_remove)
301        remove_dialog.run(close_on_finished=True, show_error=True,
302                        reply_handler=lambda: True,
303                        error_handler=self.on_error,
304                        )
305        return False
306
307    def do_upgrade(self):
308        apt_upgrade = self.client.upgrade_system(True)
309        apt_upgrade.connect("finished", self.on_finished_upgrade)
310
311        upgrade_dialog = AptProgressDialog(apt_upgrade)
312        upgrade_dialog.run(close_on_finished=True, show_error=True,
313                        reply_handler=lambda: True,
314                        error_handler=self.on_error,
315                        )
316        return False
317
318    def install_packages(self):
319        dynamicapps.operations_busy = True
320        if self.update_cache:
321            GLib.timeout_add(self._timeout,self.do_update)
322        else:
323            GLib.timeout_add(self._timeout,self.do_install)
324        self.loop.run()
325        dynamicapps.operations_busy = False
326
327    def remove_packages(self):
328        dynamicapps.operations_busy = True
329        GLib.timeout_add(self._timeout,self.do_remove)
330        self.loop.run()
331        dynamicapps.operations_busy = False
332
333    def upgrade_packages(self):
334        dynamicapps.operations_busy = True
335        if self.update_cache:
336            GLib.timeout_add(self._timeout,self.do_update)
337        else:
338            GLib.timeout_add(self._timeout,self.do_upgrade)
339        self.loop.run()
340        dynamicapps.operations_busy = False
341
342    def fix_incomplete_install(self):
343        dynamicapps.operations_busy = True
344        GLib.timeout_add(self._timeout,self.do_fix_incomplete_install)
345        self.loop.run()
346        dynamicapps.operations_busy = False
347
348    def fix_broken_depends(self):
349        dynamicapps.operations_busy = True
350        GLib.timeout_add(self._timeout,self.do_fix_broken_depends)
351        self.loop.run()
352        dynamicapps.operations_busy = False
353
354def update_repos():
355    transaction = SimpleApt('', 'update')
356    transaction.update_cache = True
357    transaction.do_update()
358
359def fix_incomplete_install():
360    transaction = SimpleApt('', 'fix-incomplete-install')
361    transaction.fix_incomplete_install()
362
363def fix_broken_depends():
364    transaction = SimpleApt('', 'fix-broken-depends')
365    transaction.fix_broken_depends()
366
367def mkdir_p(path):
368    try:
369        os.makedirs(path)
370    except OSError as exc: # Python >2.5
371        if exc.errno == errno.EEXIST and os.path.isdir(path):
372            pass
373        else:
374            raise
375
376def get_aacs_db():
377    home_dir = GLib.get_home_dir()
378    key_url = 'http://www.labdv.com/aacs/KEYDB.cfg'
379    key_db = os.path.join(home_dir, '.config', 'aacs', 'KEYDB.cfg')
380    mkdir_p(os.path.join(home_dir, '.config', 'aacs'))
381    print('[AACS] Getting ' + key_url + ' and saving as ' + key_db)
382
383    # Download the file from `key_url` and save it locally under `file_name`:
384    try:
385        with urllib.request.urlopen(key_url) as response, open(key_db, 'wb') as out_file:
386            data = response.read() # a `bytes` object
387            out_file.write(data)
388
389        Notify.init(_('Blu-ray AACS database install succeeded'))
390        aacs_notify=Notify.Notification.new(_('Successfully installed the Blu-ray AACS database.'), _('Installation of the Blu-ray AACS database was successful.'), 'dialog-information')
391        aacs_notify.show()
392    except:
393        Notify.init(_('Blu-ray AACS database install failed'))
394        aacs_notify=Notify.Notification.new(_('Failed to install the Blu-ray AACS database.'), _('Installation of the Blu-ray AACS database failed.'), 'dialog-error')
395        aacs_notify.show()
396
397class PreInstallation(object):
398    #
399    #   See the JSON Structure in the `DynamicApps` class on
400    #    how to specify pre-configuration actions in `applications.json`
401    #
402
403    def __init__(self):
404        # Always ensure we have the correct variables.
405        self.os_version = platform.dist()[1]
406        self.codename = platform.dist()[2]
407        arg.print_verbose("Pre-Install", "System is running Ubuntu " + self.os_version + " (" + self.codename + ")")
408
409    def process_packages(self, program_id, action):
410        simulating = arg.simulate_software_changes
411        print(' ')
412
413        # Get category for this program, which can be used to retrieve data later.
414        category = dynamicapps.get_attribute_for_app(program_id, 'category')
415
416        try:
417            preconfig = dynamicapps.index[category][program_id]['pre-install']
418        except:
419            print('[Pre-Install] Missing pre-configuration data for "' + program_id + '". Refusing to continue.')
420            return
421
422        try:
423            if action == 'install':
424                packages = dynamicapps.index[category][program_id]['install-packages']
425                print('[Apps] Packages to be installed:\n               ' + packages)
426            elif action == 'remove':
427                packages = dynamicapps.index[category][program_id]['remove-packages']
428                print('[Apps] Packages to be removed:\n               ' + packages)
429            elif action == 'upgrade':
430                packages = dynamicapps.index[category][program_id]['upgrade-packages']
431                print('[Apps] Packages to be upgraded:\n               ' + packages)
432            else:
433                print('[Apps] ERROR: Invalid action was requested.')
434                return
435        except:
436            print('[Apps] ERROR: No packages retrieved for requested action.')
437            return
438
439        # Validate that we have packages to work with.
440        if len(packages):
441            packages = packages.split(',')
442        else:
443            print('[Apps] ERROR: No package(s) supplied for "' + program_id + '".')
444            return
445        transaction = SimpleApt(packages, action)
446
447        # Function to run privileged commands.
448        def run_task(function):
449            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])
450
451        # Determine if any pre-configuration is specific to a codename.
452        try:
453            preinstall = dynamicapps.index[category][program_id]['pre-install']
454            codenames = list(preinstall.keys())
455        except:
456            print('[Pre-Install] No pre-install data specified for "' + program_id + '". This application entry is invalid.')
457            return
458        arg.print_verbose('Pre-Install','Available configurations: ' + str(codenames))
459        target = None
460        for name in codenames:
461            if name == self.codename:
462                target = name
463                break
464        if not target:
465                target = 'all'
466                arg.print_verbose('Pre-Install','Using "all" pre-configuration.')
467        else:
468            arg.print_verbose('Pre-Install','Using configuration for: "' + target + '".')
469
470        methods = preinstall[target]['method'].split('+')
471        if not methods:
472            print('[Pre-Install] No pre-install method was specified. The index is invalid.')
473        else:
474            arg.print_verbose('Pre-Install','Configuration changes: ' + str(methods))
475
476        # Perform any pre-configuration, if necessary.
477        if action == 'install' or action == 'upgrade':
478            for method in methods:
479                if method == 'skip':
480                    arg.print_verbose('Pre-Install','No need! The package is already in the archives.')
481                    continue
482
483                elif method == 'partner-repo':
484                    print('[Pre-Install] Enabling the Ubuntu partner repository.')
485                    if not simulating:
486                        run_task('enable_partner_repository')
487                        transaction.update_cache = True
488
489                elif method == 'ppa':
490                    try:
491                        ppa = preinstall[target]['enable-ppa']
492                    except:
493                        print('[Pre-Install] Missing "enable-ppa" attribute. Cannot add PPA as requested.')
494                        return
495                    print('[Pre-Install] Adding PPA: "' + ppa + '" and updating cache.')
496                    if not simulating:
497                        run_task('enable_ppa')
498                        transaction.update_cache = True
499                    try:
500                        source_file = preinstall[target]['source-file'].replace('OSVERSION',self.os_version).replace('CODENAME',self.codename)
501                        print('[Pre-Install] Updating Apt Source: "' + source_file + '.list"')
502                        if not simulating:
503                            transaction.source_to_update = source_file + '.list'
504                    except:
505                        arg.print_verbose('Pre-Install','Updating entire cache as no source file was specified.')
506
507                elif method == 'manual':
508                    # Do we get the apt key from a URL?
509                    try:
510                        apt_key_url = preinstall[target]['apt-key-url']
511                        print('[Pre-Install] Getting Apt key from URL: "' + apt_key_url + '"')
512                        if not simulating:
513                            run_task('add_apt_key_from_url')
514                    except:
515                        arg.print_verbose('Pre-Install', 'No apt key to retrieve from a URL.')
516
517                    # Do we get the apt key from the server?
518                    try:
519                        apt_key_server = preinstall[target]['apt-key-server'][0]
520                        apt_key_key =    preinstall[target]['apt-key-server'][1]
521                        print('[Pre-Install] Getting key "' + apt_key_key + '" from keyserver: "' + apt_key_server + '"')
522                        if not simulating:
523                            run_task('add_apt_key_from_keyserver')
524                    except:
525                        arg.print_verbose('Pre-Install', 'No apt key to retrieve from a key server.')
526
527                    # Do we need to add an apt source file?
528                    try:
529                        source = preinstall[target]['apt-sources']
530                        source_file = preinstall[target]['source-file'].replace('OSVERSION',self.os_version).replace('CODENAME',self.codename)
531                        print('[Pre-Install] Writing source file: ' + source_file + '.list')
532                        print('              -------- Start of file ------')
533                        for line in source:
534                            print('              ' + line.replace('OSVERSION',self.os_version).replace('CODENAME',self.codename))
535                        print('              -------- End of file ------')
536                        try:
537                            print('[Pre-Install] Updating Apt Source: ' + source_file + '.list')
538                            if not simulating:
539                                run_task('add_apt_sources')
540                                transaction.source_to_update = source_file + '.list'
541                                transaction.update_cache = True
542                        except:
543                            arg.print_verbose('Pre-Install','Failed to add apt sources!')
544                    except:
545                        arg.print_verbose('Pre-Install','No source data or source file to write.')
546
547        elif action == 'remove':
548            try:
549                # The function uses wild cards, so we don't need to worry about being explict.
550                listname = preinstall[target]['source-file'].replace('CODENAME','').replace('OSVERSION','')
551                if simulating:
552                    print('[Simulation] Deleting Apt Source: ' + listname)
553                else:
554                    run_task('del_apt_sources')
555            except:
556                print('[Pre-Install]', 'No apt source specified, so none will be removed.')
557
558
559        # Pre-configuration complete. Now perform the operations, unless this was just a simulation.
560        if simulating:
561            print('[Pre-Install] Simulation flag active. No changes will be performed.')
562            return
563        else:
564            if transaction.action == 'install':
565                transaction.install_packages()
566            elif transaction.action == 'remove':
567                transaction.remove_packages()
568            elif transaction.action == 'upgrade':
569                transaction.upgrade_packages()
570
571
572class WelcomeConfig(object):
573    """ Manages Welcome configuration """
574    def __init__(self):
575        # store our base architecture
576        self.os_version = platform.dist()[1]
577        self.os_codename = platform.dist()[2]
578        self.os_title = 'Ubuntu MATE ' + self.os_version
579        self._arch = systemstate.arch
580
581        # store full path to our binary
582        self._welcome_bin_path = os.path.abspath(inspect.getfile(inspect.currentframe()))
583
584        # directory for the configuration
585        self._config_dir = os.path.expanduser('~/.config/ubuntu-mate/welcome/')
586
587        # autostart directory
588        self._autostart_dir = os.path.expanduser('~/.config/autostart/')
589
590        # full path to the autostart symlink
591        self._autostart_path = os.path.expanduser(os.path.join(self._autostart_dir, 'ubuntu-mate-welcome.desktop'))
592
593        # ensure our config and autostart directories exists
594        for _dir in [self._config_dir, self._autostart_dir]:
595            if not os.path.exists(_dir):
596                try:
597                    os.makedirs(_dir)
598                except OSError as err:
599                    print(err)
600                    pass
601
602        # does autostart symlink exist
603        self._autostart = os.path.exists(self._autostart_path)
604
605    @property
606    def autostart(self):
607        return self._autostart
608
609    @autostart.setter
610    def autostart(self, state):
611        if state and not os.path.exists(self._autostart_path):
612            # create the autostart symlink
613            try:
614                os.symlink('/usr/share/applications/ubuntu-mate-welcome.desktop', self._autostart_path)
615            except OSError as err:
616                print(err)
617                pass
618        elif not state and os.path.exists(self._autostart_path):
619            # remove the autostart symlink
620            try:
621                os.unlink(self._autostart_path)
622            except OSError as err:
623                print(err)
624                pass
625
626        # determine autostart state based on absence of the disable file
627        self._autostart = os.path.exists(self._autostart_path)
628
629
630class AppView(WebKit.WebView):
631    def __init__(self, slide_list = None):
632        """
633            Args:
634            slide_list : A list of tuples containing the filenames of translated html files
635                         or, if no translation is available, the filename of the original
636                         untranslated slide
637        """
638        WebKit.WebView.__init__(self)
639        WebKit.WebView.__init__(self)
640        self._config = WelcomeConfig()
641        self._apt_cache = apt.Cache()
642        self.connect('load-finished', self._load_finished_cb)
643        self.connect('navigation-policy-decision-requested', self._nav_request_policy_decision_cb)
644        self.l_uri = None
645        self._slide_list = slide_list
646
647        self.set_zoom_level(systemstate.zoom_level)
648        print('[Welcome] Setting zoom level to: ' + str(systemstate.zoom_level))
649
650        # Disable right-click context menu as it isn't needed.
651        self.props.settings.props.enable_default_context_menu = False
652
653        # Perform a smooth transition for footer icons.
654        self.do_smooth_footer = False
655
656    def _push_config(self):
657        ### Global - On all pages ###
658        self.execute_script("$('#os_title').html('%s')" % self._config.os_title)
659        self.execute_script("$('#os_version').html('%s')" % self._config.os_version)
660        self.execute_script("$('#autostart').toggleClass('fa-check-square', %s).toggleClass('fa-square', %s)" % (json.dumps(self._config.autostart), json.dumps(not self._config.autostart)))
661
662        # If this is a Live session (booted from ISO) show the
663        # 'Install OS' button, if running on an installed system show
664        # the 'Install Software' button.
665        if systemstate.session_type == 'live':
666            self.execute_script("$('#install').show();")
667            self.execute_script("$('#software').hide();")
668            self.execute_script("$('.live-session').hide();")
669            self.execute_script("$('.live-session-only').show();")
670        else:
671            self.execute_script("$('#install').hide();")
672            self.execute_script("$('#software').show();")
673            self.execute_script("$('.live-session').show();")
674            self.execute_script("$('.live-session-only').hide();")
675
676        # If started from a Raspberry Pi.
677        if systemstate.session_type == 'pi':
678            self.execute_script("$('.rpi-only').show();")
679        else:
680            self.execute_script("$('.rpi-only').hide();")
681
682        # Display warnings if the user is not connected to the internet.
683        if systemstate.is_online:
684            self.execute_script("$('.offline').hide();")
685            self.execute_script("$('.online').show();")
686        else:
687            self.execute_script("$('.offline').show();")
688            self.execute_script("$('.online').hide();")
689
690        ## Social Links ##
691        footer_left = '<div id="social" class="pull-left"> \
692        <a href="cmd://link?https://twitter.com/lliurex" title="Twitter"><img src="img/social/twitter.svg"></a> \
693        <a href="cmd://link?http://mestreacasa.gva.es/web/lliurex/" title="lliurex"><img src="img/humanity/website.svg"></a> \
694        <a href="cmd://link?http://wiki.lliurex.net/Inicio" title="Wiki de LliureX"><img src="img/humanity/gtk-info.svg"></a> \
695        </div>'
696
697        ## Boutique Footer ##
698        str_subscribed = _("Set to retrieve the latest software listings.")
699        str_subscribe_link = _("Retrieve the latest software listings.")
700        str_subscribing = _("Please wait while the application is being updated...")
701        str_listing_version = _("Version:")
702
703        boutique_footer = '<div id="boutique-footer" class="pull-left"> \
704        <p hidden id="update-subscribed"><span class="fa fa-check"></span> ' + str_subscribed + '</p> \
705        <p hidden id="update-notification"><a href="cmd://subscribe-updates"><span class="fa fa-exclamation-circle"></span> ' + str_subscribe_link + '</a></p> \
706        <p hidden id="update-subscribing"><img src="img/welcome/processing-dark.gif" width="16px" height="16px"> ' + str_subscribing + '</p> \
707        <p><b>' + str_listing_version + '</b> <span id="boutique-version"></span></p> \
708        </div>'
709
710        # Do not show footer links on splash or software page.
711        if not arg.jump_software_page:
712            if not self.current_page == 'splash.html' and not self.current_page == 'software.html':
713                self.execute_script("$('#footer-global-left').html('" + footer_left + "');")
714
715        # Show the button depending on context.
716        footer_close = '<a href="cmd://quit" class="btn btn-inverse">' + _("Close") + '&zwnj;</a>'
717        footer_skip  = '<a onclick="continueToPage(true)" class="btn btn-inverse">' + _("Skip") + '</a>'
718
719        if self.current_page == 'splash.html':
720            self.execute_script("$('#footer-global-right').html('" + footer_skip + "');")
721        elif arg.jump_software_page:
722            # Do not show a "Close" button for the Boutique.
723            pass
724        else:
725            self.execute_script("$('#footer-global-right').html('" + footer_close + "');")
726
727        # Smoothly fade in the footer links between pages.
728        #   splash → index
729        #   index ← → software
730        if self.do_smooth_footer or self.current_page == 'software.html':
731            self.do_smooth_footer = False
732            self.execute_script("$('#footer-left').hide();")
733            self.execute_script("$('#footer-left').fadeIn();")
734
735        # Individual Page Actions
736        ### Main Menu ###
737        if self.current_page == 'index.html':
738            if systemstate.session_type == 'guest':
739                # Disable features that are unavailable to guests.
740                self.execute_script("$('#gettingstarted').hide();")
741                self.execute_script("$('#software').hide();")
742                self.execute_script("$('#introduction').addClass('btn-success');")
743                self.execute_script("$('#community').addClass('btn-success');")
744
745            # Check whether the system is subscribed for receiving more up-to-date versions of Welcome.
746            self.execute_script('$("#update-subscribing").hide()')
747            if not systemstate.updates_subscribed:
748                if systemstate.is_online:
749                    self.execute_script('$("#update-notification").fadeIn("slow")')
750            else:
751                self.execute_script('$("#update-notification").hide()')
752
753            # Disable confetti on machines that may suffer performance issues.
754            if systemstate.arch == 'armhf':
755                self.execute_script('var disable_confetti = true;')
756            elif systemstate.arch == 'powerpc':
757                self.execute_script('var disable_confetti = true;')
758            else:
759                self.execute_script('var disable_confetti = false;')
760
761            # Special event strings.
762            self.execute_script('var days_in = "&zwnj;in&zwnj;"')
763            self.execute_script('var future_days = "&zwnj;days.&zwnj;"')
764            self.execute_script('var days_ago = "&zwnj;days ago.&zwnj;"')
765            self.execute_script('var yesterday = "&zwnj;yesterday.&zwnj;"')
766            self.execute_script('var tomorrow = "&zwnj;tomorrow.&zwnj;"')
767            self.execute_script('var years_ago = "&zwnj;years ago today.&zwnj;"')
768            self.execute_script('var today_string = "&zwnj;today.&zwnj;"')
769            self.execute_script('var years_old = "&zwnj;years old&zwnj;"')
770
771            self.execute_script('var flavour_anniversary_future  = "&zwnj;Ubuntu MATE\'s official flavour anniversary&zwnj;"')
772            self.execute_script('var flavour_anniversary_present = "&zwnj;Ubuntu MATE become an official flavour&zwnj;"')
773            self.execute_script('var flavour_anniversary_past    = "&zwnj;Ubuntu MATE\'s official flavour anniversary was&zwnj;"')
774
775            self.execute_script('var project_birthday_future  = "&zwnj;Ubuntu MATE will be&zwnj;"')
776            self.execute_script('var project_birthday_present = "&zwnj;Ubuntu MATE is&zwnj;"')
777            self.execute_script('var project_birthday_past    = "&zwnj;Ubuntu MATE turned&zwnj;"')
778
779            self.execute_script('var project_birthday         = "&zwnj;Happy Birthday!&zwnj;"')
780            self.execute_script('var celebrate_new_year       = "&zwnj;Happy New Year from Ubuntu MATE!&zwnj;"')
781
782            self.execute_script('var project_release_future   = "&zwnj;will be released&zwnj;"')
783            self.execute_script('var project_release_present  = "&zwnj;is released today!&zwnj;"')
784            self.execute_script('var project_release_past     = "&zwnj;was released&zwnj;"')
785            self.execute_script('var project_release_thanks   = "&zwnj;Thank you for testing&zwnj;"')
786
787            self.execute_script('checkDates();')
788
789        ### Splash ###
790        if self.current_page == 'splash.html':
791            self.do_smooth_footer = True
792            # Determine which screen to show after the splash screen.
793            if systemstate.session_type == 'live':
794                self.execute_script('var splashNextPage = "hellolive"')
795            elif systemstate.session_type == 'guest':
796                self.execute_script('var splashNextPage = "helloguest"')
797            else:
798                self.execute_script('var splashNextPage = "index"')
799
800            # Smoothly fade footer when entering main menu.
801            self.splash_finished = True
802
803        ### Chat Page ###
804        if self.current_page == 'chatroom.html':
805            if self._apt_cache['hexchat'].is_installed:
806                self.execute_script("$('.hexchat').show();")
807                self.execute_script("$('.webchat').hide();")
808            else:
809                self.execute_script("$('.hexchat').hide();")
810                self.execute_script("$('.webchat').show();")
811
812        ### Getting Started Page ###
813        if self.current_page == 'gettingstarted.html':
814            # Display information tailored to graphics vendor (Getting Started / Drivers)
815            self.execute_script('var graphicsVendor = "' + systemstate.graphics_vendor + '";')
816            self.execute_script('var graphicsGrep = "' + systemstate.graphics_grep + '";')
817            self.execute_script('$("#boot-mode").html("' + systemstate.boot_mode + '")')
818
819            # Update any applications featured on these pages.
820            dynamicapps.update_app_status(self, 'hardinfo')
821            dynamicapps.update_app_status(self, 'gparted')
822            dynamicapps.update_app_status(self, 'gnome-disk-utility')
823            dynamicapps.update_app_status(self, 'mate-disk-usage-analyzer')
824            dynamicapps.update_app_status(self, 'mate-system-monitor')
825            dynamicapps.update_app_status(self, 'psensor')
826            dynamicapps.update_app_status(self, 'boot-repair')
827            dynamicapps.update_app_status(self, 'codecs')
828            dynamicapps.update_app_status(self, 'firmware')
829            dynamicapps.update_app_status(self, 'hp-printer')
830            dynamicapps.update_app_status(self, 'keyboard-chinese')
831            dynamicapps.update_app_status(self, 'keyboard-japanese')
832            dynamicapps.update_app_status(self, 'keyboard-korean')
833
834        ### Software Page ###
835        if self.current_page == 'software.html':
836            dynamicapps.hide_non_free = False
837            self.do_smooth_footer = True
838
839            # If loading a minimal "Get More Software" only page.
840            if arg.jump_software_page:
841                self.execute_script('$("#menu-button").hide()')
842                self.execute_script('$("#navigation-title").html("<span id=\'navigation-sub-title\'>Curated software collection</span>")')
843                self.execute_script('$("#navigation-sub-title").css("color","#DED9CB")')
844
845            # Pass 'Servers' variable used for one-click server links.
846            self.execute_script('var server_string = "' + _("Servers") + '"')
847
848            # Dynamically load application lists.
849            dynamicapps.populate_categories(self)
850            dynamicapps.update_all_app_status(self)
851            dynamicapps.populate_featured_apps(self)
852
853            # Show a different footer in the Boutique.
854            self.execute_script("$('#footer-global-left').html('" + boutique_footer + "');")
855
856            # Set version and subscription details.
857            self.execute_script('$("#boutique-version").html("' + systemstate.welcome_version + '")')
858            if systemstate.updates_subscribed:
859                self.execute_script('$("#update-subscribed").show()')
860            else:
861                self.execute_script('$("#update-notification").show()')
862
863        ### Raspberry Pi Page ###
864        if self.current_page == 'rpi.html':
865            # Check file system resize flag.
866            systemstate.rpi_resize('check', self)
867
868        ### Donate ###
869        if self.current_page == 'donate.html':
870            # Pass translatable short-hand month strings for the supporters grid.
871            self.execute_script('short_jan = "' + _("Jan") + '"')
872            self.execute_script('short_feb = "' + _("Feb") + '"')
873            self.execute_script('short_mar = "' + _("Mar") + '"')
874            self.execute_script('short_apr = "' + _("Apr") + '"')
875            self.execute_script('short_may = "' + _("May") + '"')
876            self.execute_script('short_jun = "' + _("Jun") + '"')
877            self.execute_script('short_jul = "' + _("Jul") + '"')
878            self.execute_script('short_aug = "' + _("Aug") + '"')
879            self.execute_script('short_sep = "' + _("Sep") + '"')
880            self.execute_script('short_oct = "' + _("Oct") + '"')
881            self.execute_script('short_nov = "' + _("Nov") + '"')
882            self.execute_script('short_dec = "' + _("Dec") + '"')
883
884    def _load_finished_cb(self, view, frame):
885        self._push_config()
886
887    def _nav_request_policy_decision_cb(self, view, frame, net_req, nav_act, pol_dec):
888        uri = net_req.get_uri()
889        self.current_page = uri.rsplit('/', 1)[1]
890
891        try:
892            if uri.index('#') > 0:
893                uri = uri[:uri.index('#')]
894        except ValueError:
895            pass
896
897        if uri == self.l_uri:
898            pol_dec.use()
899            return True
900
901        if uri.startswith('cmd://'):
902            self._do_command(uri)
903            return True
904
905        self.l_uri = uri
906
907        if self._slide_list is None:
908            # no translations have to been specified, so we can just load the specified page..
909            page = urllib.request.urlopen(uri)
910        else:
911            # find the slide in slide_list
912            head, slide_name = os.path.split(uri)
913            found = False
914            for slide in self._slide_list:
915                head, trans_slide_name = os.path.split(slide)
916                if slide_name == trans_slide_name:
917                    found = True
918                    # load the translated html
919                    trans_uri = urllib.parse.urljoin('file:', urllib.request.pathname2url(slide))
920                    page = urllib.request.urlopen(trans_uri)
921                    break
922
923            if not found:
924                # this should never happen, but if it does, recover by loading the originally specified page
925                arg.print_verbose('Translation','Couldn''t find slide %s when getting translation' %uri)
926                page = urllib.request.urlopen(uri)
927
928        # use UTF-8 encoding as fix for &nbsp chars in translated html
929        #
930        # When loading the html, for the base_uri, use the uri of the originally specified
931        # page (which will be in _data_path) rather than the uri of any translated html we may be using instead.
932        # Doing this allows the js, css, fonts etc. directories to be located by the translated page,
933        frame.load_string(page.read().decode(), "text/html", "UTF-8", uri);
934
935        pol_dec.ignore()
936        return True
937
938    def _do_command(self, uri):
939      if uri.startswith('cmd://'):
940          uri = uri[6:]
941
942      try:
943        if uri.startswith('install-appid?'):
944            dynamicapps.modify_app(self, 'install', uri[14:])
945        elif uri.startswith('remove-appid?'):
946            dynamicapps.modify_app(self, 'remove', uri[13:])
947        elif uri.startswith('upgrade-appid?'):
948            dynamicapps.modify_app(self, 'upgrade', uri[14:])
949        elif uri.startswith('launch-appid?'):
950            dynamicapps.launch_app(uri[13:])
951        elif uri.startswith('filter-apps?'):
952            filter_name = uri.split('?')[1]
953            nonfree_toggle = uri.split('?')[2]
954            if nonfree_toggle == 'toggle':
955                dynamicapps.apply_filter(self, filter_name, True)
956            else:
957                dynamicapps.apply_filter(self, filter_name)
958        elif uri.startswith('app-info-show?'):
959            appid = uri.split('?')[1]
960            self.execute_script('$("#info-show-' + appid + '").hide()')
961            self.execute_script('$("#info-hide-' + appid + '").show()')
962            self.execute_script('$("#details-' + appid + '").fadeIn("fast")')
963        elif uri.startswith('app-info-hide?'):
964            appid = uri.split('?')[1]
965            self.execute_script('$("#info-show-' + appid + '").show()')
966            self.execute_script('$("#info-hide-' + appid + '").hide()')
967            self.execute_script('$("#details-' + appid + '").fadeOut("fast")')
968        elif uri.startswith('screenshot?'):
969            filename = uri.split('?')[1]
970            dynamicapps.show_screenshot(filename)
971        elif uri == 'apt-update':
972            update_repos()
973            self._apt_cache.close()
974            self._apt_cache = apt.Cache()
975            self._push_config()
976        elif uri == 'fix-incomplete-install':
977            fix_incomplete_install()
978            self._apt_cache.close()
979            self._apt_cache = apt.Cache()
980            self._push_config()
981        elif uri == 'fix-broken-depends':
982            fix_broken_depends()
983            self._apt_cache.close()
984            self._apt_cache = apt.Cache()
985            self._push_config()
986        elif uri == 'get-aacs-db':
987            self.execute_script('$(".bluray-applying").show()')
988            get_aacs_db()
989            self.execute_script('$(".bluray-applying").hide()')
990        elif uri == 'autostart':
991            self._config.autostart ^= True
992            self._push_config()
993        elif uri == 'install':
994            subprocess.Popen(['ubiquity','gtk_ui'])
995        elif uri == 'backup':
996            subprocess.Popen(['deja-dup-preferences'])
997        elif uri == 'chatroom':
998            subprocess.Popen(['hexchat','IRC://irc.freenode.net/ubuntu-mate'])
999        elif uri == 'control':
1000            subprocess.Popen(['mate-control-center'])
1001        elif uri == 'drivers':
1002            subprocess.Popen(['software-properties-gtk','--open-tab=4'])
1003        elif uri == 'firewall':
1004            subprocess.Popen(['gufw'])
1005        elif uri == 'language':
1006            subprocess.Popen(['gnome-language-selector'])
1007        elif uri == 'users':
1008            subprocess.Popen(['users-admin'])
1009        elif uri == 'quit':
1010            goodbye()
1011        elif uri == 'tweak':
1012            subprocess.Popen(['mate-tweak'])
1013        elif uri == 'update':
1014            subprocess.Popen(['update-manager'])
1015        elif uri == 'printers':
1016            subprocess.Popen(['system-config-printer'])
1017        elif uri == 'gparted':
1018            subprocess.Popen(['gparted-pkexec'])
1019        elif uri == 'sysmonitor':
1020            subprocess.Popen(['mate-system-monitor'])
1021        elif uri.startswith('run?'):
1022            subprocess.Popen([uri[4:]])
1023        elif uri.startswith('link?'):
1024            webbrowser.open_new_tab(uri[5:])
1025        elif uri == 'checkInternetConnection':
1026            systemstate.check_internet_connection()
1027            if systemstate.is_online:
1028                self.execute_script("$('.offline').hide();")
1029                self.execute_script("$('.online').show();")
1030            else:
1031                self.execute_script("$('.offline').show();")
1032                self.execute_script("$('.online').hide();")
1033        elif uri == 'resize-rpi':
1034            systemstate.rpi_resize('do-resize', self)
1035        elif uri == 'reboot-rpi':
1036            systemstate.rpi_resize('reboot')
1037        elif uri == 'subscribe-updates':
1038            print('[Welcome] Subscribing to Ubuntu MATE Welcome Updates...')
1039            self.execute_script("$('#update-notification').hide()")
1040            self.execute_script("$('#update-subscribing').show()")
1041            dynamicapps.modify_app(self, 'install', 'ubuntu-mate-welcome')
1042            # Verify if the PPA was successfully added.
1043            if os.path.exists(systemstate.welcome_ppa_file):
1044                if os.path.getsize(systemstate.welcome_ppa_file) > 0:
1045                    print('[Welcome] Success, PPA added! Application restarting...')
1046                    os.execv(__file__, sys.argv)
1047            else:
1048                print('[Welcome] Failed, PPA not detected!')
1049                self.execute_script('$("#update-subscribing").hide()')
1050                self.execute_script('$("#update-notification").show()')
1051        elif uri == 'init-system-info':
1052            systemstate.get_system_info(self)
1053        else:
1054            print('[Error] Unknown command: ', uri)
1055      except Exception as e:
1056        print('[Error] Failed to execute command: ', uri)
1057        print('[Error] Exception: ', e)
1058
1059
1060class WelcomeApp(object):
1061    def __init__(self):
1062        # establish our location
1063        self._location = os.path.dirname( os.path.abspath(inspect.getfile(inspect.currentframe())) )
1064
1065        # check for relative path
1066        if( os.path.exists( os.path.join(self._location, 'data/' ) ) ):
1067            print('[Debug] Using relative path for data source. Non-production testing.')
1068            self._data_path = os.path.join(self._location, 'data/')
1069        elif( os.path.exists('/usr/share/ubuntu-mate-welcome/') ):
1070            print('Using /usr/share/ubuntu-mate-welcome/ path.')
1071            self._data_path = '/usr/share/ubuntu-mate-welcome/'
1072        else:
1073            print('Unable to source the ubuntu-mate-welcome data directory.')
1074            sys.exit(1)
1075
1076        self._build_app()
1077
1078    def _get_translated_slides(self):
1079        """ If a locale has been specified on the command line, get translated slides
1080            for that. If not, get translated slides for the current locale
1081
1082        Do not assume that every slide has a translation, check each file individually
1083
1084        Returns:
1085            a list of filenames of translated slides - if there is no translated version
1086            of a slide, the filename of the untranslated version from the _data_path directory
1087            is used instead
1088
1089        """
1090
1091        if (arg.locale is not None):
1092            locale_to_use = arg.locale
1093        else:
1094            locale_to_use = str(locale.getlocale()[0])
1095
1096        def set_locale_dir(this_locale):
1097            # check for relative path
1098            if (os.path.exists(os.path.join(self._location, 'i18n', this_locale))):
1099                locale_dir = os.path.join(self._location, 'i18n', this_locale)
1100                print('[i18n] Using ' + this_locale + '. Non-production testing.')
1101            elif (os.path.exists(os.path.join('/usr/share/ubuntu-mate-welcome/i18n/', this_locale))):
1102                locale_dir = os.path.join('/usr/share/ubuntu-mate-welcome/i18n/', this_locale)
1103                print('[i18n] Using ' + this_locale)
1104            else:
1105                locale_dir = ''
1106                print('[i18n] Locale ' + this_locale + ' not available.')
1107
1108            return locale_dir
1109
1110        # if no locale exists, try a generic locale.
1111        locale_dir = set_locale_dir(locale_to_use)
1112        if locale_dir == '':
1113            locale_generic = locale_to_use.split('_')[0]
1114            print('[i18n] Trying ' + locale_generic + '...')
1115            locale_dir = set_locale_dir(locale_generic)
1116
1117        results = []
1118
1119        slides = glob.glob(os.path.join(self._data_path, '*.html'))
1120        for slide in slides:
1121            # get the slide name and see if a translated version exists
1122            head, slide_name = os.path.split(slide)
1123
1124            trans_slide = os.path.join(locale_dir, slide_name)
1125            if os.path.exists(trans_slide):
1126                results.append(trans_slide)
1127                arg.print_verbose("i18n","Will use %s translation of %s" %(locale_to_use, slide_name))
1128            else:
1129                results.append(slide)
1130                arg.print_verbose("i18n","No %s translation of %s found. Will use version in _data_path" %(locale_to_use, slide))
1131
1132        return results
1133
1134    def _build_app(self):
1135
1136        # Slightly different attributes if "--software-only" is activated.
1137        if arg.jump_software_page:
1138            title = _("Software Boutique")
1139            width = 900
1140            height = 600
1141            load_file = 'software-only.html'
1142        else:
1143            title = _("Welcome")
1144            width = 800
1145            height = 552
1146            load_file = 'splash.html'
1147
1148        # Enlarge the window should the text be any larger.
1149        if systemstate.zoom_level == 1.1:
1150            width = width + 20
1151            height = height + 20
1152        elif systemstate.zoom_level == 1.2:
1153            width = width + 60
1154            height = height + 40
1155        elif systemstate.zoom_level == 1.3:
1156            width = width + 100
1157            height = height + 60
1158        elif systemstate.zoom_level == 1.4:
1159            width = width + 130
1160            height = height + 100
1161        elif systemstate.zoom_level == 1.5:
1162            width = width + 160
1163            height = height + 120
1164
1165        # Jump to a specific page for testing purposes.
1166        if arg.jump_to:
1167            load_file = arg.jump_to + '.html'
1168
1169        # build window
1170        w = Gtk.Window()
1171        w.set_position(Gtk.WindowPosition.CENTER)
1172        w.set_wmclass('Ubuntu MATE Welcome', 'Ubuntu MATE Welcome')
1173        w.set_title(title)
1174
1175        # http://askubuntu.com/questions/153549/how-to-detect-a-computers-physical-screen-size-in-gtk
1176        s = Gdk.Screen.get_default()
1177        if s.get_height() <= 600:
1178            w.set_size_request(768, 528)
1179        else:
1180            w.set_size_request(width, height)
1181
1182        icon_dir = os.path.join(self._data_path, 'img', 'welcome', 'ubuntu-mate-icon.svg')
1183        w.set_icon_from_file(icon_dir)
1184
1185        #get the translated slide list
1186        trans_slides = self._get_translated_slides()
1187        # build webkit container
1188        mv = AppView(trans_slides)
1189
1190        # load our index file
1191        file = os.path.abspath(os.path.join(self._data_path, load_file))
1192
1193        uri = 'file://' + urllib.request.pathname2url(file)
1194        mv.open(uri)
1195
1196        # build scrolled window widget and add our appview container
1197        sw = Gtk.ScrolledWindow()
1198        sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
1199        sw.add(mv)
1200
1201        # build an autoexpanding box and add our scrolled window
1202        b = Gtk.VBox(homogeneous=False, spacing=0)
1203        b.pack_start(sw, expand=True, fill=True, padding=0)
1204
1205        # add the box to the parent window and show
1206        w.add(b)
1207        w.connect('delete-event', goodbye)
1208        w.show_all()
1209
1210        self._window = w
1211        self._appView = mv
1212
1213    def run(self):
1214        signal.signal(signal.SIGINT, signal.SIG_DFL)
1215        Gtk.main()
1216
1217    def close(self, p1, p2):
1218        Gtk.main_quit(p1, p2);
1219
1220
1221class SystemState(object):
1222    def __init__(self):
1223        # Set initial variables
1224        self.is_online = False
1225        self.user_name = getuser()
1226        self.updates_subscribed = False
1227        self.welcome_version = 'Unknown'
1228        self.rpi_resize_pending = False
1229
1230        # Get current architecture of system.
1231        # Outputs 'i386', 'amd64', etc - Based on packages instead of kernel (eg. i686, x86_64).
1232        self.arch = str(subprocess.Popen(['dpkg','--print-architecture'], stdout=subprocess.PIPE).communicate()[0]).strip('\\nb\'')
1233
1234        # Get current codename of Ubuntu MATE in use.
1235        # Uses first word in lowercase, such as : trusty, wily, xenial
1236        self.codename = platform.dist()[2]
1237
1238        # Determine which type of session we are in.
1239        if os.path.exists('/usr/share/glib-2.0/schemas/zubuntu-mate-live.gschema.override'):
1240            self.session_type = 'live'
1241        elif self.user_name[:6] == 'guest-':
1242            self.session_type = 'guest'
1243        elif os.path.isfile(os.path.join('/','boot/','kernel7.img')):
1244            self.session_type = 'pi'
1245        else:
1246            self.session_type = 'normal'
1247
1248        # To inform the user if they are running in BIOS or UEFI mode.
1249        if os.path.exists("/sys/firmware/efi"):
1250            self.boot_mode = 'UEFI'
1251        elif self.session_type == 'pi':
1252            self.boot_mode = 'Raspberry Pi'
1253        elif self.arch == 'powerpc':
1254            self.boot_mode = 'Yaboot'
1255        else:
1256            self.boot_mode = 'BIOS'
1257
1258        # Create, then spawn threads
1259        thread1 = Thread(target=self.check_internet_connection)
1260        thread2 = Thread(target=self.detect_graphics)
1261        thread1.start()
1262        thread2.start()
1263
1264        # Check whether Welcome is subscribed for updates.
1265        self.welcome_ppa_file = '/etc/apt/sources.list.d/ubuntu-mate-dev-ubuntu-welcome-' + self.codename + '.list'
1266        if os.path.exists(self.welcome_ppa_file):
1267            if os.path.getsize(self.welcome_ppa_file) > 0:
1268                self.updates_subscribed = True
1269
1270        # Accessibility - Enlarge/shrink text based on Font DPI set by the user.
1271        if arg.font_dpi_override:
1272            font_dpi = arg.font_dpi_override
1273        else:
1274            try:
1275                font_gsettings = Gio.Settings.new('org.mate.font-rendering')
1276                font_value = font_gsettings.get_value('dpi')
1277                font_dpi = int(float(str(font_value)))
1278                arg.print_verbose('Welcome', 'Font DPI is: ' + str(font_dpi))
1279            except:
1280                font_dpi = 96
1281                print('[Welcome] Couldn\'t retrieve font DPI. Using default value of ' + str(font_dpi))
1282
1283        if font_dpi < 50:
1284            print('[Welcome] DPI below 50. Out of range..')
1285            font_dpi = 96
1286        elif font_dpi > 500:
1287            print('[Welcome] DPI over 500. Out of range.')
1288            font_dpi = 96
1289
1290        zoom_level = 1.0
1291        if font_dpi <= 80:
1292            zoom_level = 0.75
1293        elif font_dpi <= 87:
1294            zoom_level = 0.85
1295        elif font_dpi <= 94:
1296            zoom_level = 0.9
1297        elif font_dpi <= 101:
1298            zoom_level = 1.0    # Default DPI is usually 96.
1299        elif font_dpi <= 108:
1300            zoom_level = 1.1
1301        elif font_dpi <= 115:
1302            zoom_level = 1.2
1303        elif font_dpi <= 122:
1304            zoom_level = 1.3
1305        elif font_dpi <= 129:
1306            zoom_level = 1.4
1307        elif font_dpi >= 130:
1308            zoom_level = 1.5
1309
1310        self.dpi = font_dpi
1311        self.zoom_level = zoom_level
1312
1313    def check_internet_connection(self):
1314        print('[Network Test] Checking for internet connectivity... ')
1315        url = "http://archive.ubuntu.com/"
1316
1317        if arg.simulate_no_connection:
1318            print('[Network Test] Simulation argument override. Retrying will reset this.')
1319            arg.simulate_no_connection = False
1320            self.is_online = False
1321            return
1322
1323        if arg.simulate_force_connection:
1324            print('[Network Test] Simulation argument override. Forcing connection presence.')
1325            print('[Network Test] WARNING: Do not attempt to install/remove software offline as this may lead to errors later!')
1326            arg.simulate_connection = False
1327            self.is_online = True
1328            return
1329
1330        try:
1331            response = urllib.request.urlopen(url, timeout=2).read().decode('utf-8')
1332        except socket.timeout:
1333            print("[Network Test] -- Socket timed out to URL {0}".format(url))
1334            self.is_online = False
1335        except:
1336            print("[Network Test] -- Could not establish a connection to '{0}'. ".format(url))
1337            self.is_online = False
1338        else:
1339            print("[Network Test] Successfully pinged '{0}' ".format(url))
1340            self.is_online = True
1341
1342    def detect_graphics(self):
1343        # If we're the Raspberry Pi, there is nothing to output.
1344        if self.session_type == 'pi':
1345            self.graphics_grep = 'Raspberry Pi'
1346            self.graphics_vendor = 'Raspberry Pi'
1347            return
1348
1349        # TODO: Support dual graphic cards.
1350        arg.print_verbose('Graphics','Detecting graphics vendor... ')
1351        try:
1352            output = subprocess.Popen('lspci | grep VGA', stdout=subprocess.PIPE, shell='True').communicate()[0]
1353            output = output.decode(encoding='UTF-8')
1354        except:
1355            # When 'lspci' does not find a VGA controller (this is the case for the RPi 2)
1356            arg.print_verbose("Graphics","Couldn't detect a VGA Controller on this system.")
1357            output = 'Unknown'
1358
1359        # Scan for and set known brand name.
1360        if output.find('NVIDIA') != -1:
1361            self.graphics_vendor = 'NVIDIA'
1362        elif output.find('AMD') != -1:
1363            self.graphics_vendor = 'AMD'
1364        elif output.find('Intel') != -1:
1365            self.graphics_vendor = 'Intel'
1366        elif output.find('VirtualBox') != -1:
1367            self.graphics_vendor = 'VirtualBox'
1368        else:
1369            self.graphics_vendor = 'Unknown'
1370
1371        self.graphics_grep = repr(output)
1372        self.graphics_grep = self.graphics_grep.split("controller: ",1)[1]
1373        self.graphics_grep = self.graphics_grep.split("\\n",1)[0]
1374        arg.print_verbose("Graphics","Detected: {0}".format(self.graphics_grep))
1375
1376    def get_system_info(self, webkit):
1377        print('[System Specs] Gathering system specifications...')
1378
1379        # Prefixes for translation
1380        mb_prefix = _("MB")
1381        mib_prefix = _("MiB")
1382        gb_prefix = _("GB")
1383        gib_prefix = _("GiB")
1384
1385        # Start collecting advanced system information in the background.
1386        # (Python can do other things while this command completes)
1387        arg.print_verbose('System Specs', 'Running "inxi" for advanced system information...')
1388        try:
1389            inxi_raw = subprocess.Popen(['inxi','-c','0','-v','5','-p','-d','-xx'], stdout=subprocess.PIPE)
1390        except:
1391            print('[System Specs] Failed to execute collect advanced information. Is "inxi" no longer installed?')
1392
1393        # Append a failure symbol beforehand in event something goes horribly wrong.
1394        stat_error_msg = _("Could not gather data.")
1395        html_tag = '<a data-toggle=\'tooltip\' data-placement=\'top\' title=\'' + stat_error_msg + '\'><span class=\'fa fa-warning specs-error\'></span></a>'
1396        for element in ['distro', 'kernel', 'motherboard', 'boot-mode', 'cpu-model', 'cpu-speed', 'arch-use',
1397                        'arch-supported', 'memory', 'graphics', 'filesystem', 'capacity', 'allocated-space', 'free-space']:
1398            webkit.execute_script('$("#spec-' + element + '").html("' + html_tag + '")')
1399
1400        # Collect basic system information
1401        def run_external_command(command, with_shell=False):
1402            if with_shell:
1403                raw = str(subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0])
1404            else:
1405                raw = str(subprocess.Popen(command, stdout=subprocess.PIPE).communicate()[0])
1406            output = raw.replace("b'","").replace('b"',"").replace("\\n'","").replace("\\n","")
1407            return output
1408
1409        ## Distro
1410        try:
1411            arg.print_verbose('System Specs', 'Gathering data: Distribution')
1412            distro_description = run_external_command(['lsb_release','-d','-s'])
1413            distro_codename = run_external_command(['lsb_release','-c','-s'])
1414            webkit.execute_script('$("#spec-distro").html("' + distro_description + '")')
1415        except:
1416            print('[System Specs] Failed to retrieve data: Distribution')
1417
1418        ## Kernel
1419        try:
1420            arg.print_verbose('System Specs', 'Gathering data: Kernel')
1421            kernel = run_external_command(['uname','-r'])
1422            webkit.execute_script('$("#spec-kernel").html("' + kernel + '")')
1423        except:
1424            print('[System Specs] Failed to retrieve data: Kernel')
1425
1426        ## Motherboard
1427        try:
1428            arg.print_verbose('System Specs', 'Gathering data: Motherboard')
1429            motherboard_name = run_external_command(['cat','/sys/devices/virtual/dmi/id/board_name'])
1430            webkit.execute_script('$("#spec-motherboard").html("' + motherboard_name + '")')
1431        except:
1432            print('[System Specs] Failed to retrieve data: Motherboard')
1433
1434        ## CPU Details
1435        arg.print_verbose('System Specs', 'Gathering data: CPU')
1436        try:
1437            cpu_model = run_external_command(['lscpu | grep "name"'], True).split(': ')[1]
1438            webkit.execute_script('$("#spec-cpu-model").html("' + cpu_model + '")')
1439        except:
1440            print('[System Specs] Failed to retrieve data: CPU Model')
1441
1442        try:
1443            try:
1444                # Try obtaining the maximum speed first.
1445                cpu_speed = int(run_external_command(['lscpu | grep "max"'], True).split(': ')[1].strip(' ').split('.')[0])
1446            except:
1447                # Otherwise, fetch the CPU's MHz.
1448                cpu_speed = int(run_external_command(['lscpu | grep "CPU MHz"'], True).split(': ')[1].strip(' ').split('.')[0])
1449
1450            webkit.execute_script('$("#spec-cpu-speed").html("' + str(cpu_speed) + ' MHz")')
1451        except:
1452            print('[System Specs] Failed to retrieve data: CPU Speed')
1453
1454        try:
1455            if self.arch == 'i386':
1456                cpu_arch_used = '32-bit'
1457            elif self.arch == 'amd64':
1458                cpu_arch_used = '64-bit'
1459            else:
1460                cpu_arch_used = self.arch
1461            webkit.execute_script('$("#spec-arch-use").html("' + cpu_arch_used + '")')
1462        except:
1463            print('[System Specs] Failed to retrieve data: CPU Arch in Use')
1464
1465        try:
1466            cpu_arch_supported = run_external_command(['lscpu | grep "mode"'], True).split(': ')[1]
1467            webkit.execute_script('$("#spec-arch-supported").html("' + cpu_arch_supported + '")')
1468        except:
1469            print('[System Specs] Failed to retrieve data: CPU Supported Arch')
1470
1471        ## Root partition (where Ubuntu MATE is installed) and the rest of that disk.
1472        try:
1473            if self.session_type == 'live':
1474                webkit.execute_script('$(".specs-hide-live-session").hide()')
1475            else:
1476                arg.print_verbose('System Specs', 'Gathering data: Storage')
1477                ## Gather entire disk data
1478                root_partition = run_external_command(['mount | grep "on / "'], True).split(' ')[0]
1479                if root_partition[:-2] == "/dev/sd":            # /dev/sdXY
1480                    root_dev = root_partition[:-1]
1481                if root_partition[:-2] == "/dev/hd":            # /dev/hdXY
1482                    root_dev = root_partition[:-1]
1483                if root_partition[:-3] == "/dev/mmcblk":        # /dev/mmcblkXpY
1484                    root_dev = root_partition[:-2]
1485                else:
1486                    root_dev = root_partition[:-1]              # Generic
1487                disk_dev_name = root_dev.split('/')[2]
1488                arg.print_verbose('System Specs', 'Ubuntu MATE is installed on disk: ' + root_dev)
1489                rootfs = os.statvfs('/')
1490                root_size = rootfs.f_blocks * rootfs.f_frsize
1491                root_free = rootfs.f_bavail * rootfs.f_frsize
1492                root_used = root_size - root_free
1493                entire_disk = run_external_command(['lsblk -b | grep "' + disk_dev_name + '" | grep "disk"'], True)
1494                entire_disk = int(entire_disk.split()[3])
1495
1496                ## Perform calculations across units
1497                capacity_GB =   round(entire_disk/1000/1000/1000,1)
1498                capacity_GiB =  round(entire_disk/1024/1024/1024,1)
1499                allocated_GB =  round(root_size/1000/1000/1000,1)
1500                allocated_GiB = round(root_size/1024/1024/1024,1)
1501                used_GB =       round(root_used/1000/1000/1000,1)
1502                used_GiB =      round(root_used/1024/1024/1024,1)
1503                free_GB =       round(root_free/1000/1000/1000,1)
1504                free_GiB =      round(root_free/1024/1024/1024,1)
1505                other_GB =      round((entire_disk-root_size)/1000/1000/1000,1)
1506                other_GiB =     round((entire_disk-root_size)/1024/1024/1024,1)
1507
1508                # Show megabytes/mebibytes (in red) if gigabytes are too small.
1509                if capacity_GB <= 1:
1510                    capacity_GB = str(round(entire_disk/1000/1000,1)) + ' ' + mb_prefix
1511                    capacity_GiB = str(round(entire_disk/1024/1024,1)) + ' ' + mib_prefix
1512                else:
1513                    capacity_GB = str(capacity_GB) + ' ' + gb_prefix
1514                    capacity_GiB = str(capacity_GiB) + ' ' + gib_prefix
1515
1516                if allocated_GB <= 1:
1517                    allocated_GB =  str(round(root_size/1000/1000,1)) + ' ' + mb_prefix
1518                    allocated_GiB = str(round(root_size/1024/1024,1)) + ' ' + mib_prefix
1519                else:
1520                    allocated_GB = str(allocated_GB) + ' ' + gb_prefix
1521                    allocated_GiB = str(allocated_GiB) + ' ' + gib_prefix
1522
1523                if used_GB <= 1:
1524                    used_GB =  str(round(root_used/1000/1000,1)) + ' ' + mb_prefix
1525                    used_GiB = str(round(root_used/1024/1024,1)) + ' ' + mib_prefix
1526                else:
1527                    used_GB = str(used_GB) + ' ' + gb_prefix
1528                    used_GiB = str(used_GiB) + ' ' + gib_prefix
1529
1530                if free_GB <= 1:
1531                    free_GB =  str(round(root_free/1000/1000,1)) + ' ' + mb_prefix
1532                    free_GiB = str(round(root_free/1024/1024,1)) + ' ' + mib_prefix
1533                    webkit.execute_script('$("#spec-free-space").addClass("specs-error")')
1534                else:
1535                    free_GB = str(free_GB) + ' ' + gb_prefix
1536                    free_GiB = str(free_GiB) + ' ' + gib_prefix
1537
1538                if other_GB <= 1:
1539                    other_GB =  str(round((entire_disk-root_size)/1000/1000,1)) + ' ' + mb_prefix
1540                    other_GiB = str(round((entire_disk-root_size)/1024/1024,1)) + ' ' + mib_prefix
1541                else:
1542                    other_GB = str(other_GB) + ' ' + gb_prefix
1543                    other_GiB = str(other_GiB) + ' ' + gib_prefix
1544
1545                ## Append data to HTML.
1546                webkit.execute_script('$("#spec-filesystem").html("' + root_partition + '")')
1547                webkit.execute_script('$("#spec-capacity").html("' + capacity_GB + ' <span class=\'secondary-value\'>(' + capacity_GiB + ')</span>")')
1548                webkit.execute_script('$("#spec-allocated-space").html("' + allocated_GB + ' <span class=\'secondary-value\'>(' + allocated_GiB + ')</span>")')
1549                webkit.execute_script('$("#spec-used-space").html("' + used_GB + ' <span class=\'secondary-value\'>(' + used_GiB + ')</span>")')
1550                webkit.execute_script('$("#spec-free-space").html("' + free_GB + ' <span class=\'secondary-value\'>(' + free_GiB + ')</span>")')
1551                webkit.execute_script('$("#spec-other-space").html("' + other_GB + ' <span class=\'secondary-value\'>(' + other_GiB + ')</span>")')
1552
1553                ## Calculate representation across physical disk
1554                disk_percent_UM_used = int(round(root_used / entire_disk * 100)) * 2
1555                disk_percent_UM_free = int(round(root_free / entire_disk * 100)) * 2
1556                disk_percent_other   = (200 - disk_percent_UM_used - disk_percent_UM_free)
1557                arg.print_verbose('System Specs', ' --- Disk: ' + root_dev)
1558                arg.print_verbose('System Specs', ' --- * OS Used: ' + str(root_used) + ' bytes (' + str(disk_percent_UM_used/2) + '%)')
1559                arg.print_verbose('System Specs', ' --- * OS Free: ' + str(root_free) + ' bytes (' + str(disk_percent_UM_free/2) + '%)')
1560                arg.print_verbose('System Specs', ' --- = Other Partitions: ' + str(entire_disk - root_size) + ' bytes (' + str(disk_percent_other/2) + '%)')
1561                webkit.execute_script("$('#disk-used').width('" + str(disk_percent_UM_used) + "px');")
1562                webkit.execute_script("$('#disk-free').width('" + str(disk_percent_UM_free) + "px');")
1563                webkit.execute_script("$('#disk-other').width('" + str(disk_percent_other) + "px');")
1564        except:
1565            print('[System Specs] Failed to retrieve data: Storage')
1566
1567        ## RAM
1568        try:
1569            arg.print_verbose('System Specs', 'Gathering Data: RAM')
1570            ram_bytes = run_external_command(['free -b | grep "Mem:" '], True)
1571            ram_bytes = float(ram_bytes.split()[1])
1572            if round(ram_bytes / 1024 / 1024) < 1024:
1573                ram_xb = str(round(ram_bytes / 1000 / 1000, 1)) + ' ' + mb_prefix
1574                ram_xib = str(round(ram_bytes / 1024 / 1024, 1)) + ' ' + mib_prefix
1575            else:
1576                ram_xb =  str(round(ram_bytes / 1000 / 1000 / 1000, 1)) + ' ' + gb_prefix
1577                ram_xib = str(round(ram_bytes / 1024 / 1024 / 1024, 1)) + ' ' + gib_prefix
1578            ram_string = ram_xb + ' <span class=\'secondary-value\'>(' + ram_xib + ')</span>'
1579            webkit.execute_script('$("#spec-memory").html("' + ram_string + '")')
1580        except:
1581            print('[System Specs] Failed to retrieve data: RAM (Memory)')
1582
1583        ## Graphics
1584        webkit.execute_script('$("#spec-graphics").html("' + self.graphics_grep + '")')
1585
1586        ## Collect missing data differently for some architectures.
1587        if systemstate.arch == 'powerpc':
1588            ## Motherboard & Revision
1589            try:
1590                arg.print_verbose('System Specs', 'Gathering alternate data: PowerPC Motherboard')
1591                mb_model = run_external_command(['grep','motherboard','/proc/cpuinfo']).split(': ')[1]
1592                mb_rev = run_external_command(['grep','revision','/proc/cpuinfo']).split(': ')[1]
1593                webkit.execute_script('$("#spec-motherboard").html("' + mb_model + ' ' + mb_rev + '")')
1594            except:
1595                arg.print_verbose('System Specs', 'Failed to gather data: PowerPC Motherboard')
1596
1597            ## CPU and Clock Speed
1598            try:
1599                arg.print_verbose('System Specs', 'Gathering alternate data: PowerPC CPU')
1600                cpu_model = run_external_command(['grep','cpu','/proc/cpuinfo']).split(': ')[1]
1601                cpu_speed = run_external_command(['grep','clock','/proc/cpuinfo']).split(': ')[1]
1602                webkit.execute_script('$("#spec-cpu-model").html("' + cpu_model + '")')
1603                webkit.execute_script('$("#spec-cpu-speed").html("' + str(cpu_speed) + '")')
1604            except:
1605                arg.print_verbose('System Specs', 'Failed to gather data: PowerPC CPU')
1606
1607            ## Device Name
1608            try:
1609                arg.print_verbose('System Specs', 'Gathering alternate data: PowerPC Model Name')
1610                mb_name = run_external_command(['grep','detected','/proc/cpuinfo']).split(': ')[1]
1611                webkit.execute_script('$("#spec-motherboard").append(" / ' + mb_name + '")')
1612            except:
1613                arg.print_verbose('System Specs', 'Failed to gather data: PowerPC Model Name')
1614
1615            ## Boot Mode / PowerMac Generation
1616            try:
1617                arg.print_verbose('System Specs', 'Gathering alternate data: PowerMac Generation')
1618                mac_generation = run_external_command(['grep','pmac-generation','/proc/cpuinfo']).split(': ')[1]
1619                webkit.execute_script('$("#spec-boot-mode").html("Yaboot (' + mac_generation + ')")')
1620            except:
1621                arg.print_verbose('System Specs', 'Failed to gather data: PowerMac Generation')
1622
1623        # Append advanced system information
1624        try:
1625            arg.print_verbose('System Specs', 'Waiting for inxi process to finish...')
1626            inxi_output = str(inxi_raw.communicate()[0])
1627            inxi_output = inxi_output.replace("b'","").replace("\\n","\n")
1628            webkit.execute_script("$('#specs-inxi').html('')")
1629            for line in inxi_output.split('\n'):
1630                webkit.execute_script("$('#specs-inxi').append('" + line.strip('"').strip("'") + "<br>')")
1631            print('[System Specs] Successfully appended advanced system information.')
1632        except:
1633            print('[System Specs] Failed to append advanced system information or communicate with "inxi" process.')
1634
1635        # Check internet connectivity status.
1636        if self.is_online:
1637            webkit.execute_script('$("#specs-has-net").show()')
1638            webkit.execute_script('$("#specs-has-no-net").hide()')
1639        else:
1640            webkit.execute_script('$("#specs-has-net").hide()')
1641            webkit.execute_script('$("#specs-has-no-net").show()')
1642
1643        # Change icon depending on what type of device we are using.
1644        if self.session_type == 'pi':
1645            webkit.execute_script('$("#specs-device-rpi").show()')
1646            webkit.execute_script('$(".specs-hide-pi").hide()')
1647        elif self.arch == 'powerpc':
1648            webkit.execute_script('$("#specs-device-powerpc").show()')
1649            webkit.execute_script('$(".specs-hide-ppc").hide()')
1650        elif self.graphics_vendor == 'VirtualBox':
1651            webkit.execute_script('$("#specs-device-vbox").show()')
1652            webkit.execute_script('$(".specs-hide-vbox").hide()')
1653        elif self.session_type == 'live':
1654            webkit.execute_script('$("#specs-live-session").show()')
1655            webkit.execute_script('$(".specs-hide-live").hide()')
1656        else:
1657            webkit.execute_script('$("#specs-device-normal").show()')
1658
1659        # Display UEFI/BIOS boot mode.
1660        if systemstate.arch == 'i386' or systemstate.arch == 'amd64':
1661            webkit.execute_script('$("#spec-boot-mode").html("' + self.boot_mode + '")')
1662
1663        # Hide root storage info if in a live session.
1664        if self.session_type == 'live':
1665            webkit.execute_script('$(".spec-3").hide()')
1666
1667        # Data cached, ready to display.
1668        webkit.execute_script('$("#specs-loading").fadeOut("fast")')
1669        webkit.execute_script('$("#specs-tabs").fadeIn("fast")')
1670        webkit.execute_script('$("#specs-basic").fadeIn("medium")')
1671        webkit.execute_script('setCursorNormal()')
1672
1673    def rpi_resize(self, action, webkit=None):
1674        if action == 'do-resize':
1675            subprocess.call(['pkexec', '/usr/lib/ubuntu-mate/ubuntu-mate-welcome-rpi2-partition-resize'])
1676
1677            def notify(subject, body, icon):
1678                Notify.init(_('Raspberry Pi Partition Resize'))
1679                resize_notify=Notify.Notification.new(subject, body, icon)
1680                resize_notify.show()
1681
1682            try:
1683                with open('/tmp/notify_rpi_status') as status_file:
1684                    status_code = int(status_file.read())
1685            except:
1686                status_code = 0
1687
1688            try:
1689                with open('/tmp/notify_rpi_text') as misc_file:
1690                    misc_text = misc_file.read()
1691            except:
1692                misc_text = ""
1693
1694            if status_code == 1:
1695                notify( _("Root partition has been resized."), _("The filesystem will be enlarged upon the next reboot."), 'dialog-information' )
1696                self.rpi_resize_pending = True
1697                webkit.execute_script('$("#rpi-resized").hide()')
1698                webkit.execute_script('$("#rpi-not-resized").hide()')
1699                webkit.execute_script('$("#rpi-restart-now").show()')
1700            elif status_code == 2:
1701                notify( _("Don't know how to expand."), misc_text + ' ' + _("does not exist or is not a symlink."), 'dialog-error' )
1702            elif status_code == 3:
1703                notify( _("Don't know how to expand."), misc_text + ' ' + _("is not an SD card."), 'dialog-error' )
1704            elif status_code == 4:
1705                notify( _("Don't know how to expand."), _("Your partition layout is not currently supported by this tool."), 'dialog-error' )
1706            elif status_code == 5:
1707                notify( _("Don't know how to expand."), misc_text + ' ' + _("is not the last partition."), 'dialog-error' )
1708            else:
1709                notify( _("Failed to run resize script."), _("The returned error code is:") + str(status_code), 'dialog-error' )
1710                print('[Welcome] Unrecognised return code for Raspberry Pi resize: ' + str(status_code))
1711
1712            app._appView._push_config()
1713
1714        elif action == 'check':
1715            if os.path.exists('/.resized'):
1716                resized = True
1717            else:
1718                resized = False
1719
1720            if resized:
1721                webkit.execute_script('$("#rpi-resized").show()')
1722                webkit.execute_script('$("#rpi-not-resized").hide()')
1723            else:
1724                webkit.execute_script('$("#rpi-resized").hide()')
1725                webkit.execute_script('$("#rpi-not-resized").show()')
1726
1727            if self.rpi_resize_pending:
1728                webkit.execute_script('$("#rpi-resized").hide()')
1729                webkit.execute_script('$("#rpi-not-resized").hide()')
1730                webkit.execute_script('$("#rpi-restart-now").show()')
1731
1732        elif action == 'reboot':
1733            subprocess.call(['mate-session-save','--shutdown-dialog'])
1734
1735
1736class DynamicApps(object):
1737    def __init__(self):
1738        # Load JSON Index into Memory
1739        self.reload_index()
1740
1741        # Variables to remember common details.
1742        self.all_categories = ['Accessories', 'Education', 'Games', 'Graphics', 'Internet', 'Office', 'Programming', 'Media', 'SysTools', 'UnivAccess', 'Servers', 'MoreApps']
1743        self.hide_non_free = False
1744
1745        # Reading the apt cache later.
1746        self._apt_cache = apt.Cache()
1747
1748        # Indicate that operations are in progress.
1749        self.operations_busy = False
1750
1751        # Get the version of Welcome in use.
1752        for pkgname in self._apt_cache.keys():
1753            if 'ubuntu-mate-welcome' in pkgname:
1754                systemstate.welcome_version = self._apt_cache['ubuntu-mate-welcome'].installed.version
1755                break
1756        print('[Welcome] Version: ' + systemstate.welcome_version)
1757
1758
1759    ###### JSON Index Structure
1760    #
1761    #   ===== Structure Overview =====
1762    #   {
1763    #     "Category" {                                  - Application category.
1764    #       "application-id" {                          - Unique string identifier for application, apps sorted A-Z. Hyphens preferred.
1765    #         "variable": "data",                       - Variable containing single data.
1766    #         "list": ["This is a line.",
1767    #                   "The same line."]               - Variable containing a 'list' of data.
1768    #         "group": { "variable": "data" }           - Group containing data.
1769    #        }
1770    #      }
1771    #   }
1772    #
1773    #   ** Standard JSON rules apply. Watch out for the commas.
1774    #   ** Important!! Use &#8217; instead of ' for an apostrophe character.
1775
1776    #   ===== Variable Index =====
1777    #   Variable                Type        Required?   Description
1778    #   ----------------------- ----------  ----------  ---------------------------------------------
1779    #   name                    string      Yes         Name of the application as displayed to the user.
1780    #   img                     string      Yes         Name of image. Excluding ".png" extension.
1781    #   main-package            string      Yes         Package used to detect if it's installed.
1782    #   launch-command          string      No          Command to launch the installed program. Can be ignored for no launch option.
1783    #   install-packages        string      *           Packages to install/reinstall. Comma separated.
1784    #   remove-packages         string      *           Packages to remove. Comma separated.
1785    #   upgradable              boolean     *           This package is only for upgrading.
1786    #   upgrade-packages        string      *           Packages to upgrade. Comma separated.
1787    #   description             list        Yes         Description of the application. Use usual HTML tags for formatting. Can be left blank if unlisted.
1788    #   alternate-to            string      No          If the app is similar or has an alternate. Can be ignored.
1789    #   subcategory             string      Yes         Used for filtering applications within the category. Eg. "Partitioning", "Audio Production".
1790    #   open-source             boolean     Yes         Proprietary or Open Source?
1791    #   url-info                string      Yes         URL to the web page for more information.
1792    #   url-android             string      No          URL if there is an associated Android app. Can be ignored.
1793    #   url-ios                 string      No          URL if there is an associated Android app. Can be ignored.
1794    #   arch                    string      Yes         Supported architectures for this app. Comma seperated.
1795    #   releases                string      Yes         Supported versions of Ubuntu MATE to show this application. Comma seperated.
1796    #   working                 boolean     Yes         Show/hide visibility of this application.
1797    #   notes                   string      No          Optional developer notes for the application.
1798    #
1799    #### * Only if applicable to application.
1800
1801    #   ===== Pre-installation Index =====
1802    #
1803    #   "pre-install": {                                - Required group of data containing pre-installation procedures.
1804    #       "trusty": {                                 - Different releases may have different operations.
1805    #           "variable":  "data"                     - See table below for possible operations.
1806    #       },
1807    #       "all": {                                    - Use "all" to specify all other releases. This should be last.
1808    #           "variable":  "data"                         If there is only one instruction,
1809    #       }
1810    #   }
1811    #
1812    #   method                  string      Yes         Pre-configuration methods. Multiple can be specified with a plus '+'.
1813    #                                                       "skip"          =   Package is already in archive.
1814    #                                                       "ppa"           =   Add a PPA. Specify (2), optionally (1).
1815    #                                                       "partner-repo"  =   Add the Ubuntu Partners Repo.
1816    #                                                       "manual"        =   Get keys and write a sources.list file. (3)
1817    #   source-file        (1)  string      No          Source file to update, excluding the ".list" extension.
1818    #   enable-ppa         (2)  string      *           Name of PPA to add, eg. "ppa:somebody/someapp".
1819    #   apt-key-url        (3)  string      *           Retrieve the key from URL.
1820    #   apt-key-server     (3)  list        *           Retrieve the key from a server.
1821    #                                                       "server-address"    = Eg. "keyserver.ubuntu.com"
1822    #                                                       "key"               = Eg. "D2C19886"
1823    #   apt-sources        (3)  list        *           Contents for the sources file. Each variable is a new line.
1824    #
1825    #### These keys words can be given as placeholders:
1826    #
1827    #   CODENAME        =   Current Ubuntu release, eg. "xenial".
1828    #   OSVERSION       =   Current Ubuntu version, eg "16.04".
1829    #
1830
1831    def reload_index(self):
1832        try:
1833            print('[Apps] Reading index...')
1834            json_path = os.path.abspath(os.path.join(app._data_path, 'js/applications.json'))
1835            with open(json_path) as data_file:
1836                self.index = json.load(data_file)
1837                print('[Apps] Successfully loaded index.')
1838        except Exception as e:
1839            self.index = None
1840            print("[Apps] ERROR: Software Index JSON is invalid or missing!")
1841            print("------------------------------------------------------------")
1842            print("Exception:")
1843            print(str(e))
1844            print("------------------------------------------------------------")
1845
1846    def set_app_info(self, category, program_id):
1847        self.app_name = self.index[category][program_id]['name']
1848        self.app_img = self.index[category][program_id]['img']
1849        self.app_main_package = self.index[category][program_id]['main-package']
1850        self.app_launch_command = self.index[category][program_id]['launch-command']
1851        self.app_upgrade_only = False
1852        try:
1853            if self.index[category][program_id]['upgradable']:
1854                self.app_upgrade_only = True
1855                self.app_upgrade_packages = self.index[category][program_id]['upgrade-packages']
1856        except:
1857            self.app_upgrade_only = False
1858
1859        if not self.app_upgrade_only:
1860            self.app_install_packages = self.index[category][program_id]['install-packages']
1861            self.app_remove_packages = self.index[category][program_id]['remove-packages']
1862        self.app_description = ''
1863        for line in self.index[category][program_id]['description']:
1864            self.app_description = self.app_description + ' ' + line
1865        self.app_alternate_to = self.index[category][program_id]['alternate-to']
1866        self.app_subcategory = self.index[category][program_id]['subcategory']
1867        self.app_open_source = self.index[category][program_id]['open-source']
1868        self.app_url_info = self.index[category][program_id]['url-info']
1869        self.app_url_android = self.index[category][program_id]['url-android']
1870        self.app_url_ios = self.index[category][program_id]['url-ios']
1871        self.app_arch = self.index[category][program_id]['arch']
1872        self.app_releases = self.index[category][program_id]['releases']
1873        self.app_working = self.index[category][program_id]['working']
1874
1875    def populate_categories(self, webkit):
1876        ''' List all of the applications supported on the current architecture. '''
1877        total_added = 0
1878        total_skipped = 0
1879        total_unsupported = 0
1880
1881        # Don't attempt to continue if the index is missing/incorrectly parsed.
1882        if not self.index:
1883            print('[Apps] ERROR: Application index not loaded. Cannot populate categories.')
1884            return
1885
1886        # Strings
1887        str_nothing_here = _("Sorry, Welcome could not feature any software for this category that is compatible on this system.")
1888        str_upgraded = _("This application is set to receive the latest updates.")
1889        str_alternate_to = _('Alternative to:')
1890        str_hide = _("Hide")
1891        str_show = _("Show")
1892        str_install = _("Install")
1893        str_reinstall = _("Reinstall")
1894        str_remove = _("Remove")
1895        str_upgrade = _("Upgrade")
1896        str_launch = _("Launch")
1897        str_license = _("License")
1898        str_platform = _("Platform")
1899        str_category = _("Category")
1900        str_website = _("Website")
1901        str_screenshot = _("Screenshot")
1902        str_source = _("Source")
1903        str_source_ppa = '<span class="fa fa-cube"></span>&nbsp;'
1904        str_source_manual = '<span class="fa fa-globe"></span></a>&nbsp;'
1905        str_source_partner = '<img src="img/logos/ubuntu-mono.png" width="16px" height="16px"/>&nbsp;' + _('Canonical Partner Repository')
1906        str_source_skip = '<img src="img/logos/ubuntu-mono.png" width="16px" height="16px"/>&nbsp;' + _('Ubuntu Repository')
1907        str_unknown = _('Unknown')
1908
1909        # Get the app data from each category and list them.
1910        for category in self.all_categories:
1911            arg.print_verbose('Apps', ' ------ Processing: ' + category + ' ------')
1912
1913            # Convert to a list to work with. Sort alphabetically.
1914            category_items = list(self.index[category].keys())
1915            category_items.sort()
1916
1917            # Keep a count of apps in case there are none to list.
1918            apps_here = 0
1919
1920            # Keep track of the subcategories of the apps in this category so we can filter them.
1921            subcategories = []
1922
1923            # Enumerate each program in this category.
1924            for program_id in category_items:
1925                self.set_app_info(category, program_id)
1926
1927                # Only list the program if it's working.
1928                if not self.app_working:
1929                    arg.print_verbose('Apps', ' Unlisted: ' + self.app_name)
1930                    total_skipped = total_skipped + 1
1931                    continue
1932
1933                # Only list the program if it supports the current architecture in use.
1934                supported = False
1935                supported_arch = False
1936                supported_release = False
1937
1938                for architecture in self.app_arch.split(','):
1939                    if architecture == systemstate.arch:
1940                        supported_arch = True
1941
1942                # Only list the program if it's available for the current release.
1943                for release in self.app_releases.split(','):
1944                    if release == systemstate.codename:
1945                        supported_release = True
1946
1947                if supported_arch and supported_release:
1948                    supported = True
1949
1950                if not supported:
1951                    arg.print_verbose('Apps', ' Unsupported: ' + self.app_name + ' (Only for architectures: ' + self.app_arch + ' and releases: ' + self.app_releases + ')' )
1952                    total_unsupported = total_unsupported + 1
1953                    continue
1954
1955                # If the app has made it this far, it can be added to the grid.
1956                # CSS breaks with dots (.), so any must become hyphens (-).
1957                arg.print_verbose('Apps', ' Added: ' + self.app_name)
1958                subcategories.append(self.app_subcategory)
1959                html_buffer = ''
1960                css_class = program_id.replace('.','-')
1961                css_subcategory = self.app_subcategory.replace(' ','-')
1962
1963                # "Normal" packages that can be installed/removed by the user.
1964                if self.app_open_source:
1965                    html_buffer = html_buffer + '<div id="' + css_class + '" class="app-entry filter-' + css_subcategory + '">'
1966                else:
1967                    html_buffer = html_buffer + '<div id="' + css_class + '" class="app-entry filter-' + css_subcategory + ' proprietary">'
1968                html_buffer = html_buffer + '<div class="row-fluid">'
1969                html_buffer = html_buffer + '<div class="span2 center-inside">'
1970                html_buffer = html_buffer + '<img src="img/applications/' + self.app_img + '.png">'
1971                html_buffer = html_buffer + '<span class="fa fa-check-circle fa-2x installed-check ' + css_class + '-remove"></span>'
1972                html_buffer = html_buffer + '</div><div class="span10">'
1973                html_buffer = html_buffer + '<p><b class="' + css_class + '-text">' + self.app_name + '</b></p>'
1974                html_buffer = html_buffer + '<p class="' + css_class + '-text">' + self.app_description + '</p>'
1975
1976                # Check any "Upgrade" packages if the PPA has already been added.
1977                upgraded = False
1978                if self.app_upgrade_only:
1979                    try:
1980                        listname = dynamicapps.index[category][program_id]['pre-install']['all']['source-file']
1981                        listname = listname.replace('OSVERSION',preinstallation.os_version).replace('CODENAME',preinstallation.codename)
1982                        if os.path.exists(os.path.join('/', 'etc', 'apt', 'sources.list.d', listname+'.list')):
1983                            upgraded = True
1984                            html_buffer = html_buffer + '<h5 class="' + css_class + '-text"><span class="fa fa-check-circle"></span> ' + str_upgraded + '</h5>'
1985                    except:
1986                        pass
1987
1988                if not self.app_alternate_to == None:
1989                    html_buffer = html_buffer + '<ul><li class="' + css_class + '-text"><b>' + str_alternate_to + ' </b><i>' + self.app_alternate_to + '</i></li></ul>'
1990                html_buffer = html_buffer + '<p class="text-right">'
1991                html_buffer = html_buffer + '<a id="info-show-' + css_class + '" class="btn" href="cmd://app-info-show?' + css_class + '"><span class="fa fa-chevron-down"></span> ' + str_show + '</a>&nbsp;'
1992                html_buffer = html_buffer + '<a hidden id="info-hide-' + css_class + '" class="btn" href="cmd://app-info-hide?' + css_class + '"><span class="fa fa-chevron-up"></span> ' + str_hide + '</a>&nbsp;'
1993
1994                # "Regular" packages - can be installed or removed with one-click by the user.
1995                if not self.app_upgrade_only:
1996                    html_buffer = html_buffer + '<span class="' + css_class + '-applying"> <span class="' + css_class + '-applying-status"></span> &nbsp;<img src="img/welcome/processing.gif" width="24px" height="24px"/></span>'
1997                    html_buffer = html_buffer + '<a class="' + css_class + '-install btn btn-success" href="cmd://install-appid?' + program_id + '"><span class="fa fa-download"></span>&nbsp; ' + str_install + '</a>&nbsp;'
1998                    html_buffer = html_buffer + '<a class="' + css_class + '-reinstall btn btn-warning" href="cmd://install-appid?' + program_id + '" data-toggle="tooltip" data-placement="top" title="' + str_reinstall + '"><span class="fa fa-refresh"></span></a>&nbsp;'
1999                    html_buffer = html_buffer + '<a class="' + css_class + '-remove btn btn-danger" href="cmd://remove-appid?' + program_id + '" data-toggle="tooltip" data-placement="top" title="' + str_remove + '"><span class="fa fa-trash"></span></a>&nbsp;'
2000
2001                # "Upgradable" packages - usually pre-installed but have a more up-to-date repository.
2002                if self.app_upgrade_only:
2003                    arg.print_verbose('Apps', 'Upgrade: ' + self.app_name)
2004                    if not upgraded:
2005                        html_buffer = html_buffer + '<a class="' + css_class + '-upgrade btn btn-warning" href="cmd://upgrade-appid?' + program_id + '"><span class="fa fa-level-up"></span>&nbsp; ' + str_upgrade + '</a>&nbsp;'
2006
2007                if not self.app_launch_command == None:
2008                    html_buffer = html_buffer + '<a class="' + css_class + '-launch btn btn-inverse" href="cmd://launch-appid?' + program_id + '"><img src="img/applications/' + self.app_img + '.png" width="20px" height="20px" />&nbsp; ' + str_launch + '</a>&nbsp;'
2009
2010                # More details section.
2011                html_buffer = html_buffer + '</p><div hidden id="details-' + css_class + '">'
2012
2013                ## Determine string for license
2014                if self.app_open_source:
2015                    license_string = _('Open Source')
2016                else:
2017                    license_string = _('Proprietary')
2018
2019                ## Determine supported platforms
2020                platform_string = ''
2021                for arch in self.app_arch.split(','):
2022                    if arch == 'i386':
2023                        platform_string = platform_string + '<span class="i386"><span class="i386 fa fa-laptop"></span> 32-bit</span> &nbsp;&nbsp;'
2024                    elif arch =='amd64':
2025                        platform_string = platform_string + '<span class="amd64"><span class="fa fa-laptop"></span> 64-bit</span> &nbsp;&nbsp;'
2026                    elif arch =='armhf':
2027                        platform_string = platform_string + '<span class="armhf"><span class="fa fa-tv"></span> aarch32 (ARMv7)</span> &nbsp;&nbsp;'
2028                    elif arch =='powerpc':
2029                        platform_string = platform_string + '<span class="powerpc"><span class="fa fa-desktop"></span> PowerPC</span> &nbsp;&nbsp;'
2030
2031                ## Add Android / iOS app links if necessary.
2032                if not self.app_url_android == None:
2033                    platform_string = platform_string + '<a href="cmd://link?' + self.app_url_android + '"><span class="fa fa-android"></span> Android</a> &nbsp;&nbsp;'
2034
2035                if not self.app_url_ios == None:
2036                    platform_string = platform_string + '<a href="cmd://link?' + self.app_url_ios + '"><span class="fa fa-apple"></span> iOS</a> &nbsp;&nbsp;'
2037
2038                ## Add details about the source of this file.
2039                try:
2040                    preinstall = dynamicapps.index[category][program_id]['pre-install']
2041                    codenames = list(preinstall.keys())
2042                    target = None
2043                    for name in codenames:
2044                        if name == systemstate.codename:
2045                            target = name
2046                            break
2047                    if not target:
2048                            target = 'all'
2049
2050                    methods = preinstall[target]['method'].split('+')
2051                    self.source_info = []
2052                    if len(methods) > 1:
2053                        multiple_sources = True
2054                    else:
2055                        multiple_sources = False
2056
2057                    for method in methods:
2058                        if method == 'skip':
2059                            self.source_info.insert(0, str_source_skip)
2060
2061                        elif method == 'partner-repo':
2062                            self.source_info.insert(0, str_source_partner)
2063
2064                        elif method == 'ppa':
2065                            ppa = preinstall[target]['enable-ppa']
2066                            ppa_author = ppa.split(':')[1].split('/')[0]
2067                            ppa_archive = ppa.split(':')[1].split('/')[1]
2068                            self.source_info.insert(0, str_source_ppa + ' <a href="cmd://link?https://launchpad.net/~' + ppa_author + '/+archive/ubuntu/' + ppa_archive + '">' + ppa + '</a>')
2069
2070                        elif method == 'manual':
2071                            apt_source = ''.join(preinstall[target]['apt-sources'])
2072                            manual_text = str_source_manual + ' ' + str_unknown
2073                            for substring in apt_source.split(' '):
2074                                if substring[:4] == 'http':
2075                                    apt_source = substring.replace('OSVERSION',preinstallation.os_version).replace('CODENAME',preinstallation.codename)
2076                                    manual_text = str_source_manual + ' ' + apt_source
2077                                    break
2078                            self.source_info.insert(0, manual_text)
2079
2080                except:
2081                    print('[Apps] WARNING: Error occurred while processing pre-configuration! Skipped Source: ' + program_id)
2082                    self.source_info = [str_unknown]
2083
2084                ## Write contents of the table.
2085                html_buffer = html_buffer + '<table class="more-details table table-striped">'
2086                html_buffer = html_buffer + '<tr><th>' + str_license + '</th><td>' + license_string + '</td></tr>'
2087                html_buffer = html_buffer + '<tr><th>' + str_platform + '</th><td>' + platform_string + '</td></tr>'
2088                html_buffer = html_buffer + '<tr><th>' + str_category + '</th><td>' + self.app_subcategory + '</td></tr>'
2089
2090                ## Add a website URL if there is one.
2091                if self.app_url_info:
2092                    html_buffer = html_buffer + '<tr><th>' + str_website + '</th><td><a href="cmd://link?' + self.app_url_info + '">' + self.app_url_info + '</a></td></tr>'
2093
2094                ## Add the source for this application.
2095                if multiple_sources:
2096                    html_buffer = html_buffer + '<tr><th>' + str_source + '</th><td><ul>'
2097                    for item in self.source_info:
2098                        html_buffer = html_buffer + '<li>' + item + '</li>'
2099                    html_buffer = html_buffer + '</td></tr></ul>'
2100                else:
2101                    html_buffer = html_buffer + '<tr><th>' + str_source + '</th><td>' + self.source_info[0] + '</td></tr>'
2102
2103                ## Add a screenshot if there is any.
2104                ## Images should be labelled the same as 'img' and increment starting at 1.
2105                screenshots = 1
2106                screenshots_end = False
2107                screenshot_buffer = ''
2108                while not screenshots_end:
2109                    screenshot_path = os.path.join(app._data_path + 'img/applications/screenshots/' + self.app_img + '-' + str(screenshots) + '.jpg')
2110                    if os.path.exists(screenshot_path):
2111                        screenshot_buffer = screenshot_buffer + '<a class="screenshot-link" href="cmd://screenshot?' + self.app_img + '-' + str(screenshots) + '"><img src="' + screenshot_path + '" class="screenshot"/></a>'
2112                        screenshots = screenshots + 1
2113                    else:
2114                        screenshots_end = True
2115
2116                if not screenshots == 1:
2117                    html_buffer = html_buffer + '<tr><th>' + str_screenshot + '</th><td>' + screenshot_buffer + '</td></tr>'
2118
2119                html_buffer = html_buffer + '</table>'
2120
2121                # End the div's for this application.
2122                html_buffer = html_buffer + '</div><br><hr class="soften"></div></div></div>'
2123
2124                # Append buffer to page
2125                webkit.execute_script('$("#' + category + '").append(\'' + html_buffer + '\')')
2126                webkit.execute_script('$("#info-hide-' + css_class + '").hide()')
2127
2128                # Keep track of how many apps added.
2129                apps_here = apps_here + 1
2130                total_added = total_added + 1
2131
2132            # Display a message if there is nothing for this category.
2133            if apps_here == 0:
2134                webkit.execute_script('$("#' + category + '").append("<p class=\'center\'><span class=\'fa fa-warning\'></span>&nbsp; ' + str_nothing_here + '</p>")')
2135
2136            # Post actions to page
2137            ## Colour the architecture currently in use.
2138            webkit.execute_script('$(".' + systemstate.arch + '").addClass("arch-in-use")')
2139
2140            # Process filters for this category.
2141            filters = list(set(subcategories))
2142            filters.sort()
2143            for string in filters:
2144                css_subcategory = string.replace(' ','-')
2145                webkit.execute_script('$("#Filter-' + category + '").append(\'<option value="' + css_subcategory + '">' + string + '</option>\')')
2146
2147        # "Stats for nerds"
2148        total_apps = total_added + total_skipped + total_unsupported
2149        arg.print_verbose('Apps','------------------')
2150        arg.print_verbose('Apps','Applications added: ' + str(total_added))
2151        arg.print_verbose('Apps','Applications unsupported on this architecture: ' + str(total_unsupported))
2152        arg.print_verbose('Apps','Applications that are broken or not suitable for inclusion: ' + str(total_skipped))
2153        arg.print_verbose('Apps','Total number of applications: ' + str(total_apps))
2154        arg.print_verbose('Apps','------------------')
2155
2156    def populate_featured_apps(self, webkit):
2157        arg.print_verbose('Apps', '---- Populating Featured Apps Grid ----')
2158        # Randomly generate a list of apps to feature if supported on this architecture.
2159        possible_apps = []
2160        for category in self.all_categories:
2161            category_items = list(self.index[category].keys())
2162            for program_id in category_items:
2163                if systemstate.arch in self.index[category][program_id]['arch']:
2164                    possible_apps.append(self.index[category][program_id]['img'])
2165
2166        random.shuffle(possible_apps)
2167        for no in range(0,17):
2168            arg.print_verbose('Apps', str(no) + '. ' + possible_apps[no])
2169            webkit.execute_script("addToGrid('" + possible_apps[no] + "');")
2170        webkit.execute_script("initGrid();")
2171        arg.print_verbose('Apps','------------------')
2172
2173    def modify_app(self, webkit, action, program_id):
2174        ''' Installs, removes or upgrades an application. '''
2175        # Indicate changes are in progress.
2176        css_class = program_id.replace('.','-')
2177        webkit.execute_script("$('." + css_class + "-applying').show();")
2178        webkit.execute_script("$('." + css_class + "-launch').hide();")
2179        webkit.execute_script("$('." + css_class + "-install').hide();")
2180        webkit.execute_script("$('." + css_class + "-reinstall').hide();")
2181        webkit.execute_script("$('." + css_class + "-remove').hide();")
2182        webkit.execute_script("$('." + css_class + "-upgrade').hide();")
2183        webkit.execute_script("$('." + css_class + "-text').css('color','#000');")
2184
2185        # Text to display when applying changes.
2186        install_text = _("Installing...")
2187        remove_text = _("Removing...")
2188        upgrade_text = _("Upgrading...")
2189
2190        # Asynchronous apt process
2191        if action == 'install':
2192            webkit.execute_script("$('." + css_class + "-applying-status').html('" + install_text + "');")
2193            preinstallation.process_packages(program_id, 'install')
2194        elif action == 'remove':
2195            webkit.execute_script("$('." + css_class + "-applying-status').html('" + remove_text + "');")
2196            preinstallation.process_packages(program_id, 'remove')
2197        elif action == 'upgrade':
2198            webkit.execute_script("$('." + css_class + "-applying-status').html('" + upgrade_text + "');")
2199            preinstallation.process_packages(program_id, 'upgrade')
2200        else:
2201            print('[Apps] An unknown action was requested.')
2202
2203        # Refresh the page to reflect changes (if any).
2204        self._apt_cache.close()
2205        self._apt_cache = apt.Cache()
2206        self.update_app_status(webkit, program_id)
2207
2208    def update_app_status(self, webkit, program_id):
2209        ''' Update the web page for an individual application. '''
2210
2211        # Don't attempt to continue if the index is missing/incorrectly parsed.
2212        if not self.index:
2213            print('[Apps] ERROR: Application index not loaded. Cannot update application status.')
2214            return
2215
2216        # Check whether the application is installed or not.
2217        main_package = self.get_attribute_for_app(program_id, 'main-package')
2218        try:
2219            if self._apt_cache[main_package].is_installed:
2220                this_installed = True
2221                arg.print_verbose('Apps', '  Installed: ' + main_package)
2222            else:
2223                this_installed = False
2224                arg.print_verbose('Apps', 'Not present: ' + main_package)
2225        except:
2226            this_installed = False
2227            arg.print_verbose('Apps', 'Not present: ' + main_package)
2228
2229        # Replace any dots with dashes, as they are unsupported in CSS.
2230        css_class = program_id.replace('.','-')
2231
2232        # Update appearance on this page.
2233        webkit.execute_script("$('." + css_class + "-applying').hide();")
2234        if this_installed:
2235            webkit.execute_script("$('." + css_class + "-launch').show();")
2236            webkit.execute_script("$('." + css_class + "-install').hide();")
2237            webkit.execute_script("$('." + css_class + "-reinstall').show();")
2238            webkit.execute_script("$('." + css_class + "-remove').show();")
2239            webkit.execute_script("$('." + css_class + "-upgrade').show();")
2240        else:
2241            webkit.execute_script("$('." + css_class + "-launch').hide();")
2242            webkit.execute_script("$('." + css_class + "-install').show();")
2243            webkit.execute_script("$('." + css_class + "-reinstall').hide();")
2244            webkit.execute_script("$('." + css_class + "-remove').hide();")
2245            webkit.execute_script("$('." + css_class + "-upgrade').hide();")
2246
2247    def update_all_app_status(self, webkit):
2248        ''' Update the webpage whether all indexed applications are installed or not. '''
2249
2250        # Don't attempt to continue if the index is missing/incorrectly parsed.
2251        if not self.index:
2252            print('[Apps] ERROR: Application index not loaded. Cannot update page.')
2253            return
2254
2255        # Enumerate each program and check each one from the index.
2256        arg.print_verbose('Apps', '---- Checking cache for installed applications ----')
2257        for category in self.all_categories:
2258            category_items = list(self.index[category].keys())
2259            for program_id in category_items:
2260                main_package = self.index[category][program_id]['main-package']
2261                # Only check if it's supported on this architecture.
2262                if systemstate.arch in self.index[category][program_id]['arch']:
2263                    self.update_app_status(webkit, program_id)
2264                else:
2265                    continue
2266
2267        arg.print_verbose('Apps', '----------------------------------------')
2268
2269    def get_attribute_for_app(self, requested_id, attribute):
2270        ''' Retrieves a specific attribute from a listed application,
2271            without specifying its category. '''
2272        for category in list(self.index.keys()):
2273            category_items = list(self.index[category].keys())
2274            for program_id in category_items:
2275                if program_id == requested_id:
2276                    if not attribute == 'category':
2277                        return self.index[category][program_id][attribute]
2278                    else:
2279                        return category
2280
2281    def launch_app(self, appid):
2282        ''' Launch an application directly from Welcome '''
2283        program_name = self.get_attribute_for_app(appid, 'name')
2284        program_command = self.get_attribute_for_app(appid, 'launch-command')
2285        print('[Apps] Launched "' + program_name + '" (Command: "' + program_command + '").')
2286        try:
2287            subprocess.Popen(program_command.split(' '))
2288        except:
2289            print('[Apps] Failed to launch command: ' + program_command)
2290            title = _("Software Boutique")
2291            ok_label = _("OK")
2292            text_error = _("An error occurred while launching PROGRAM_NAME. Please consider re-installing the application.").replace('PROGRAM_NAME', program_name) + \
2293                            '\n\n' + _("Command:") + ' "' + program_command + '"'
2294            messagebox = subprocess.Popen(['zenity',
2295                         '--error',
2296                         '--title=' + title,
2297                         "--text=" + text_error,
2298                         "--ok-label=" + ok_label,
2299                         '--window-icon=error',
2300                         '--timeout=15'])
2301
2302    def apply_filter(self, webkit, filter_value, nonfree_toggle=False):
2303        sub_css_class = 'filter-' + filter_value
2304
2305        # Toggle visibility of non-free software.
2306        if nonfree_toggle:
2307            if self.hide_non_free:
2308                self.hide_non_free = False
2309                webkit.execute_script('$("#nonFreeCheckBox").addClass("fa-square");')
2310                webkit.execute_script('$("#nonFreeCheckBox").removeClass("fa-check-square");')
2311            else:
2312                self.hide_non_free = True
2313                webkit.execute_script('$("#nonFreeCheckBox").removeClass("fa-square");')
2314                webkit.execute_script('$("#nonFreeCheckBox").addClass("fa-check-square");')
2315
2316        if filter_value == 'none':
2317            arg.print_verbose('Apps','Filter reset.')
2318            webkit.execute_script('$(".app-entry").show();')
2319            if self.hide_non_free:
2320                arg.print_verbose('Apps','Hiding all proprietary software.')
2321                webkit.execute_script('$(".proprietary").hide();')
2322            return
2323        else:
2324            arg.print_verbose('Apps','Applying filter: ' + filter_value)
2325            webkit.execute_script('$(".app-entry").hide();')
2326
2327            for category in self.all_categories:
2328                category_items = list(self.index[category].keys())
2329                for program_id in category_items:
2330                    app_subcategory = self.index[category][program_id]['subcategory'].replace(' ','-')
2331                    app_open_source = self.index[category][program_id]['open-source']
2332
2333                    # If the application is closed source and we're told to hide it.
2334                    if not app_open_source and self.hide_non_free:
2335                        webkit.execute_script('$("#' + program_id.replace('.','-') + '").hide();')
2336                        continue
2337
2338                    # Only show if subcategory matches.
2339                    if app_subcategory.replace(' ','-') == filter_value:
2340                        webkit.execute_script('$("#' + program_id.replace('.','-') + '").show();')
2341
2342    def show_screenshot(self, filename):
2343        ssw = ScreenshotWindow(filename)
2344
2345
2346class ScreenshotWindow(Gtk.Window):
2347    ''' Displays a simple window when enlarging a screenshot. '''
2348
2349    # FIXME: Destroy this window when finished as it prevents the app from closing via the "Close" button and bloats memory.
2350
2351    def __init__(self, filename):
2352        # Strings for this child window.
2353        title_string = 'Preview Screenshot'
2354        close_string = 'Close'
2355        path = app._data_path + '/img/applications/screenshots/' + filename + '.jpg'
2356
2357        # Build a basic pop up window containing the screenshot at its full dimensions.
2358        Gtk.Window.__init__(self, title=title_string)
2359        self.overlay = Gtk.Overlay()
2360        self.add(self.overlay)
2361        self.background = Gtk.Image.new_from_file(path)
2362        self.overlay.add(self.background)
2363        self.grid = Gtk.Grid()
2364        self.overlay.add_overlay(self.grid)
2365        self.connect('button-press-event', self.destroy_window)      # Click anywhere to close the window.
2366        self.connect('delete-event', Gtk.main_quit)
2367        self.set_position(Gtk.WindowPosition.CENTER)
2368        self.set_resizable(False)
2369        # FIXME: Set the cursor to a hand, like it was a link.
2370        #~ self.get_root_window().set_cursor(Gdk.Cursor(Gdk.CursorType.HAND1))
2371        self.show_all()
2372        Gtk.main()
2373
2374    def destroy_window(self, widget, dummy=None):
2375        self.destroy()
2376
2377
2378class Arguments(object):
2379    '''Check arguments passed the application.'''
2380
2381    def __init__(self):
2382        self.verbose_enabled = False
2383        self.simulate_arch = None
2384        self.simulate_session = None
2385        self.simulate_codename = None
2386        self.simulate_no_connection = False
2387        self.simulate_force_connection = False
2388        self.jump_software_page = False
2389        self.simulate_software_changes = False
2390        self.locale = None
2391        self.jump_to = None
2392        self.font_dpi_override = None
2393
2394        for arg in sys.argv:
2395          if arg == '--help':
2396              print('\nUbuntu MATE Welcome Parameters\n  Intended for debugging and testing purposes only!\n')
2397              print('\nUsage: ubuntu-mate-welcome [arguments]')
2398              print('  -v  --verbose               Show more details.')
2399              print('  --force-arch=<ARCH>         Simulate a specific architecture.')
2400              print('                                 "i386", "amd64" or "armhf" or "powerpc"')
2401              print('  --force-session=<TYPE>      Simulate a specific architecture.')
2402              print('                                 "guest", "live" or "pi" or "vbox"')
2403              print('  --force-codename=<NAME>     Simulate a specific Ubuntu MATE codename release.')
2404              print('                                 Examples: "trusty", "wily" or "xenial"')
2405              print('  --force-no-net              Simulate no internet connection.')
2406              print('  --force-net                 Simulate a working internet connection.')
2407              print('  --software-only             Open Welcome only for the software selections.')
2408              print('  --simulate-changes          Simulate software package changes without modifying the system.')
2409              print('  --locale=<LOCALE>           Locale to use e.g. fr_FR.')
2410              print('  --jump-to=<page>            Open a specific page, excluding html extension.')
2411              print('  --font-dpi=<number>         Override the font size by specifying a font DPI.')
2412              print('')
2413              exit()
2414
2415          if arg == '--verbose' or arg == '-v':
2416              print('[Debug] Verbose mode enabled.')
2417              self.verbose_enabled = True
2418
2419          if arg.startswith('--force-arch'):
2420              try:
2421                  self.simulate_arch = arg.split('--force-arch=')[1]
2422                  if not self.simulate_arch == 'i386' and not self.simulate_arch == 'amd64' and not self.simulate_arch == 'armhf' and not self.simulate_arch == 'powerpc':
2423                      print('[Debug] Unrecognised architecture: ' + self.simulate_arch)
2424                      exit()
2425                  else:
2426                      print('[Debug] Simulating architecture: ' + self.simulate_arch)
2427              except:
2428                  print('[Debug] Invalid arguments for "--force-arch"')
2429                  exit()
2430
2431          if arg.startswith('--force-session'):
2432              try:
2433                  self.simulate_session = arg.split('--force-session=')[1]
2434                  if not self.simulate_session == 'guest' and not self.simulate_session == 'live' and not self.simulate_session == 'pi' and not self.simulate_session == 'vbox':
2435                      print('[Debug] Unrecognised session type: ' + self.simulate_session)
2436                      exit()
2437                  else:
2438                      print('[Debug] Simulating session: ' + self.simulate_session)
2439              except:
2440                  print('[Debug] Invalid arguments for "--force-session"')
2441                  exit()
2442
2443          if arg.startswith('--force-codename'):
2444              self.simulate_codename = arg.split('--force-codename=')[1]
2445              print('[Debug] Simulating Ubuntu MATE release: ' + self.simulate_codename)
2446
2447          if arg == '--force-no-net':
2448              print('[Debug] Simulating the application without an internet connection.')
2449              self.simulate_no_connection = True
2450
2451          if arg == '--force-net':
2452              print('[Debug] Forcing the application to think we\'re connected with an internet connection.')
2453              self.simulate_force_connection = True
2454
2455          if arg == '--software-only':
2456              print('[Welcome] Starting in software selections only mode.')
2457              self.jump_software_page = True
2458
2459          if arg == '--simulate-changes':
2460              print('[Debug] Any changes to software will be simulated without modifying the actual system.')
2461              self.simulate_software_changes = True
2462
2463          if arg.startswith('--locale='):
2464              self.locale = arg.split('--locale=')[1]
2465              print('[Debug] Setting locale to: ' + self.locale)
2466
2467          if arg.startswith('--jump-to='):
2468              self.jump_to = arg.split('--jump-to=')[1]
2469              print('[Debug] Opening page: ' + self.jump_to + '.html')
2470
2471          if arg.startswith('--font-dpi='):
2472              try:
2473                  self.font_dpi_override = int(arg.split('--font-dpi=')[1])
2474              except:
2475                  print('[Debug] Invalid Override Font DPI specified. Ignoring.')
2476                  return
2477              print('[Debug] Overriding font DPI to ' + str(self.font_dpi_override) + '.')
2478
2479    def print_verbose(self, feature, text):
2480        if self.verbose_enabled:
2481            print('[' + feature + '] ' + text)
2482
2483    def override_arch(self):
2484        if not self.simulate_arch == None:
2485            systemstate.arch = self.simulate_arch
2486
2487    def override_session(self):
2488        if not self.simulate_session == None:
2489            if self.simulate_session == 'vbox':
2490                systemstate.graphics_vendor = 'VirtualBox'
2491                systemstate.graphics_grep = 'VirtualBox'
2492            else:
2493                systemstate.session_type = self.simulate_session
2494
2495    def override_codename(self):
2496        if not self.simulate_codename == None:
2497            systemstate.codename = self.simulate_codename
2498
2499
2500if __name__ == "__main__":
2501
2502    # Process any parameters passed to the program.
2503    arg = Arguments()
2504
2505    # Application Initialization
2506    set_proc_title()
2507    systemstate = SystemState()
2508    app = WelcomeApp()
2509    dynamicapps = DynamicApps()
2510    preinstallation = PreInstallation()
2511
2512    # Argument Overrides
2513    arg.override_arch()
2514    arg.override_session()
2515    arg.override_codename()
2516
2517    print('[Welcome] Application Started.')
2518    app.run()
Note: See TracBrowser for help on using the repository browser.