source: lliurex-mate-welcome/trunk/fuentes/lliurex-mate-welcome @ 5163

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