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