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

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