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

Last change on this file since 3931 was 3931, checked in by alviboi, 3 years ago
  • Property svn:executable set to *
File size: 131.0 KB
Line 
1#! /usr/bin/python3
2# -*- coding:utf-8 -*-
3#
4# Copyright 2012-2013 "Korora Project" <dev@kororaproject.org>
5# Copyright 2013 "Manjaro Linux" <support@manjaro.org>
6# Copyright 2014 Antergos
7# Copyright 2015-2016 Martin Wimpress <code@flexion.org>
8# Copyright 2015-2016 Luke Horwell <lukehorwell37+code@gmail.com>
9#
10# Ubuntu MATE Welcome is free software: you can redistribute it and/or modify
11# it under the temms of the GNU General Public License as published by
12# the Free Software Foundation, either version 3 of the License, or
13# (at your option) any later version.
14#
15# Ubuntu MATE Welcome is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with Ubuntu MATE Welcome. If not, see <http://www.gnu.org/licenses/>.
22#
23
24""" Welcome screen for Ubuntu MATE """
25
26import gi
27gi.require_version("Gdk", "3.0")
28gi.require_version("Gtk", "3.0")
29gi.require_version("Notify", "0.7")
30gi.require_version("WebKit", "3.0")
31
32import apt
33import errno
34import gettext
35import glob
36import inspect
37import json
38import locale
39import logging
40import os
41import platform
42import random
43import shutil
44import signal
45import socket
46import subprocess
47import sys
48import tempfile
49import urllib.request
50import webbrowser
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/ubuntu-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/ubuntu-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        else:
1058            print('[Error] Unknown command: ', uri)
1059      except Exception as e:
1060        print('[Error] Failed to execute command: ', uri)
1061        print('[Error] Exception: ', e)
1062
1063
1064class WelcomeApp(object):
1065    def __init__(self):
1066        # establish our location
1067        self._location = os.path.dirname( os.path.abspath(inspect.getfile(inspect.currentframe())) )
1068
1069        # check for relative path
1070        if( os.path.exists( os.path.join(self._location, 'data/' ) ) ):
1071            print('[Debug] Using relative path for data source. Non-production testing.')
1072            self._data_path = os.path.join(self._location, 'data/')
1073        elif( os.path.exists('/usr/share/lliurex-mate-welcome/') ):
1074            print('Using /usr/share/lliurex-mate-welcome/ path.')
1075            self._data_path = '/usr/share/lliurex-mate-welcome/'
1076        else:
1077            print('Unable to source the lliurex-mate-welcome data directory.')
1078            sys.exit(1)
1079
1080        self._build_app()
1081
1082    def _get_translated_slides(self):
1083        """ If a locale has been specified on the command line, get translated slides
1084            for that. If not, get translated slides for the current locale
1085
1086        Do not assume that every slide has a translation, check each file individually
1087
1088        Returns:
1089            a list of filenames of translated slides - if there is no translated version
1090            of a slide, the filename of the untranslated version from the _data_path directory
1091            is used instead
1092
1093        """
1094
1095        if (arg.locale is not None):
1096            locale_to_use = arg.locale
1097        else:
1098            locale_to_use = str(locale.getlocale()[0])       
1099
1100        def set_locale_dir(this_locale):
1101            # check for relative path
1102            if (os.path.exists(os.path.join(self._location, 'i18n', this_locale))):
1103                locale_dir = os.path.join(self._location, 'i18n', this_locale)
1104                print('[i18n] Using ' + this_locale + '. Non-production testing.')
1105            elif (os.path.exists(os.path.join('/usr/share/lliurex-mate-welcome/i18n/', this_locale))):
1106                locale_dir = os.path.join('/usr/share/lliurex-mate-welcome/i18n/', this_locale)
1107                print('[i18n] Using ' + this_locale)
1108            else:
1109                locale_dir = ''
1110                print('[i18n] Locale ' + this_locale + ' not available.')
1111
1112            return locale_dir
1113
1114        # if no locale exists, try a generic locale.
1115        locale_dir = set_locale_dir(locale_to_use)
1116        if locale_dir == '':
1117            locale_generic = locale_to_use.split('_')[0]
1118            print('[i18n] Trying ' + locale_generic + '...')
1119            locale_dir = set_locale_dir(locale_generic)
1120
1121        results = []
1122
1123        slides = glob.glob(os.path.join(self._data_path, '*.html'))
1124        for slide in slides:
1125            # get the slide name and see if a translated version exists
1126            head, slide_name = os.path.split(slide)
1127
1128            trans_slide = os.path.join(locale_dir, slide_name)
1129            if os.path.exists(trans_slide):
1130                results.append(trans_slide)
1131                arg.print_verbose("i18n","Will use %s translation of %s" %(locale_to_use, slide_name))
1132            else:
1133                results.append(slide)
1134                arg.print_verbose("i18n","No %s translation of %s found. Will use version in _data_path" %(locale_to_use, slide))
1135
1136        return results
1137
1138    def _build_app(self):
1139
1140        # Slightly different attributes if "--software-only" is activated.
1141        if arg.jump_software_page:
1142            title = _("Software Boutique")
1143            width = 900
1144            height = 600
1145            load_file = 'software-only.html'
1146        else:
1147            title = _("LliureX 16")
1148            width = 800
1149            height = 552
1150            load_file = 'splash.html'
1151
1152        # Enlarge the window should the text be any larger.
1153        if systemstate.zoom_level == 1.1:
1154            width = width + 20
1155            height = height + 20
1156        elif systemstate.zoom_level == 1.2:
1157            width = width + 60
1158            height = height + 40
1159        elif systemstate.zoom_level == 1.3:
1160            width = width + 100
1161            height = height + 60
1162        elif systemstate.zoom_level == 1.4:
1163            width = width + 130
1164            height = height + 100
1165        elif systemstate.zoom_level == 1.5:
1166            width = width + 160
1167            height = height + 120
1168
1169        # Jump to a specific page for testing purposes.
1170        if arg.jump_to:
1171            load_file = arg.jump_to + '.html'
1172
1173        # build window
1174        w = Gtk.Window()
1175        w.set_position(Gtk.WindowPosition.CENTER)
1176        w.set_wmclass('Ubuntu MATE Welcome', 'Ubuntu MATE Welcome')
1177        w.set_title(title)
1178
1179        # http://askubuntu.com/questions/153549/how-to-detect-a-computers-physical-screen-size-in-gtk
1180        s = Gdk.Screen.get_default()
1181        if s.get_height() <= 600:
1182            w.set_size_request(768, 528)
1183        else:
1184            w.set_size_request(width, height)
1185
1186        icon_dir = os.path.join(self._data_path, 'img', 'welcome', 'ubuntu-mate-icon.svg')
1187        w.set_icon_from_file(icon_dir)
1188
1189        #get the translated slide list
1190        trans_slides = self._get_translated_slides()
1191        # build webkit container
1192        mv = AppView(trans_slides)
1193
1194        # load our index file
1195        file = os.path.abspath(os.path.join(self._data_path, load_file))
1196
1197        uri = 'file://' + urllib.request.pathname2url(file)
1198        mv.open(uri)
1199
1200        # build scrolled window widget and add our appview container
1201        sw = Gtk.ScrolledWindow()
1202        sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
1203        sw.add(mv)
1204
1205        # build an autoexpanding box and add our scrolled window
1206        b = Gtk.VBox(homogeneous=False, spacing=0)
1207        b.pack_start(sw, expand=True, fill=True, padding=0)
1208
1209        # add the box to the parent window and show
1210        w.add(b)
1211        w.connect('delete-event', goodbye)
1212        w.show_all()
1213
1214        self._window = w
1215        self._appView = mv
1216
1217    def run(self):
1218        signal.signal(signal.SIGINT, signal.SIG_DFL)
1219        Gtk.main()
1220
1221    def close(self, p1, p2):
1222        Gtk.main_quit(p1, p2);
1223
1224
1225###############################################3
1226
1227class TutorialApp(object):
1228    def inicia(self):
1229        # establish our location
1230        self._location = os.path.dirname( os.path.abspath(inspect.getfile(inspect.currentframe())) )
1231
1232        # check for relative path
1233        if( os.path.exists( os.path.join(self._location, 'data/' ) ) ):
1234            print('[Debug] Using relative path for data source. Non-production testing.')
1235            self._data_path = os.path.join(self._location, 'data/')
1236        elif( os.path.exists('/usr/share/LliureX-mate-welcome/') ):
1237            print('Using /usr/share/lliurex-mate-welcome/ path.')
1238            self._data_path = '/usr/share/lliurex-mate-welcome/'
1239        else:
1240            print('Unable to source the lliurex-mate-welcome data directory.')
1241            sys.exit(1)
1242
1243        self._build_app()
1244
1245    def _get_translated_slides(self):
1246        """ If a locale has been specified on the command line, get translated slides
1247            for that. If not, get translated slides for the current locale
1248
1249        Do not assume that every slide has a translation, check each file individually
1250
1251        Returns:
1252            a list of filenames of translated slides - if there is no translated version
1253            of a slide, the filename of the untranslated version from the _data_path directory
1254            is used instead
1255
1256        """
1257
1258        if (arg.locale is not None):
1259            locale_to_use = arg.locale
1260        else:
1261            locale_to_use = str(locale.getlocale()[0])
1262
1263        def set_locale_dir(this_locale):
1264            # check for relative path
1265            if (os.path.exists(os.path.join(self._location, 'i18n', this_locale))):
1266                locale_dir = os.path.join(self._location, 'i18n', this_locale)
1267                print('[i18n] Using ' + this_locale + '. Non-production testing.')
1268            elif (os.path.exists(os.path.join('/usr/share/lliurex-mate-welcome/i18n/', this_locale))):
1269                locale_dir = os.path.join('/usr/share/lliurex-mate-welcome/i18n/', this_locale)
1270                print('[i18n] Using ' + this_locale)
1271            else:
1272                locale_dir = ''
1273                print('[i18n] Locale ' + this_locale + ' not available.')
1274
1275            return locale_dir
1276
1277        # if no locale exists, try a generic locale.
1278        locale_dir = set_locale_dir(locale_to_use)
1279        if locale_dir == '':
1280            locale_generic = locale_to_use.split('_')[0]
1281            print('[i18n] Trying ' + locale_generic + '...')
1282            locale_dir = set_locale_dir(locale_generic)
1283
1284        results = []
1285
1286        slides = glob.glob(os.path.join(self._data_path, '*.html'))
1287        for slide in slides:
1288            # get the slide name and see if a translated version exists
1289            head, slide_name = os.path.split(slide)
1290
1291            trans_slide = os.path.join(locale_dir, slide_name)
1292            if os.path.exists(trans_slide):
1293                results.append(trans_slide)
1294                arg.print_verbose("i18n","Will use %s translation of %s" %(locale_to_use, slide_name))
1295            else:
1296                results.append(slide)
1297                arg.print_verbose("i18n","No %s translation of %s found. Will use version in _data_path" %(locale_to_use, slide))
1298
1299        return results
1300
1301    def _build_app(self):
1302
1303        # Slightly different attributes if "--software-only" is activated.
1304       
1305        title = _("LliureX 16")
1306        width = 800
1307        height = 552
1308        load_file = 'foros2.html'
1309
1310        # Enlarge the window should the text be any larger.
1311        if systemstate.zoom_level == 1.1:
1312            width = width + 20
1313            height = height + 20
1314        elif systemstate.zoom_level == 1.2:
1315            width = width + 60
1316            height = height + 40
1317        elif systemstate.zoom_level == 1.3:
1318            width = width + 100
1319            height = height + 60
1320        elif systemstate.zoom_level == 1.4:
1321            width = width + 130
1322            height = height + 100
1323        elif systemstate.zoom_level == 1.5:
1324            width = width + 160
1325            height = height + 120
1326
1327        # build window
1328        w = Gtk.Window()
1329        w.set_position(Gtk.WindowPosition.CENTER)
1330        w.set_wmclass('Ubuntu MATE Welcome', 'Ubuntu MATE Welcome')
1331        w.set_title(title)
1332
1333        # http://askubuntu.com/questions/153549/how-to-detect-a-computers-physical-screen-size-in-gtk
1334        s = Gdk.Screen.get_default()
1335        if s.get_height() <= 600:
1336            w.set_size_request(768, 528)
1337        else:
1338            w.set_size_request(width, height)
1339
1340        icon_dir = os.path.join(self._data_path, 'img', 'welcome', 'ubuntu-mate-icon.svg')
1341        w.set_icon_from_file(icon_dir)
1342
1343        #get the translated slide list
1344        trans_slides = self._get_translated_slides()
1345        # build webkit container
1346        mv = AppView(trans_slides)
1347
1348        # load our index file
1349        file = os.path.abspath(os.path.join(self._data_path, load_file))
1350
1351        uri = 'file://' + urllib.request.pathname2url(file)
1352        mv.open(uri)
1353
1354        # build scrolled window widget and add our appview container
1355        sw = Gtk.ScrolledWindow()
1356        sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
1357        sw.add(mv)
1358
1359        # build an autoexpanding box and add our scrolled window
1360        b = Gtk.VBox(homogeneous=False, spacing=0)
1361        b.pack_start(sw, expand=True, fill=True, padding=0)
1362
1363        # add the box to the parent window and show
1364        w.add(b)
1365        w.connect('delete-event', goodbye)
1366        w.show_all()
1367
1368        self._window = w
1369        self._appView = mv
1370
1371    def run(self):
1372        signal.signal(signal.SIGINT, signal.SIG_DFL)
1373        Gtk.main()
1374
1375    def close(self, p1, p2):
1376        Gtk.main_quit(p1, p2);
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388#################################################
1389
1390
1391class SystemState(object):
1392    def __init__(self):
1393        # Set initial variables
1394        self.is_online = False
1395        self.user_name = getuser()
1396        self.updates_subscribed = False
1397        self.welcome_version = 'Unknown'
1398        self.rpi_resize_pending = False
1399
1400        # Get current architecture of system.
1401        # Outputs 'i386', 'amd64', etc - Based on packages instead of kernel (eg. i686, x86_64).
1402        self.arch = str(subprocess.Popen(['dpkg','--print-architecture'], stdout=subprocess.PIPE).communicate()[0]).strip('\\nb\'')
1403
1404        # Get current codename of Ubuntu MATE in use.
1405        # Uses first word in lowercase, such as : trusty, wily, xenial
1406        self.codename = platform.dist()[2]
1407
1408        def run_external_command(command, with_shell=False):
1409            if with_shell:
1410                raw = str(subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0])
1411            else:
1412                raw = str(subprocess.Popen(command, stdout=subprocess.PIPE).communicate()[0])
1413            output = raw.replace("b'","").replace('b"',"").replace("\\n'","").replace("\\n","")
1414            return output
1415
1416        # Determine which type of session we are in.
1417               
1418        if os.path.exists('/usr/share/glib-2.0/schemas/zubuntu-mate-live.gschema.override'):
1419            self.session_type = 'live'
1420        elif self.user_name[:6] == 'guest-':
1421            self.session_type = 'guest'
1422        elif os.path.isfile(os.path.join('/','boot/','kernel7.img')):
1423            self.session_type = 'pi'
1424        else:
1425            self.session_type = 'normal'
1426
1427        # Determine which type of flavour we are in.
1428
1429        self.session_flavour = str(run_external_command(['lliurex-version','-f']))   
1430
1431       
1432        #print('[SystemState] Lliurex Session: ' + self.session_type)
1433        #print('[SystemState] Lliurex Flavour: ' + self.session_flavour)
1434
1435        # To inform the user if they are running in BIOS or UEFI mode.
1436        if os.path.exists("/sys/firmware/efi"):
1437            self.boot_mode = 'UEFI'
1438        elif self.session_type == 'pi':
1439            self.boot_mode = 'Raspberry Pi'
1440        elif self.arch == 'powerpc':
1441            self.boot_mode = 'Yaboot'
1442        else:
1443            self.boot_mode = 'BIOS'
1444
1445        # Create, then spawn threads
1446        thread1 = Thread(target=self.check_internet_connection)
1447        thread2 = Thread(target=self.detect_graphics)
1448        thread1.start()
1449        thread2.start()
1450
1451        # Check whether Welcome is subscribed for updates.
1452        self.welcome_ppa_file = '/etc/apt/sources.list.d/ubuntu-mate-dev-ubuntu-welcome-' + self.codename + '.list'
1453        if os.path.exists(self.welcome_ppa_file):
1454            if os.path.getsize(self.welcome_ppa_file) > 0:
1455                self.updates_subscribed = True
1456
1457        # Accessibility - Enlarge/shrink text based on Font DPI set by the user.
1458        if arg.font_dpi_override:
1459            font_dpi = arg.font_dpi_override
1460        else:
1461            try:
1462                font_gsettings = Gio.Settings.new('org.mate.font-rendering')
1463                font_value = font_gsettings.get_value('dpi')
1464                font_dpi = int(float(str(font_value)))
1465                arg.print_verbose('Welcome', 'Font DPI is: ' + str(font_dpi))
1466            except:
1467                font_dpi = 96
1468                print('[Welcome] Couldn\'t retrieve font DPI. Using default value of ' + str(font_dpi))
1469
1470        if font_dpi < 50:
1471            print('[Welcome] DPI below 50. Out of range..')
1472            font_dpi = 96
1473        elif font_dpi > 500:
1474            print('[Welcome] DPI over 500. Out of range.')
1475            font_dpi = 96
1476
1477        zoom_level = 1.0
1478        if font_dpi <= 80:
1479            zoom_level = 0.75
1480        elif font_dpi <= 87:
1481            zoom_level = 0.85
1482        elif font_dpi <= 94:
1483            zoom_level = 0.9
1484        elif font_dpi <= 101:
1485            zoom_level = 1.0    # Default DPI is usually 96.
1486        elif font_dpi <= 108:
1487            zoom_level = 1.1
1488        elif font_dpi <= 115:
1489            zoom_level = 1.2
1490        elif font_dpi <= 122:
1491            zoom_level = 1.3
1492        elif font_dpi <= 129:
1493            zoom_level = 1.4
1494        elif font_dpi >= 130:
1495            zoom_level = 1.5
1496
1497        self.dpi = font_dpi
1498        self.zoom_level = zoom_level
1499
1500    def check_internet_connection(self):
1501        print('[Network Test] Checking for internet connectivity... ')
1502        url = "http://www.lliurex.net"
1503
1504        if arg.simulate_no_connection:
1505            print('[Network Test] Simulation argument override. Retrying will reset this.')
1506            arg.simulate_no_connection = False
1507            self.is_online = False
1508            return
1509
1510        if arg.simulate_force_connection:
1511            print('[Network Test] Simulation argument override. Forcing connection presence.')
1512            print('[Network Test] WARNING: Do not attempt to install/remove software offline as this may lead to errors later!')
1513            arg.simulate_connection = False
1514            self.is_online = True
1515            return
1516
1517        try:
1518            response = urllib.request.urlopen(url, timeout=2).read().decode('utf-8')
1519        except socket.timeout:
1520            print("[Network Test] -- Socket timed out to URL {0}".format(url))
1521            self.is_online = False
1522        except:
1523            print("[Network Test] -- Could not establish a connection to '{0}'. ".format(url))
1524            self.is_online = False
1525        else:
1526            print("[Network Test] Successfully pinged '{0}' ".format(url))
1527            self.is_online = True
1528
1529    def detect_graphics(self):
1530        # If we're the Raspberry Pi, there is nothing to output.
1531        if self.session_type == 'pi':
1532            self.graphics_grep = 'Raspberry Pi'
1533            self.graphics_vendor = 'Raspberry Pi'
1534            return
1535
1536        # TODO: Support dual graphic cards.
1537        arg.print_verbose('Graphics','Detecting graphics vendor... ')
1538        try:
1539            output = subprocess.Popen('lspci | grep VGA', stdout=subprocess.PIPE, shell='True').communicate()[0]
1540            output = output.decode(encoding='UTF-8')
1541        except:
1542            # When 'lspci' does not find a VGA controller (this is the case for the RPi 2)
1543            arg.print_verbose("Graphics","Couldn't detect a VGA Controller on this system.")
1544            output = 'Unknown'
1545
1546        # Scan for and set known brand name.
1547        if output.find('NVIDIA') != -1:
1548            self.graphics_vendor = 'NVIDIA'
1549        elif output.find('AMD') != -1:
1550            self.graphics_vendor = 'AMD'
1551        elif output.find('Intel') != -1:
1552            self.graphics_vendor = 'Intel'
1553        elif output.find('VirtualBox') != -1:
1554            self.graphics_vendor = 'VirtualBox'
1555        else:
1556            self.graphics_vendor = 'Unknown'
1557
1558        self.graphics_grep = repr(output)
1559        self.graphics_grep = self.graphics_grep.split("controller: ",1)[1]
1560        self.graphics_grep = self.graphics_grep.split("\\n",1)[0]
1561        arg.print_verbose("Graphics","Detected: {0}".format(self.graphics_grep))
1562
1563
1564
1565
1566    def get_server_info(self, webkit):
1567        print('[LliureX Specs] Gathering LliureX Server information...')
1568
1569        msg_srv_started = _('The server is started: ')
1570        msg_no_srv_started = _(" You don\'t have the server started yet. You must start the server before starting the next step.")
1571        msg_mirror_date = _(' The mirror was done at: ')
1572        msg_mirror_not_done = _(' The mirror is not done. Before continue you must do it.')
1573        msg_image_done = _(' It seems you have an image done.')
1574        msg_image_not_done =_(' It seems you have no image done. Before procedure with the next step you should do one.')   
1575
1576        # Collect basic system information
1577        def run_external_command(command, with_shell=False):           
1578            if with_shell:
1579                raw = str(subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0])
1580            else:
1581                raw = str(subprocess.Popen(command, stdout=subprocess.PIPE).communicate()[0])
1582            output = raw.replace("b'","").replace('b"',"").replace("\\n'","").replace("\\n","")
1583            return output
1584
1585        #Check if Sever is started###############################################################################
1586        try:
1587            server_started = client.get_variable("",   "VariablesManager", "SRV_IP")
1588            webkit.execute_script('$("#servidor_iniciat").html("'+ msg_srv_started +' '+ server_started +'")')
1589            webkit.execute_script('$("#servidor_iniciat").addClass("alert alert-info")')
1590        except:
1591            webkit.execute_script('$("#text_servidor_iniciat").html("' + msg_no_srv_started+ '")')
1592            webkit.execute_script('$("#icon_servidor_iniciat").addClass("fa fa-warning")')
1593            webkit.execute_script('$("#servidor_iniciat").addClass("alert alert-warning")')
1594            print('[LliureX Specs] Failed to retrieve data: servidor_iniciat')
1595
1596        #Check mirror in LliureX###############################################################################
1597        try:
1598            mirror_done = client.get_variable("",   "VariablesManager", "LLIUREXMIRROR")
1599            print ("[LliureX Specs] The mirror server in LliureX is: " + mirror_done['llx16']['status_mirror'])
1600            if mirror_done['llx16']['status_mirror'] == 'Ok':
1601                webkit.execute_script('$("#mirror_fet").html("'+ msg_mirror_date +' '+ mirror_done['llx16']['last_mirror_date'] +'")')
1602                webkit.execute_script('$("#mirror_fet").addClass("alert alert-info")')
1603            else:
1604                webkit.execute_script('$("#text_mirror_fet").html("'+ msg_mirror_not_done +'")')
1605                webkit.execute_script('$("#icon_mirror_fet").addClass("fa fa-warning")')
1606                webkit.execute_script('$("#mirror_fet").addClass("alert alert-warning")')
1607                print('[LliureX Specs] Failed to retrieve data: mirror_fet')
1608        except:
1609                webkit.execute_script('$("#text_mirror_fet").html("'+ msg_mirror_not_done +'")')
1610                webkit.execute_script('$("#icon_mirror_fet").addClass("fa fa-warning")')
1611                webkit.execute_script('$("#mirror_fet").addClass("alert alert-warning")')
1612                print('[LliureX Specs] Failed to retrieve data: mirror_fet')
1613 
1614        #Are there any client done in the Server?###############################################################################
1615        try:
1616            imatge_feta = client.getClientList("",   "LmdClientManager")
1617            if imatge_feta != "[]":
1618                webkit.execute_script('$("#imatge_feta").html("'+ msg_image_done +'")')
1619                webkit.execute_script('$("#imatge_feta").addClass("alert alert-info")')
1620            else:
1621                webkit.execute_script('$("#text_imatge_feta").html("'+ msg_image_not_done +'")')
1622                webkit.execute_script('$("#icon_imatge_feta").addClass("fa fa-warning")')
1623                webkit.execute_script('$("#imatge_feta").addClass("alert alert-warning")')
1624        except:
1625            webkit.execute_script('$("#text_imatge_feta").html("'+ msg_image_not_done +'")')
1626            webkit.execute_script('$("#icon_imatge_feta").addClass("fa fa-warning")')
1627            webkit.execute_script('$("#imatge_feta").addClass("alert alert-warning")')
1628            print('[LliureX Specs] Failed to retrieve data: imatge_feta')
1629        ##############################################################################################
1630
1631
1632
1633    def get_system_info(self, webkit):
1634        print('[System Specs] Gathering system specifications...')
1635
1636        # Prefixes for translation
1637        mb_prefix = _("MB")
1638        mib_prefix = _("MiB")
1639        gb_prefix = _("GB")
1640        gib_prefix = _("GiB")
1641
1642        # Start collecting advanced system information in the background.
1643        # (Python can do other things while this command completes)
1644        arg.print_verbose('System Specs', 'Running "inxi" for advanced system information...')
1645        try:
1646            inxi_raw = subprocess.Popen(['inxi','-c','0','-v','5','-p','-d','-xx'], stdout=subprocess.PIPE)
1647        except:
1648            print('[System Specs] Failed to execute collect advanced information. Is "inxi" no longer installed?')
1649
1650        # Append a failure symbol beforehand in event something goes horribly wrong.
1651        stat_error_msg = _("Could not gather data.")
1652        html_tag = '<a data-toggle=\'tooltip\' data-placement=\'top\' title=\'' + stat_error_msg + '\'><span class=\'fa fa-warning specs-error\'></span></a>'
1653        for element in ['distro', 'kernel', 'motherboard', 'boot-mode', 'cpu-model', 'cpu-speed', 'arch-use',
1654                        'arch-supported', 'memory', 'graphics', 'filesystem', 'capacity', 'allocated-space', 'free-space']:
1655            webkit.execute_script('$("#spec-' + element + '").html("' + html_tag + '")')
1656
1657        # Collect basic system information
1658        def run_external_command(command, with_shell=False):
1659            if with_shell:
1660                raw = str(subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0])
1661            else:
1662                raw = str(subprocess.Popen(command, stdout=subprocess.PIPE).communicate()[0])
1663            output = raw.replace("b'","").replace('b"',"").replace("\\n'","").replace("\\n","")
1664            return output
1665
1666        ## Distro
1667        try:
1668            arg.print_verbose('System Specs', 'Gathering data: Distribution')
1669            distro_description = run_external_command(['lliurex-version'])
1670            distro_codename = run_external_command(['lsb_release','-c','-s'])
1671            webkit.execute_script('$("#spec-distro").html("Lliurex: ' + distro_description + '")')
1672        except:
1673            print('[System Specs] Failed to retrieve data: Distribution')
1674
1675        ## Kernel
1676        try:
1677            arg.print_verbose('System Specs', 'Gathering data: Kernel')
1678            kernel = run_external_command(['uname','-r'])
1679            webkit.execute_script('$("#spec-kernel").html("' + kernel + '")')
1680        except:
1681            print('[System Specs] Failed to retrieve data: Kernel')
1682
1683        ## Motherboard
1684        try:
1685            arg.print_verbose('System Specs', 'Gathering data: Motherboard')
1686            motherboard_name = run_external_command(['cat','/sys/devices/virtual/dmi/id/board_name'])
1687            webkit.execute_script('$("#spec-motherboard").html("' + motherboard_name + '")')
1688        except:
1689            print('[System Specs] Failed to retrieve data: Motherboard')
1690
1691        ## CPU Details
1692        arg.print_verbose('System Specs', 'Gathering data: CPU')
1693        try:
1694            cpu_model = run_external_command(['lscpu | grep "name"'], True).split(': ')[1]
1695            webkit.execute_script('$("#spec-cpu-model").html("' + cpu_model + '")')
1696        except:
1697            print('[System Specs] Failed to retrieve data: CPU Model')
1698        try:
1699            try:
1700                # Try obtaining the maximum speed first.
1701                cpu_speed = int(run_external_command(['lscpu | grep "max"'], True).split(': ')[1].strip(' ').split('.')[0])
1702            except:
1703                # Otherwise, fetch the CPU's MHz.
1704                cpu_speed = int(run_external_command(['lscpu | grep "CPU MHz"'], True).split(': ')[1].strip(' ').split('.')[0])
1705
1706            webkit.execute_script('$("#spec-cpu-speed").html("' + str(cpu_speed) + ' MHz")')
1707        except:
1708            print('[System Specs] Failed to retrieve data: CPU Speed')
1709
1710        try:
1711            if self.arch == 'i386':
1712                cpu_arch_used = '32-bit'
1713            elif self.arch == 'amd64':
1714                cpu_arch_used = '64-bit'
1715            else:
1716                cpu_arch_used = self.arch
1717            webkit.execute_script('$("#spec-arch-use").html("' + cpu_arch_used + '")')
1718        except:
1719            print('[System Specs] Failed to retrieve data: CPU Arch in Use')
1720
1721        try:
1722            cpu_arch_supported = run_external_command(['lscpu | grep "modo"'], True).split(':')[1]
1723            webkit.execute_script('$("#spec-arch-supported").html("' + cpu_arch_supported + '")')
1724        except:
1725            print('[System Specs] Failed to retrieve data: CPU Supported Arch')
1726
1727        ## Root partition (where Ubuntu MATE is installed) and the rest of that disk.
1728        try:
1729            if self.session_type == 'live':
1730                webkit.execute_script('$(".specs-hide-live-session").hide()')
1731            else:
1732                arg.print_verbose('System Specs', 'Gathering data: Storage')
1733                ## Gather entire disk data
1734                root_partition = run_external_command(['mount | grep "on / "'], True).split(' ')[0]
1735                if root_partition[:-2] == "/dev/sd":            # /dev/sdXY
1736                    root_dev = root_partition[:-1]
1737                if root_partition[:-2] == "/dev/hd":            # /dev/hdXY
1738                    root_dev = root_partition[:-1]
1739                if root_partition[:-3] == "/dev/mmcblk":        # /dev/mmcblkXpY
1740                    root_dev = root_partition[:-2]
1741                else:
1742                    root_dev = root_partition[:-1]              # Generic
1743                disk_dev_name = root_dev.split('/')[2]
1744                arg.print_verbose('System Specs', 'LliureX 16 is installed on disk: ' + root_dev)
1745                rootfs = os.statvfs('/')
1746                root_size = rootfs.f_blocks * rootfs.f_frsize
1747                root_free = rootfs.f_bavail * rootfs.f_frsize
1748                root_used = root_size - root_free
1749                entire_disk = run_external_command(['lsblk -b | grep "' + disk_dev_name + '" | grep "disk"'], True)
1750                entire_disk = int(entire_disk.split()[3])
1751
1752                ## Perform calculations across units
1753                capacity_GB =   round(entire_disk/1000/1000/1000,1)
1754                capacity_GiB =  round(entire_disk/1024/1024/1024,1)
1755                allocated_GB =  round(root_size/1000/1000/1000,1)
1756                allocated_GiB = round(root_size/1024/1024/1024,1)
1757                used_GB =       round(root_used/1000/1000/1000,1)
1758                used_GiB =      round(root_used/1024/1024/1024,1)
1759                free_GB =       round(root_free/1000/1000/1000,1)
1760                free_GiB =      round(root_free/1024/1024/1024,1)
1761                other_GB =      round((entire_disk-root_size)/1000/1000/1000,1)
1762                other_GiB =     round((entire_disk-root_size)/1024/1024/1024,1)
1763
1764                # Show megabytes/mebibytes (in red) if gigabytes are too small.
1765                if capacity_GB <= 1:
1766                    capacity_GB = str(round(entire_disk/1000/1000,1)) + ' ' + mb_prefix
1767                    capacity_GiB = str(round(entire_disk/1024/1024,1)) + ' ' + mib_prefix
1768                else:
1769                    capacity_GB = str(capacity_GB) + ' ' + gb_prefix
1770                    capacity_GiB = str(capacity_GiB) + ' ' + gib_prefix
1771
1772                if allocated_GB <= 1:
1773                    allocated_GB =  str(round(root_size/1000/1000,1)) + ' ' + mb_prefix
1774                    allocated_GiB = str(round(root_size/1024/1024,1)) + ' ' + mib_prefix
1775                else:
1776                    allocated_GB = str(allocated_GB) + ' ' + gb_prefix
1777                    allocated_GiB = str(allocated_GiB) + ' ' + gib_prefix
1778
1779                if used_GB <= 1:
1780                    used_GB =  str(round(root_used/1000/1000,1)) + ' ' + mb_prefix
1781                    used_GiB = str(round(root_used/1024/1024,1)) + ' ' + mib_prefix
1782                else:
1783                    used_GB = str(used_GB) + ' ' + gb_prefix
1784                    used_GiB = str(used_GiB) + ' ' + gib_prefix
1785
1786                if free_GB <= 1:
1787                    free_GB =  str(round(root_free/1000/1000,1)) + ' ' + mb_prefix
1788                    free_GiB = str(round(root_free/1024/1024,1)) + ' ' + mib_prefix
1789                    webkit.execute_script('$("#spec-free-space").addClass("specs-error")')
1790                else:
1791                    free_GB = str(free_GB) + ' ' + gb_prefix
1792                    free_GiB = str(free_GiB) + ' ' + gib_prefix
1793
1794                if other_GB <= 1:
1795                    other_GB =  str(round((entire_disk-root_size)/1000/1000,1)) + ' ' + mb_prefix
1796                    other_GiB = str(round((entire_disk-root_size)/1024/1024,1)) + ' ' + mib_prefix
1797                else:
1798                    other_GB = str(other_GB) + ' ' + gb_prefix
1799                    other_GiB = str(other_GiB) + ' ' + gib_prefix
1800
1801                ## Append data to HTML.
1802                webkit.execute_script('$("#spec-filesystem").html("' + root_partition + '")')
1803                webkit.execute_script('$("#spec-capacity").html("' + capacity_GB + ' <span class=\'secondary-value\'>(' + capacity_GiB + ')</span>")')
1804                webkit.execute_script('$("#spec-allocated-space").html("' + allocated_GB + ' <span class=\'secondary-value\'>(' + allocated_GiB + ')</span>")')
1805                webkit.execute_script('$("#spec-used-space").html("' + used_GB + ' <span class=\'secondary-value\'>(' + used_GiB + ')</span>")')
1806                webkit.execute_script('$("#spec-free-space").html("' + free_GB + ' <span class=\'secondary-value\'>(' + free_GiB + ')</span>")')
1807                webkit.execute_script('$("#spec-other-space").html("' + other_GB + ' <span class=\'secondary-value\'>(' + other_GiB + ')</span>")')
1808
1809                ## Calculate representation across physical disk
1810                disk_percent_UM_used = int(round(root_used / entire_disk * 100)) * 2
1811                disk_percent_UM_free = int(round(root_free / entire_disk * 100)) * 2
1812                disk_percent_other   = (200 - disk_percent_UM_used - disk_percent_UM_free)
1813                arg.print_verbose('System Specs', ' --- Disk: ' + root_dev)
1814                arg.print_verbose('System Specs', ' --- * OS Used: ' + str(root_used) + ' bytes (' + str(disk_percent_UM_used/2) + '%)')
1815                arg.print_verbose('System Specs', ' --- * OS Free: ' + str(root_free) + ' bytes (' + str(disk_percent_UM_free/2) + '%)')
1816                arg.print_verbose('System Specs', ' --- = Other Partitions: ' + str(entire_disk - root_size) + ' bytes (' + str(disk_percent_other/2) + '%)')
1817                webkit.execute_script("$('#disk-used').width('" + str(disk_percent_UM_used) + "px');")
1818                webkit.execute_script("$('#disk-free').width('" + str(disk_percent_UM_free) + "px');")
1819                webkit.execute_script("$('#disk-other').width('" + str(disk_percent_other) + "px');")
1820        except:
1821            print('[System Specs] Failed to retrieve data: Storage')
1822
1823        ## RAM
1824        try:
1825            arg.print_verbose('System Specs', 'Gathering Data: RAM')
1826            ram_bytes = run_external_command(['free -b | grep "Memoria:" '], True)
1827            ram_bytes = float(ram_bytes.split()[1])
1828            if round(ram_bytes / 1024 / 1024) < 1024:
1829                ram_xb = str(round(ram_bytes / 1000 / 1000, 1)) + ' ' + mb_prefix
1830                ram_xib = str(round(ram_bytes / 1024 / 1024, 1)) + ' ' + mib_prefix
1831            else:
1832                ram_xb =  str(round(ram_bytes / 1000 / 1000 / 1000, 1)) + ' ' + gb_prefix
1833                ram_xib = str(round(ram_bytes / 1024 / 1024 / 1024, 1)) + ' ' + gib_prefix
1834            ram_string = ram_xb + ' <span class=\'secondary-value\'>(' + ram_xib + ')</span>'
1835            webkit.execute_script('$("#spec-memory").html("' + ram_string + '")')
1836        except:
1837            print('[System Specs] Failed to retrieve data: RAM (Memory)')
1838
1839        ## Graphics
1840        webkit.execute_script('$("#spec-graphics").html("' + self.graphics_grep + '")')
1841
1842        ## Collect missing data differently for some architectures.
1843        if systemstate.arch == 'powerpc':
1844            ## Motherboard & Revision
1845            try:
1846                arg.print_verbose('System Specs', 'Gathering alternate data: PowerPC Motherboard')
1847                mb_model = run_external_command(['grep','motherboard','/proc/cpuinfo']).split(': ')[1]
1848                mb_rev = run_external_command(['grep','revision','/proc/cpuinfo']).split(': ')[1]
1849                webkit.execute_script('$("#spec-motherboard").html("' + mb_model + ' ' + mb_rev + '")')
1850            except:
1851                arg.print_verbose('System Specs', 'Failed to gather data: PowerPC Motherboard')
1852
1853            ## CPU and Clock Speed
1854            try:
1855                arg.print_verbose('System Specs', 'Gathering alternate data: PowerPC CPU')
1856                cpu_model = run_external_command(['grep','cpu','/proc/cpuinfo']).split(': ')[1]
1857                cpu_speed = run_external_command(['grep','clock','/proc/cpuinfo']).split(': ')[1]
1858                webkit.execute_script('$("#spec-cpu-model").html("' + cpu_model + '")')
1859                webkit.execute_script('$("#spec-cpu-speed").html("' + str(cpu_speed) + '")')
1860            except:
1861                arg.print_verbose('System Specs', 'Failed to gather data: PowerPC CPU')
1862
1863            ## Device Name
1864            try:
1865                arg.print_verbose('System Specs', 'Gathering alternate data: PowerPC Model Name')
1866                mb_name = run_external_command(['grep','detected','/proc/cpuinfo']).split(': ')[1]
1867                webkit.execute_script('$("#spec-motherboard").append(" / ' + mb_name + '")')
1868            except:
1869                arg.print_verbose('System Specs', 'Failed to gather data: PowerPC Model Name')
1870
1871            ## Boot Mode / PowerMac Generation
1872            try:
1873                arg.print_verbose('System Specs', 'Gathering alternate data: PowerMac Generation')
1874                mac_generation = run_external_command(['grep','pmac-generation','/proc/cpuinfo']).split(': ')[1]
1875                webkit.execute_script('$("#spec-boot-mode").html("Yaboot (' + mac_generation + ')")')
1876            except:
1877                arg.print_verbose('System Specs', 'Failed to gather data: PowerMac Generation')
1878
1879        # Append advanced system information
1880        try:
1881            arg.print_verbose('System Specs', 'Waiting for inxi process to finish...')
1882            inxi_output = str(inxi_raw.communicate()[0])
1883            inxi_output = inxi_output.replace("b'","").replace("\\n","\n")
1884            webkit.execute_script("$('#specs-inxi').html('')")
1885            for line in inxi_output.split('\n'):
1886                webkit.execute_script("$('#specs-inxi').append('" + line.strip('"').strip("'") + "<br>')")
1887            print('[System Specs] Successfully appended advanced system information.')
1888        except:
1889            print('[System Specs] Failed to append advanced system information or communicate with "inxi" process.')
1890
1891        # Check internet connectivity status.
1892        if self.is_online:
1893            webkit.execute_script('$("#specs-has-net").show()')
1894            webkit.execute_script('$("#specs-has-no-net").hide()')
1895        else:
1896            webkit.execute_script('$("#specs-has-net").hide()')
1897            webkit.execute_script('$("#specs-has-no-net").show()')
1898
1899        # Change icon depending on what type of device we are using.
1900        if self.session_type == 'pi':
1901            webkit.execute_script('$("#specs-device-rpi").show()')
1902            webkit.execute_script('$(".specs-hide-pi").hide()')
1903        elif self.arch == 'powerpc':
1904            webkit.execute_script('$("#specs-device-powerpc").show()')
1905            webkit.execute_script('$(".specs-hide-ppc").hide()')
1906        elif self.graphics_vendor == 'VirtualBox':
1907            webkit.execute_script('$("#specs-device-vbox").show()')
1908            webkit.execute_script('$(".specs-hide-vbox").hide()')
1909        elif self.session_type == 'live':
1910            webkit.execute_script('$("#specs-live-session").show()')
1911            webkit.execute_script('$(".specs-hide-live").hide()')
1912        else:
1913            webkit.execute_script('$("#specs-device-normal").show()')
1914
1915        # Display UEFI/BIOS boot mode.
1916        if systemstate.arch == 'i386' or systemstate.arch == 'amd64':
1917            webkit.execute_script('$("#spec-boot-mode").html("' + self.boot_mode + '")')
1918
1919        # Hide root storage info if in a live session.
1920        if self.session_type == 'live':
1921            webkit.execute_script('$(".spec-3").hide()')
1922
1923        if self.session_flavour == 'music':
1924            webkit.execute_script('$(".spec-3").hide()')
1925
1926        # Data cached, ready to display.
1927        webkit.execute_script('$("#specs-loading").fadeOut("fast")')
1928        webkit.execute_script('$("#specs-tabs").fadeIn("fast")')
1929        webkit.execute_script('$("#specs-basic").fadeIn("medium")')
1930        webkit.execute_script('setCursorNormal()')
1931
1932
1933
1934    def rpi_resize(self, action, webkit=None):
1935        if action == 'do-resize':
1936            subprocess.call(['pkexec', '/usr/lib/lliurex-mate/lliurex-mate-welcome-rpi2-partition-resize'])
1937
1938            def notify(subject, body, icon):
1939                Notify.init(_('Raspberry Pi Partition Resize'))
1940                resize_notify=Notify.Notification.new(subject, body, icon)
1941                resize_notify.show()
1942
1943            try:
1944                with open('/tmp/notify_rpi_status') as status_file:
1945                    status_code = int(status_file.read())
1946            except:
1947                status_code = 0
1948
1949            try:
1950                with open('/tmp/notify_rpi_text') as misc_file:
1951                    misc_text = misc_file.read()
1952            except:
1953                misc_text = ""
1954
1955            if status_code == 1:
1956                notify( _("Root partition has been resized."), _("The filesystem will be enlarged upon the next reboot."), 'dialog-information' )
1957                self.rpi_resize_pending = True
1958                webkit.execute_script('$("#rpi-resized").hide()')
1959                webkit.execute_script('$("#rpi-not-resized").hide()')
1960                webkit.execute_script('$("#rpi-restart-now").show()')
1961            elif status_code == 2:
1962                notify( _("Don't know how to expand."), misc_text + ' ' + _("does not exist or is not a symlink."), 'dialog-error' )
1963            elif status_code == 3:
1964                notify( _("Don't know how to expand."), misc_text + ' ' + _("is not an SD card."), 'dialog-error' )
1965            elif status_code == 4:
1966                notify( _("Don't know how to expand."), _("Your partition layout is not currently supported by this tool."), 'dialog-error' )
1967            elif status_code == 5:
1968                notify( _("Don't know how to expand."), misc_text + ' ' + _("is not the last partition."), 'dialog-error' )
1969            else:
1970                notify( _("Failed to run resize script."), _("The returned error code is:") + str(status_code), 'dialog-error' )
1971                print('[Welcome] Unrecognised return code for Raspberry Pi resize: ' + str(status_code))
1972
1973            app._appView._push_config()
1974
1975        elif action == 'check':
1976            if os.path.exists('/.resized'):
1977                resized = True
1978            else:
1979                resized = False
1980
1981            if resized:
1982                webkit.execute_script('$("#rpi-resized").show()')
1983                webkit.execute_script('$("#rpi-not-resized").hide()')
1984            else:
1985                webkit.execute_script('$("#rpi-resized").hide()')
1986                webkit.execute_script('$("#rpi-not-resized").show()')
1987
1988            if self.rpi_resize_pending:
1989                webkit.execute_script('$("#rpi-resized").hide()')
1990                webkit.execute_script('$("#rpi-not-resized").hide()')
1991                webkit.execute_script('$("#rpi-restart-now").show()')
1992
1993        elif action == 'reboot':
1994            subprocess.call(['mate-session-save','--shutdown-dialog'])
1995
1996
1997class DynamicApps(object):
1998    def __init__(self):
1999        # Load JSON Index into Memory
2000        self.reload_index()
2001
2002        # Variables to remember common details.
2003        self.all_categories = ['Accessories', 'Education', 'Games', 'Graphics', 'Internet', 'Office', 'Programming', 'Media', 'SysTools', 'UnivAccess', 'Servers', 'MoreApps', 'Lliurex']
2004        self.hide_non_free = True
2005
2006        # Reading the apt cache later.
2007        self._apt_cache = apt.Cache()
2008
2009        # Indicate that operations are in progress.
2010        self.operations_busy = False
2011
2012        # Get the version of Welcome in use.
2013        for pkgname in self._apt_cache.keys():
2014            if 'lliurex-mate-welcome' in pkgname:
2015                systemstate.welcome_version = self._apt_cache['lliurex-mate-welcome'].installed.version
2016                break
2017        print('[Welcome] Version: ' + systemstate.welcome_version)
2018
2019
2020    ###### JSON Index Structure
2021    #
2022    #   ===== Structure Overview =====
2023    #   {
2024    #     "Category" {                                  - Application category.
2025    #       "application-id" {                          - Unique string identifier for application, apps sorted A-Z. Hyphens preferred.
2026    #         "variable": "data",                       - Variable containing single data.
2027    #         "list": ["This is a line.",
2028    #                   "The same line."]               - Variable containing a 'list' of data.
2029    #         "group": { "variable": "data" }           - Group containing data.
2030    #        }
2031    #      }
2032    #   }
2033    #
2034    #   ** Standard JSON rules apply. Watch out for the commas.
2035    #   ** Important!! Use &#8217; instead of ' for an apostrophe character.
2036
2037    #   ===== Variable Index =====
2038    #   Variable                Type        Required?   Description
2039    #   ----------------------- ----------  ----------  ---------------------------------------------
2040    #   name                    string      Yes         Name of the application as displayed to the user.
2041    #   img                     string      Yes         Name of image. Excluding ".png" extension.
2042    #   main-package            string      Yes         Package used to detect if it's installed.
2043    #   launch-command          string      No          Command to launch the installed program. Can be ignored for no launch option.
2044    #   install-packages        string      *           Packages to install/reinstall. Comma separated.
2045    #   remove-packages         string      *           Packages to remove. Comma separated.
2046    #   upgradable              boolean     *           This package is only for upgrading.
2047    #   upgrade-packages        string      *           Packages to upgrade. Comma separated.
2048    #   description             list        Yes         Description of the application. Use usual HTML tags for formatting. Can be left blank if unlisted.
2049    #   alternate-to            string      No          If the app is similar or has an alternate. Can be ignored.
2050    #   subcategory             string      Yes         Used for filtering applications within the category. Eg. "Partitioning", "Audio Production".
2051    #   open-source             boolean     Yes         Proprietary or Open Source?
2052    #   url-info                string      Yes         URL to the web page for more information.
2053    #   url-android             string      No          URL if there is an associated Android app. Can be ignored.
2054    #   url-ios                 string      No          URL if there is an associated Android app. Can be ignored.
2055    #   arch                    string      Yes         Supported architectures for this app. Comma seperated.
2056    #   releases                string      Yes         Supported versions of Ubuntu MATE to show this application. Comma seperated.
2057    #   working                 boolean     Yes         Show/hide visibility of this application.
2058    #   notes                   string      No          Optional developer notes for the application.
2059    #
2060    #### * Only if applicable to application.
2061
2062    #   ===== Pre-installation Index =====
2063    #
2064    #   "pre-install": {                                - Required group of data containing pre-installation procedures.
2065    #       "trusty": {                                 - Different releases may have different operations.
2066    #           "variable":  "data"                     - See table below for possible operations.
2067    #       },
2068    #       "all": {                                    - Use "all" to specify all other releases. This should be last.
2069    #           "variable":  "data"                         If there is only one instruction,
2070    #       }
2071    #   }
2072    #
2073    #   method                  string      Yes         Pre-configuration methods. Multiple can be specified with a plus '+'.
2074    #                                                       "skip"          =   Package is already in archive.
2075    #                                                       "ppa"           =   Add a PPA. Specify (2), optionally (1).
2076    #                                                       "partner-repo"  =   Add the Ubuntu Partners Repo.
2077    #                                                       "manual"        =   Get keys and write a sources.list file. (3)
2078    #   source-file        (1)  string      No          Source file to update, excluding the ".list" extension.
2079    #   enable-ppa         (2)  string      *           Name of PPA to add, eg. "ppa:somebody/someapp".
2080    #   apt-key-url        (3)  string      *           Retrieve the key from URL.
2081    #   apt-key-server     (3)  list        *           Retrieve the key from a server.
2082    #                                                       "server-address"    = Eg. "keyserver.ubuntu.com"
2083    #                                                       "key"               = Eg. "D2C19886"
2084    #   apt-sources        (3)  list        *           Contents for the sources file. Each variable is a new line.
2085    #
2086    #### These keys words can be given as placeholders:
2087    #
2088    #   CODENAME        =   Current Ubuntu release, eg. "xenial".
2089    #   OSVERSION       =   Current Ubuntu version, eg "16.04".
2090    #
2091
2092    def reload_index(self):
2093        try:
2094            print('[Apps] Reading index...')
2095            json_path = os.path.abspath(os.path.join(app._data_path, 'js/applications.json'))
2096            with open(json_path) as data_file:
2097                self.index = json.load(data_file)
2098                print('[Apps] Successfully loaded index.')
2099        except Exception as e:
2100            self.index = None
2101            print("[Apps] ERROR: Software Index JSON is invalid or missing!")
2102            print("------------------------------------------------------------")
2103            print("Exception:")
2104            print(str(e))
2105            print("------------------------------------------------------------")
2106
2107    def set_app_info(self, category, program_id):
2108        self.app_name = self.index[category][program_id]['name']
2109        self.app_img = self.index[category][program_id]['img']
2110        self.app_main_package = self.index[category][program_id]['main-package']
2111        self.app_launch_command = self.index[category][program_id]['launch-command']
2112        self.app_upgrade_only = False
2113        try:
2114            if self.index[category][program_id]['upgradable']:
2115                self.app_upgrade_only = True
2116                self.app_upgrade_packages = self.index[category][program_id]['upgrade-packages']
2117        except:
2118            self.app_upgrade_only = False
2119
2120        if not self.app_upgrade_only:
2121            self.app_install_packages = self.index[category][program_id]['install-packages']
2122            self.app_remove_packages = self.index[category][program_id]['remove-packages']
2123        self.app_description = ''
2124        for line in self.index[category][program_id]['description']:
2125            self.app_description = self.app_description + ' ' + line
2126        self.app_alternate_to = self.index[category][program_id]['alternate-to']
2127        self.app_subcategory = self.index[category][program_id]['subcategory']
2128        self.app_open_source = self.index[category][program_id]['open-source']
2129        self.app_url_info = self.index[category][program_id]['url-info']
2130        self.app_url_android = self.index[category][program_id]['url-android']
2131        self.app_url_ios = self.index[category][program_id]['url-ios']
2132        self.app_arch = self.index[category][program_id]['arch']
2133        self.app_releases = self.index[category][program_id]['releases']
2134        self.app_working = self.index[category][program_id]['working']
2135
2136    def populate_categories(self, webkit):
2137        ''' List all of the applications supported on the current architecture. '''
2138        total_added = 0
2139        total_skipped = 0
2140        total_unsupported = 0
2141
2142        # Don't attempt to continue if the index is missing/incorrectly parsed.
2143        if not self.index:
2144            print('[Apps] ERROR: Application index not loaded. Cannot populate categories.')
2145            return
2146
2147        # Strings
2148        str_nothing_here = _("Sorry, Welcome could not feature any software for this category that is compatible on this system.")
2149        str_upgraded = _("LliureX: ")
2150        str_alternate_to = _('Alternative to:')
2151        str_hide = _("Hide")
2152        str_show = _("Show")
2153        str_install = _("Install")
2154        str_reinstall = _("Reinstall")
2155        str_remove = _("Remove")
2156        str_upgrade = _("Upgrade")
2157        str_launch = _("Launch")
2158        str_license = _("License")
2159        str_platform = _("Platform")
2160        str_category = _("Category")
2161        str_website = _("Website")
2162        str_screenshot = _("Screenshot")
2163        str_source = _("Source")
2164        str_source_ppa = '<span class="fa fa-cube"></span>&nbsp;'
2165        str_source_manual = '<span class="fa fa-globe"></span></a>&nbsp;'
2166        str_source_partner = '<img src="img/logos/ubuntu-mono.png" width="16px" height="16px"/>&nbsp;' + _('Canonical Partner Repository')
2167        str_source_skip = '<img src="img/logos/ubuntu-mono.png" width="16px" height="16px"/>&nbsp;' + _('Ubuntu Repository')
2168        str_unknown = _('Unknown')
2169
2170        # Get the app data from each category and list them.
2171        for category in self.all_categories:
2172            arg.print_verbose('Apps', ' ------ Processing: ' + category + ' ------')
2173
2174            # Convert to a list to work with. Sort alphabetically.
2175            category_items = list(self.index[category].keys())
2176            category_items.sort()
2177
2178            # Keep a count of apps in case there are none to list.
2179            apps_here = 0
2180
2181            # Keep track of the subcategories of the apps in this category so we can filter them.
2182            subcategories = []
2183
2184            # Enumerate each program in this category.
2185            for program_id in category_items:
2186                self.set_app_info(category, program_id)
2187
2188                # Only list the program if it's working.
2189                if not self.app_working:
2190                    arg.print_verbose('Apps', ' Unlisted: ' + self.app_name)
2191                    total_skipped = total_skipped + 1
2192                    continue
2193
2194                # Only list the program if it supports the current architecture in use.
2195                supported = False
2196                supported_arch = False
2197                supported_release = False
2198
2199                for architecture in self.app_arch.split(','):
2200                    if architecture == systemstate.arch:
2201                        supported_arch = True
2202
2203                # Only list the program if it's available for the current release.
2204                for release in self.app_releases.split(','):
2205                    if release == systemstate.codename:
2206                        supported_release = True
2207
2208                if supported_arch and supported_release:
2209                    supported = True
2210
2211                if not supported:
2212                    arg.print_verbose('Apps', ' Unsupported: ' + self.app_name + ' (Only for architectures: ' + self.app_arch + ' and releases: ' + self.app_releases + ')' )
2213                    total_unsupported = total_unsupported + 1
2214                    continue
2215
2216                # If the app has made it this far, it can be added to the grid.
2217                # CSS breaks with dots (.), so any must become hyphens (-).
2218                arg.print_verbose('Apps', ' Added: ' + self.app_name)
2219                subcategories.append(self.app_subcategory)
2220                html_buffer = ''
2221                css_class = program_id.replace('.','-')
2222                css_subcategory = self.app_subcategory.replace(' ','-')
2223
2224                # "Normal" packages that can be installed/removed by the user.
2225                if self.app_open_source:
2226                    html_buffer = html_buffer + '<div id="' + css_class + '" class="app-entry filter-' + css_subcategory + '">'
2227                else:
2228                    html_buffer = html_buffer + '<div id="' + css_class + '" class="app-entry filter-' + css_subcategory + ' proprietary">'
2229                html_buffer = html_buffer + '<div class="row-fluid">'
2230                html_buffer = html_buffer + '<div class="span2 center-inside">'
2231                html_buffer = html_buffer + '<img src="img/applications/' + self.app_img + '.png">'
2232                html_buffer = html_buffer + '<span class="fa fa-check-circle fa-2x installed-check ' + css_class + '-remove"></span>'
2233                html_buffer = html_buffer + '</div><div class="span10">'
2234                html_buffer = html_buffer + '<p><b class="' + css_class + '-text">' + self.app_name + '</b></p>'
2235                html_buffer = html_buffer + '<p class="' + css_class + '-text">' + self.app_description + '</p>'
2236
2237                # Check any "Upgrade" packages if the PPA has already been added.
2238                upgraded = False
2239                if self.app_upgrade_only:
2240                    try:
2241                        listname = dynamicapps.index[category][program_id]['pre-install']['all']['source-file']
2242                        listname = listname.replace('OSVERSION',preinstallation.os_version).replace('CODENAME',preinstallation.codename)
2243                        if os.path.exists(os.path.join('/', 'etc', 'apt', 'sources.list.d', listname+'.list')):
2244                            upgraded = True
2245                            html_buffer = html_buffer + '<h5 class="' + css_class + '-text"><span class="fa fa-check-circle"></span> ' + str_upgraded + '</h5>'
2246                    except:
2247                        pass
2248
2249                if not self.app_alternate_to == None:
2250                    html_buffer = html_buffer + '<ul><li class="' + css_class + '-text"><b>' + str_alternate_to + ' </b><i>' + self.app_alternate_to + '</i></li></ul>'
2251                html_buffer = html_buffer + '<p class="text-right">'
2252                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;'
2253                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;'
2254
2255                # "Regular" packages - can be installed or removed with one-click by the user.
2256                if not self.app_upgrade_only:
2257                    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>'
2258                    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;'
2259                    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;'
2260                    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;'
2261
2262                # "Upgradable" packages - usually pre-installed but have a more up-to-date repository.
2263                if self.app_upgrade_only:
2264                    arg.print_verbose('Apps', 'Upgrade: ' + self.app_name)
2265                    if not upgraded:
2266                        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;'
2267
2268                if not self.app_launch_command == None:
2269                    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;'
2270
2271                # More details section.
2272                html_buffer = html_buffer + '</p><div hidden id="details-' + css_class + '">'
2273
2274                ## Determine string for license
2275                if self.app_open_source:
2276                    license_string = _('Open Source')
2277                else:
2278                    license_string = _('Proprietary')
2279
2280                ## Determine supported platforms
2281                platform_string = ''
2282                for arch in self.app_arch.split(','):
2283                    if arch == 'i386':
2284                        platform_string = platform_string + '<span class="i386"><span class="i386 fa fa-laptop"></span> 32-bit</span> &nbsp;&nbsp;'
2285                    elif arch =='amd64':
2286                        platform_string = platform_string + '<span class="amd64"><span class="fa fa-laptop"></span> 64-bit</span> &nbsp;&nbsp;'
2287                    elif arch =='armhf':
2288                        platform_string = platform_string + '<span class="armhf"><span class="fa fa-tv"></span> aarch32 (ARMv7)</span> &nbsp;&nbsp;'
2289                    elif arch =='powerpc':
2290                        platform_string = platform_string + '<span class="powerpc"><span class="fa fa-desktop"></span> PowerPC</span> &nbsp;&nbsp;'
2291
2292                ## Add Android / iOS app links if necessary.
2293                if not self.app_url_android == None:
2294                    platform_string = platform_string + '<a href="cmd://link?' + self.app_url_android + '"><span class="fa fa-android"></span> Android</a> &nbsp;&nbsp;'
2295
2296                if not self.app_url_ios == None:
2297                    platform_string = platform_string + '<a href="cmd://link?' + self.app_url_ios + '"><span class="fa fa-apple"></span> iOS</a> &nbsp;&nbsp;'
2298
2299                ## Add details about the source of this file.
2300                try:
2301                    preinstall = dynamicapps.index[category][program_id]['pre-install']
2302                    codenames = list(preinstall.keys())
2303                    target = None
2304                    for name in codenames:
2305                        if name == systemstate.codename:
2306                            target = name
2307                            break
2308                    if not target:
2309                            target = 'all'
2310
2311                    methods = preinstall[target]['method'].split('+')
2312                    self.source_info = []
2313                    if len(methods) > 1:
2314                        multiple_sources = True
2315                    else:
2316                        multiple_sources = False
2317
2318                    for method in methods:
2319                        if method == 'skip':
2320                            self.source_info.insert(0, str_source_skip)
2321
2322                        elif method == 'partner-repo':
2323                            self.source_info.insert(0, str_source_partner)
2324
2325                        elif method == 'ppa':
2326                            ppa = preinstall[target]['enable-ppa']
2327                            ppa_author = ppa.split(':')[1].split('/')[0]
2328                            ppa_archive = ppa.split(':')[1].split('/')[1]
2329                            self.source_info.insert(0, str_source_ppa + ' <a href="cmd://link?https://launchpad.net/~' + ppa_author + '/+archive/ubuntu/' + ppa_archive + '">' + ppa + '</a>')
2330
2331                        elif method == 'manual':
2332                            apt_source = ''.join(preinstall[target]['apt-sources'])
2333                            manual_text = str_source_manual + ' ' + str_unknown
2334                            for substring in apt_source.split(' '):
2335                                if substring[:4] == 'http':
2336                                    apt_source = substring.replace('OSVERSION',preinstallation.os_version).replace('CODENAME',preinstallation.codename)
2337                                    manual_text = str_source_manual + ' ' + apt_source
2338                                    break
2339                            self.source_info.insert(0, manual_text)
2340
2341                except:
2342                    print('[Apps] WARNING: Error occurred while processing pre-configuration! Skipped Source: ' + program_id)
2343                    self.source_info = [str_unknown]
2344
2345                ## Write contents of the table.
2346                html_buffer = html_buffer + '<table class="more-details table table-striped">'
2347                html_buffer = html_buffer + '<tr><th>' + str_license + '</th><td>' + license_string + '</td></tr>'
2348                html_buffer = html_buffer + '<tr><th>' + str_platform + '</th><td>' + platform_string + '</td></tr>'
2349                html_buffer = html_buffer + '<tr><th>' + str_category + '</th><td>' + self.app_subcategory + '</td></tr>'
2350
2351                ## Add a website URL if there is one.
2352                if self.app_url_info:
2353                    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>'
2354
2355                ## Add the source for this application.
2356                if multiple_sources:
2357                    html_buffer = html_buffer + '<tr><th>' + str_source + '</th><td><ul>'
2358                    for item in self.source_info:
2359                        html_buffer = html_buffer + '<li>' + item + '</li>'
2360                    html_buffer = html_buffer + '</td></tr></ul>'
2361                else:
2362                    html_buffer = html_buffer + '<tr><th>' + str_source + '</th><td>' + self.source_info[0] + '</td></tr>'
2363
2364                ## Add a screenshot if there is any.
2365                ## Images should be labelled the same as 'img' and increment starting at 1.
2366                screenshots = 1
2367                screenshots_end = False
2368                screenshot_buffer = ''
2369                while not screenshots_end:
2370                    screenshot_path = os.path.join(app._data_path + 'img/applications/screenshots/' + self.app_img + '-' + str(screenshots) + '.jpg')
2371                    if os.path.exists(screenshot_path):
2372                        screenshot_buffer = screenshot_buffer + '<a class="screenshot-link" href="cmd://screenshot?' + self.app_img + '-' + str(screenshots) + '"><img src="' + screenshot_path + '" class="screenshot"/></a>'
2373                        screenshots = screenshots + 1
2374                    else:
2375                        screenshots_end = True
2376
2377                if not screenshots == 1:
2378                    html_buffer = html_buffer + '<tr><th>' + str_screenshot + '</th><td>' + screenshot_buffer + '</td></tr>'
2379
2380                html_buffer = html_buffer + '</table>'
2381
2382                # End the div's for this application.
2383                html_buffer = html_buffer + '</div><br><hr class="soften"></div></div></div>'
2384
2385                # Append buffer to page
2386                webkit.execute_script('$("#' + category + '").append(\'' + html_buffer + '\')')
2387                webkit.execute_script('$("#info-hide-' + css_class + '").hide()')
2388
2389                # Keep track of how many apps added.
2390                apps_here = apps_here + 1
2391                total_added = total_added + 1
2392
2393            # Display a message if there is nothing for this category.
2394            if apps_here == 0:
2395                webkit.execute_script('$("#' + category + '").append("<p class=\'center\'><span class=\'fa fa-warning\'></span>&nbsp; ' + str_nothing_here + '</p>")')
2396
2397            # Post actions to page
2398            ## Colour the architecture currently in use.
2399            webkit.execute_script('$(".' + systemstate.arch + '").addClass("arch-in-use")')
2400
2401            # Process filters for this category.
2402            filters = list(set(subcategories))
2403            filters.sort()
2404            for string in filters:
2405                css_subcategory = string.replace(' ','-')
2406                webkit.execute_script('$("#Filter-' + category + '").append(\'<option value="' + css_subcategory + '">' + string + '</option>\')')
2407
2408        # "Stats for nerds"
2409        total_apps = total_added + total_skipped + total_unsupported
2410        arg.print_verbose('Apps','------------------')
2411        arg.print_verbose('Apps','Applications added: ' + str(total_added))
2412        arg.print_verbose('Apps','Applications unsupported on this architecture: ' + str(total_unsupported))
2413        arg.print_verbose('Apps','Applications that are broken or not suitable for inclusion: ' + str(total_skipped))
2414        arg.print_verbose('Apps','Total number of applications: ' + str(total_apps))
2415        arg.print_verbose('Apps','------------------')
2416
2417    def populate_featured_apps(self, webkit):
2418        arg.print_verbose('Apps', '---- Populating Featured Apps Grid ----')
2419        # Randomly generate a list of apps to feature if supported on this architecture.
2420        possible_apps = []
2421        for category in self.all_categories:
2422            category_items = list(self.index[category].keys())
2423            for program_id in category_items:
2424                if systemstate.arch in self.index[category][program_id]['arch']:
2425                    possible_apps.append(self.index[category][program_id]['img'])
2426
2427        random.shuffle(possible_apps)
2428        for no in range(0,17):
2429            arg.print_verbose('Apps', str(no) + '. ' + possible_apps[no])
2430            webkit.execute_script("addToGrid('" + possible_apps[no] + "');")
2431        webkit.execute_script("initGrid();")
2432        arg.print_verbose('Apps','------------------')
2433
2434    def modify_app(self, webkit, action, program_id):
2435        ''' Installs, removes or upgrades an application. '''
2436        # Indicate changes are in progress.
2437        css_class = program_id.replace('.','-')
2438        webkit.execute_script("$('." + css_class + "-applying').show();")
2439        webkit.execute_script("$('." + css_class + "-launch').hide();")
2440        webkit.execute_script("$('." + css_class + "-install').hide();")
2441        webkit.execute_script("$('." + css_class + "-reinstall').hide();")
2442        webkit.execute_script("$('." + css_class + "-remove').hide();")
2443        webkit.execute_script("$('." + css_class + "-upgrade').hide();")
2444        webkit.execute_script("$('." + css_class + "-text').css('color','#000');")
2445
2446        # Text to display when applying changes.
2447        install_text = _("Installing...")
2448        remove_text = _("Removing...")
2449        upgrade_text = _("Upgrading...")
2450
2451        # Asynchronous apt process
2452        if action == 'install':
2453            webkit.execute_script("$('." + css_class + "-applying-status').html('" + install_text + "');")
2454            preinstallation.process_packages(program_id, 'install')
2455        elif action == 'remove':
2456            webkit.execute_script("$('." + css_class + "-applying-status').html('" + remove_text + "');")
2457            preinstallation.process_packages(program_id, 'remove')
2458        elif action == 'upgrade':
2459            webkit.execute_script("$('." + css_class + "-applying-status').html('" + upgrade_text + "');")
2460            preinstallation.process_packages(program_id, 'upgrade')
2461        else:
2462            print('[Apps] An unknown action was requested.')
2463
2464        # Refresh the page to reflect changes (if any).
2465        self._apt_cache.close()
2466        self._apt_cache = apt.Cache()
2467        self.update_app_status(webkit, program_id)
2468
2469    def update_app_status(self, webkit, program_id):
2470        ''' Update the web page for an individual application. '''
2471
2472        # Don't attempt to continue if the index is missing/incorrectly parsed.
2473        if not self.index:
2474            print('[Apps] ERROR: Application index not loaded. Cannot update application status.')
2475            return
2476
2477        # Check whether the application is installed or not.
2478        main_package = self.get_attribute_for_app(program_id, 'main-package')
2479        try:
2480            if self._apt_cache[main_package].is_installed:
2481                this_installed = True
2482                arg.print_verbose('Apps', '  Installed: ' + main_package)
2483            else:
2484                this_installed = False
2485                arg.print_verbose('Apps', 'Not present: ' + main_package)
2486        except:
2487            this_installed = False
2488            arg.print_verbose('Apps', 'Not present: ' + main_package)
2489
2490        # Replace any dots with dashes, as they are unsupported in CSS.
2491        css_class = program_id.replace('.','-')
2492
2493        # Update appearance on this page.
2494        webkit.execute_script("$('." + css_class + "-applying').hide();")
2495        if this_installed:
2496            webkit.execute_script("$('." + css_class + "-launch').show();")
2497            webkit.execute_script("$('." + css_class + "-install').hide();")
2498            webkit.execute_script("$('." + css_class + "-reinstall').show();")
2499            webkit.execute_script("$('." + css_class + "-remove').show();")
2500            webkit.execute_script("$('." + css_class + "-upgrade').show();")
2501        else:
2502            webkit.execute_script("$('." + css_class + "-launch').hide();")
2503            webkit.execute_script("$('." + css_class + "-install').show();")
2504            webkit.execute_script("$('." + css_class + "-reinstall').hide();")
2505            webkit.execute_script("$('." + css_class + "-remove').hide();")
2506            webkit.execute_script("$('." + css_class + "-upgrade').hide();")
2507
2508    def update_all_app_status(self, webkit):
2509        ''' Update the webpage whether all indexed applications are installed or not. '''
2510
2511        # Don't attempt to continue if the index is missing/incorrectly parsed.
2512        if not self.index:
2513            print('[Apps] ERROR: Application index not loaded. Cannot update page.')
2514            return
2515
2516        # Enumerate each program and check each one from the index.
2517        arg.print_verbose('Apps', '---- Checking cache for installed applications ----')
2518        for category in self.all_categories:
2519            category_items = list(self.index[category].keys())
2520            for program_id in category_items:
2521                main_package = self.index[category][program_id]['main-package']
2522                # Only check if it's supported on this architecture.
2523                if systemstate.arch in self.index[category][program_id]['arch']:
2524                    self.update_app_status(webkit, program_id)
2525                else:
2526                    continue
2527
2528        arg.print_verbose('Apps', '----------------------------------------')
2529
2530    def get_attribute_for_app(self, requested_id, attribute):
2531        ''' Retrieves a specific attribute from a listed application,
2532            without specifying its category. '''
2533        for category in list(self.index.keys()):
2534            category_items = list(self.index[category].keys())
2535            for program_id in category_items:
2536                if program_id == requested_id:
2537                    if not attribute == 'category':
2538                        return self.index[category][program_id][attribute]
2539                    else:
2540                        return category
2541
2542    def launch_app(self, appid):
2543        ''' Launch an application directly from Welcome '''
2544        program_name = self.get_attribute_for_app(appid, 'name')
2545        program_command = self.get_attribute_for_app(appid, 'launch-command')
2546        print('[Apps] Launched "' + program_name + '" (Command: "' + program_command + '").')
2547        try:
2548            subprocess.Popen(program_command.split(' '))
2549        except:
2550            print('[Apps] Failed to launch command: ' + program_command)
2551            title = _("Software Boutique")
2552            ok_label = _("OK")
2553            text_error = _("An error occurred while launching PROGRAM_NAME. Please consider re-installing the application.").replace('PROGRAM_NAME', program_name) + \
2554                            '\n\n' + _("Command:") + ' "' + program_command + '"'
2555            messagebox = subprocess.Popen(['zenity',
2556                         '--error',
2557                         '--title=' + title,
2558                         "--text=" + text_error,
2559                         "--ok-label=" + ok_label,
2560                         '--window-icon=error',
2561                         '--timeout=15'])
2562
2563    def apply_filter(self, webkit, filter_value, nonfree_toggle=False):
2564        sub_css_class = 'filter-' + filter_value
2565
2566        # Toggle visibility of non-free software.
2567        if nonfree_toggle:
2568            if self.hide_non_free:
2569                self.hide_non_free = False
2570                webkit.execute_script('$("#nonFreeCheckBox").addClass("fa-square");')
2571                webkit.execute_script('$("#nonFreeCheckBox").removeClass("fa-check-square");')
2572            else:
2573                self.hide_non_free = True
2574                webkit.execute_script('$("#nonFreeCheckBox").removeClass("fa-square");')
2575                webkit.execute_script('$("#nonFreeCheckBox").addClass("fa-check-square");')
2576
2577        if filter_value == 'none':
2578            arg.print_verbose('Apps','Filter reset.')
2579            webkit.execute_script('$(".app-entry").show();')
2580            if self.hide_non_free:
2581                arg.print_verbose('Apps','Hiding all proprietary software.')
2582                webkit.execute_script('$(".proprietary").hide();')
2583            return
2584        else:
2585            arg.print_verbose('Apps','Applying filter: ' + filter_value)
2586            webkit.execute_script('$(".app-entry").hide();')
2587
2588            for category in self.all_categories:
2589                category_items = list(self.index[category].keys())
2590                for program_id in category_items:
2591                    app_subcategory = self.index[category][program_id]['subcategory'].replace(' ','-')
2592                    app_open_source = self.index[category][program_id]['open-source']
2593
2594                    # If the application is closed source and we're told to hide it.
2595                    if not app_open_source and self.hide_non_free:
2596                        webkit.execute_script('$("#' + program_id.replace('.','-') + '").hide();')
2597                        continue
2598
2599                    # Only show if subcategory matches.
2600                    if app_subcategory.replace(' ','-') == filter_value:
2601                        webkit.execute_script('$("#' + program_id.replace('.','-') + '").show();')
2602
2603    def show_screenshot(self, filename):
2604        ssw = ScreenshotWindow(filename)
2605
2606
2607class ScreenshotWindow(Gtk.Window):
2608    ''' Displays a simple window when enlarging a screenshot. '''
2609
2610    # FIXME: Destroy this window when finished as it prevents the app from closing via the "Close" button and bloats memory.
2611
2612    def __init__(self, filename):
2613        # Strings for this child window.
2614        title_string = 'Preview Screenshot'
2615        close_string = 'Close'
2616        path = app._data_path + '/img/applications/screenshots/' + filename + '.jpg'
2617
2618        # Build a basic pop up window containing the screenshot at its full dimensions.
2619        Gtk.Window.__init__(self, title=title_string)
2620        self.overlay = Gtk.Overlay()
2621        self.add(self.overlay)
2622        self.background = Gtk.Image.new_from_file(path)
2623        self.overlay.add(self.background)
2624        self.grid = Gtk.Grid()
2625        self.overlay.add_overlay(self.grid)
2626        self.connect('button-press-event', self.destroy_window)      # Click anywhere to close the window.
2627        self.connect('delete-event', Gtk.main_quit)
2628        self.set_position(Gtk.WindowPosition.CENTER)
2629        self.set_resizable(False)
2630        # FIXME: Set the cursor to a hand, like it was a link.
2631        #~ self.get_root_window().set_cursor(Gdk.Cursor(Gdk.CursorType.HAND1))
2632        self.show_all()
2633        Gtk.main()
2634
2635    def destroy_window(self, widget, dummy=None):
2636        self.destroy()
2637
2638
2639class Arguments(object):
2640    '''Check arguments passed the application.'''
2641
2642    def __init__(self):
2643        self.verbose_enabled = False
2644        self.simulate_arch = None
2645        self.simulate_session = None
2646        self.simulate_session_flavour = None
2647        self.simulate_codename = None
2648        self.simulate_no_connection = False
2649        self.simulate_force_connection = False
2650        self.jump_software_page = False
2651        self.simulate_software_changes = False
2652        self.locale = None
2653        self.jump_to = None
2654        self.font_dpi_override = None
2655
2656        for arg in sys.argv:
2657          if arg == '--help':
2658              print('\LliureX Welcome Parameters\n  Intended for debugging and testing purposes only!\n')
2659              print('\nUsage: lliurex-mate-welcome [arguments]')
2660              print('  -v  --verbose               Show more details.')
2661              print('  --force-arch=<ARCH>         Simulate a specific architecture.')
2662              print('                                 "i386", "amd64" or "armhf" or "powerpc"')
2663              print('  --force-session=<TYPE>      Simulate a specific architecture.')
2664              print('                                 "guest", "live", "pi", "vbox"')
2665              print('  --force-codename=<NAME>     Simulate a specific LliureX codename release.')
2666              print('                                 Examples: "trusty", "wily" or "xenial"')
2667              print('  --force-session=<TYPE>      Simulate a specific Session in LliureX.')
2668              print('                                 "server", "desktop", "client", "infantil", "music", "pime"')
2669              print('  --force-no-net              Simulate no internet connection.')
2670              print('  --force-net                 Simulate a working internet connection.')
2671              print('  --software-only             Open Welcome only for the software selections.')
2672              print('  --simulate-changes          Simulate software package changes without modifying the system.')
2673              print('  --locale=<LOCALE>           Locale to use e.g. fr_FR.')
2674              print('  --jump-to=<page>            Open a specific page, excluding html extension.')
2675              print('  --font-dpi=<number>         Override the font size by specifying a font DPI.')
2676              print('')
2677              exit()
2678
2679          if arg == '--verbose' or arg == '-v':
2680              print('[Debug] Verbose mode enabled.')
2681              self.verbose_enabled = True
2682
2683          if arg.startswith('--force-arch'):
2684              try:
2685                  self.simulate_arch = arg.split('--force-arch=')[1]
2686                  if not self.simulate_arch == 'i386' and not self.simulate_arch == 'amd64' and not self.simulate_arch == 'armhf' and not self.simulate_arch == 'powerpc':
2687                      print('[Debug] Unrecognised architecture: ' + self.simulate_arch)
2688                      exit()
2689                  else:
2690                      print('[Debug] Simulating architecture: ' + self.simulate_arch)
2691              except:
2692                  print('[Debug] Invalid arguments for "--force-arch"')
2693                  exit()
2694
2695          if arg.startswith('--force-session'):
2696              try:
2697                  self.simulate_session = arg.split('--force-session=')[1]
2698                  if not self.simulate_session == 'guest' and not self.simulate_session == 'live' and not self.simulate_session == 'pi' and not self.simulate_session == 'vbox':
2699                      print('[Debug] Unrecognised session type: ' + self.simulate_session)
2700                      exit()
2701                  else:
2702                      print('[Debug] Simulating session: ' + self.simulate_session)
2703              except:
2704                  print('[Debug] Invalid arguments for "--force-session"')
2705                  exit()
2706
2707          if arg.startswith('--force-flavour'):
2708              try:
2709                  self.simulate_session_flavour = arg.split('--force-flavour=')[1]
2710                  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':
2711                      print('[Debug] Unrecognised session flavour: ' + self.simulate_session_flavour)
2712                      exit()
2713                  else:
2714                      print('[Debug] Simulating flavour: ' + self.simulate_session_flavour)
2715              except:
2716                  print('[Debug] Invalid arguments for "--force-flavour"')
2717                  exit()
2718
2719          if arg.startswith('--force-codename'):
2720              self.simulate_codename = arg.split('--force-codename=')[1]
2721              print('[Debug] Simulating LliureX release: ' + self.simulate_codename)
2722
2723          if arg == '--force-no-net':
2724              print('[Debug] Simulating the application without an internet connection.')
2725              self.simulate_no_connection = True
2726
2727          if arg == '--force-net':
2728              print('[Debug] Forcing the application to think we\'re connected with an internet connection.')
2729              self.simulate_force_connection = True
2730
2731          if arg == '--software-only':
2732              print('[Welcome] Starting in software selections only mode.')
2733              self.jump_software_page = True
2734
2735          if arg == '--simulate-changes':
2736              print('[Debug] Any changes to software will be simulated without modifying the actual system.')
2737              self.simulate_software_changes = True
2738
2739          if arg.startswith('--locale='):
2740              self.locale = arg.split('--locale=')[1]
2741              print('[Debug] Setting locale to: ' + self.locale)
2742
2743          if arg.startswith('--jump-to='):
2744              self.jump_to = arg.split('--jump-to=')[1]
2745              print('[Debug] Opening page: ' + self.jump_to + '.html')
2746
2747          if arg.startswith('--font-dpi='):
2748              try:
2749                  self.font_dpi_override = int(arg.split('--font-dpi=')[1])
2750              except:
2751                  print('[Debug] Invalid Override Font DPI specified. Ignoring.')
2752                  return
2753              print('[Debug] Overriding font DPI to ' + str(self.font_dpi_override) + '.')
2754
2755    def print_verbose(self, feature, text):
2756        if self.verbose_enabled:
2757            print('[' + feature + '] ' + text)
2758
2759    def override_arch(self):
2760        if not self.simulate_arch == None:
2761            systemstate.arch = self.simulate_arch
2762
2763    def override_session(self):
2764        if not self.simulate_session == None:
2765            if self.simulate_session == 'vbox':
2766                systemstate.graphics_vendor = 'VirtualBox'
2767                systemstate.graphics_grep = 'VirtualBox'
2768            else:
2769                systemstate.session_type = self.simulate_session
2770
2771    def override_flavour(self):
2772        if not self.simulate_session_flavour == None:
2773            systemstate.session_flavour = self.simulate_session_flavour
2774
2775    def override_codename(self):
2776        if not self.simulate_codename == None:
2777            systemstate.codename = self.simulate_codename
2778
2779
2780if __name__ == "__main__":
2781
2782    # Process any parameters passed to the program.
2783    arg = Arguments()
2784
2785    # Application Initialization
2786    set_proc_title()
2787    systemstate = SystemState()
2788    app = WelcomeApp()
2789    dynamicapps = DynamicApps()
2790    preinstallation = PreInstallation()
2791
2792    # Argument Overrides
2793    arg.override_arch()
2794    arg.override_session()
2795    arg.override_flavour()
2796    arg.override_codename()
2797
2798    print('[Welcome] Application Started.')
2799    print('[SystemState] Lliurex Session: ' + systemstate.session_type)
2800    print('[SystemState] Lliurex Flavour: ' + systemstate.session_flavour)
2801    app.run()
Note: See TracBrowser for help on using the repository browser.