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

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