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

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