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

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