Merge lp:~hmollercl/software-properties/software-properties into lp:software-properties

Proposed by Hans P Möller on 2018-12-29
Status: Merged
Merged at revision: 1066
Proposed branch: lp:~hmollercl/software-properties/software-properties
Merge into: lp:software-properties
Diff against target: 927 lines (+712/-32)
3 files modified
data/designer/main.ui (+225/-23)
software-properties-qt (+6/-1)
softwareproperties/qt/SoftwarePropertiesQt.py (+481/-8)
To merge this branch: bzr merge lp:~hmollercl/software-properties/software-properties
Reviewer Review Type Date Requested Status
Walter Lapchynski 2019-01-02 Pending
Simon Quigley 2018-12-29 Pending
Review via email: mp+361331@code.launchpad.net

Commit message

Added "additional driver" tab for the Qt version

Description of the change

Added "additional driver" tab for the qt version
To maintain same numbers as gtk version it will be tab 4.
Also added --open-tab commandline option so you can open directly in one tab (same as gtk)
Added lxqt-sudo mention (along with pkexec) when not run with root privileges.
Migrate some GridLayout to BoxLayout in Qt version so windows resizing still makes things look good.

To post a comment you must log in.
1059. By Sebastien Bacher on 2019-01-15

[ Carlo Vanini ]
Fix removal of signing keys in the Authentication tab always failing.
(lp: #1656100)

1060. By Sebastien Bacher on 2019-01-15

releasing package software-properties version 0.96.30

1061. By Sebastien Bacher on 2019-01-16

Remove the migration script, it was online needed until bionic

1062. By Andrea Azzarone on 2019-01-22

[ Andrea Azzarone ]
* data/gtkbuilder/dialog-auth.ui: Update design of the authentication
  dialog.
* softwareproperties/gtk/DialogAuth.py: Implement new design of the
  authentication dialog.

1063. By Sebastien Bacher on 2019-01-22

releasing package software-properties version 0.96.31

1064. By Sebastien Bacher on 2019-01-24

* softwareproperties/gtk/DialogAuth.py:
  - Log call_ensure_credentials_finish errors, fixes the build failing
    due to pyflakes warning about an unused variable

1065. By Sebastien Bacher on 2019-01-24

releasing package software-properties version 0.96.32

1066. By Simon Quigley on 2019-02-09

Merge lp:~hmollercl/software-properties/software-properties into lp:software-properties and add changelog entry.

Hans P Möller (hmollercl) wrote :

for testing run software-properties-qt with the --data-dir modifier to use the modified UI.
lxqt-sudo ./software-properties-qt --data-dir ./data

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'data/designer/main.ui'
2--- data/designer/main.ui 2018-07-14 10:32:29 +0000
3+++ data/designer/main.ui 2018-12-29 13:41:58 +0000
4@@ -6,21 +6,15 @@
5 <rect>
6 <x>0</x>
7 <y>0</y>
8- <width>605</width>
9+ <width>706</width>
10 <height>408</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Software Sources</string>
15 </property>
16- <layout class="QGridLayout">
17- <property name="margin">
18- <number>9</number>
19- </property>
20- <property name="spacing">
21- <number>6</number>
22- </property>
23- <item row="0" column="0">
24+ <layout class="QVBoxLayout" name="verticalLayout">
25+ <item>
26 <widget class="QTabWidget" name="tabWidget">
27 <property name="currentIndex">
28 <number>0</number>
29@@ -30,7 +24,16 @@
30 <string>Software &amp; Updates</string>
31 </attribute>
32 <layout class="QGridLayout">
33- <property name="margin">
34+ <property name="leftMargin">
35+ <number>9</number>
36+ </property>
37+ <property name="topMargin">
38+ <number>9</number>
39+ </property>
40+ <property name="rightMargin">
41+ <number>9</number>
42+ </property>
43+ <property name="bottomMargin">
44 <number>9</number>
45 </property>
46 <property name="spacing">
47@@ -42,7 +45,16 @@
48 <string>&lt;b&gt;Downloadable from the Internet&lt;/b&gt;</string>
49 </property>
50 <layout class="QGridLayout">
51- <property name="margin">
52+ <property name="leftMargin">
53+ <number>9</number>
54+ </property>
55+ <property name="topMargin">
56+ <number>9</number>
57+ </property>
58+ <property name="rightMargin">
59+ <number>9</number>
60+ </property>
61+ <property name="bottomMargin">
62 <number>9</number>
63 </property>
64 <property name="spacing">
65@@ -57,7 +69,16 @@
66 <enum>QFrame::Raised</enum>
67 </property>
68 <layout class="QGridLayout">
69- <property name="margin">
70+ <property name="leftMargin">
71+ <number>0</number>
72+ </property>
73+ <property name="topMargin">
74+ <number>0</number>
75+ </property>
76+ <property name="rightMargin">
77+ <number>0</number>
78+ </property>
79+ <property name="bottomMargin">
80 <number>0</number>
81 </property>
82 <property name="spacing">
83@@ -68,7 +89,16 @@
84 <property name="spacing">
85 <number>6</number>
86 </property>
87- <property name="margin">
88+ <property name="leftMargin">
89+ <number>0</number>
90+ </property>
91+ <property name="topMargin">
92+ <number>0</number>
93+ </property>
94+ <property name="rightMargin">
95+ <number>0</number>
96+ </property>
97+ <property name="bottomMargin">
98 <number>0</number>
99 </property>
100 </layout>
101@@ -105,7 +135,16 @@
102 <string>Installable from CD-ROM/DVD</string>
103 </property>
104 <layout class="QGridLayout">
105- <property name="margin">
106+ <property name="leftMargin">
107+ <number>9</number>
108+ </property>
109+ <property name="topMargin">
110+ <number>9</number>
111+ </property>
112+ <property name="rightMargin">
113+ <number>9</number>
114+ </property>
115+ <property name="bottomMargin">
116 <number>9</number>
117 </property>
118 <property name="spacing">
119@@ -168,7 +207,16 @@
120 <string>Other Software</string>
121 </attribute>
122 <layout class="QGridLayout">
123- <property name="margin">
124+ <property name="leftMargin">
125+ <number>9</number>
126+ </property>
127+ <property name="topMargin">
128+ <number>9</number>
129+ </property>
130+ <property name="rightMargin">
131+ <number>9</number>
132+ </property>
133+ <property name="bottomMargin">
134 <number>9</number>
135 </property>
136 <property name="spacing">
137@@ -233,7 +281,16 @@
138 <string>Updates</string>
139 </attribute>
140 <layout class="QGridLayout">
141- <property name="margin">
142+ <property name="leftMargin">
143+ <number>9</number>
144+ </property>
145+ <property name="topMargin">
146+ <number>9</number>
147+ </property>
148+ <property name="rightMargin">
149+ <number>9</number>
150+ </property>
151+ <property name="bottomMargin">
152 <number>9</number>
153 </property>
154 <property name="spacing">
155@@ -245,7 +302,16 @@
156 <string>&lt;b&gt;Ubuntu updates&lt;/b&gt;</string>
157 </property>
158 <layout class="QGridLayout" name="gridLayout_2">
159- <property name="margin">
160+ <property name="leftMargin">
161+ <number>9</number>
162+ </property>
163+ <property name="topMargin">
164+ <number>9</number>
165+ </property>
166+ <property name="rightMargin">
167+ <number>9</number>
168+ </property>
169+ <property name="bottomMargin">
170 <number>9</number>
171 </property>
172 <property name="spacing">
173@@ -256,7 +322,16 @@
174 <property name="spacing">
175 <number>6</number>
176 </property>
177- <property name="margin">
178+ <property name="leftMargin">
179+ <number>0</number>
180+ </property>
181+ <property name="topMargin">
182+ <number>0</number>
183+ </property>
184+ <property name="rightMargin">
185+ <number>0</number>
186+ </property>
187+ <property name="bottomMargin">
188 <number>0</number>
189 </property>
190 <item>
191@@ -268,7 +343,16 @@
192 <enum>QFrame::Raised</enum>
193 </property>
194 <layout class="QGridLayout">
195- <property name="margin">
196+ <property name="leftMargin">
197+ <number>0</number>
198+ </property>
199+ <property name="topMargin">
200+ <number>0</number>
201+ </property>
202+ <property name="rightMargin">
203+ <number>0</number>
204+ </property>
205+ <property name="bottomMargin">
206 <number>0</number>
207 </property>
208 <property name="spacing">
209@@ -288,7 +372,16 @@
210 <string>&lt;b&gt;Automatic updates&lt;/b&gt;</string>
211 </property>
212 <layout class="QGridLayout" name="gridLayout">
213- <property name="margin">
214+ <property name="leftMargin">
215+ <number>9</number>
216+ </property>
217+ <property name="topMargin">
218+ <number>9</number>
219+ </property>
220+ <property name="rightMargin">
221+ <number>9</number>
222+ </property>
223+ <property name="bottomMargin">
224 <number>9</number>
225 </property>
226 <property name="spacing">
227@@ -370,7 +463,16 @@
228 <string>Authentication</string>
229 </attribute>
230 <layout class="QGridLayout">
231- <property name="margin">
232+ <property name="leftMargin">
233+ <number>9</number>
234+ </property>
235+ <property name="topMargin">
236+ <number>9</number>
237+ </property>
238+ <property name="rightMargin">
239+ <number>9</number>
240+ </property>
241+ <property name="bottomMargin">
242 <number>9</number>
243 </property>
244 <property name="spacing">
245@@ -437,12 +539,112 @@
246 </item>
247 </layout>
248 </widget>
249+ <widget class="QWidget" name="vbox_drivers">
250+ <attribute name="title">
251+ <string>Additional Drivers</string>
252+ </attribute>
253+ <layout class="QVBoxLayout" name="verticalLayout_2">
254+ <property name="spacing">
255+ <number>0</number>
256+ </property>
257+ <item>
258+ <widget class="QScrollArea" name="scrollArea">
259+ <property name="widgetResizable">
260+ <bool>true</bool>
261+ </property>
262+ <widget class="QWidget" name="scrollAreaWidgetContents">
263+ <property name="geometry">
264+ <rect>
265+ <x>0</x>
266+ <y>0</y>
267+ <width>670</width>
268+ <height>244</height>
269+ </rect>
270+ </property>
271+ <layout class="QVBoxLayout" name="verticalLayout_3">
272+ <property name="spacing">
273+ <number>0</number>
274+ </property>
275+ <property name="leftMargin">
276+ <number>0</number>
277+ </property>
278+ <property name="topMargin">
279+ <number>0</number>
280+ </property>
281+ <property name="rightMargin">
282+ <number>0</number>
283+ </property>
284+ <property name="bottomMargin">
285+ <number>0</number>
286+ </property>
287+ <item>
288+ <layout class="QVBoxLayout" name="box_driver_detail"/>
289+ </item>
290+ </layout>
291+ </widget>
292+ </widget>
293+ </item>
294+ <item>
295+ <widget class="QGroupBox" name="groupBox_driver_action">
296+ <property name="sizePolicy">
297+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
298+ <horstretch>0</horstretch>
299+ <verstretch>0</verstretch>
300+ </sizepolicy>
301+ </property>
302+ <layout class="QHBoxLayout" name="box_driver_action">
303+ <property name="spacing">
304+ <number>0</number>
305+ </property>
306+ <property name="leftMargin">
307+ <number>0</number>
308+ </property>
309+ <property name="topMargin">
310+ <number>0</number>
311+ </property>
312+ <property name="rightMargin">
313+ <number>0</number>
314+ </property>
315+ <property name="bottomMargin">
316+ <number>0</number>
317+ </property>
318+ <item>
319+ <widget class="QLabel" name="label_driver_action">
320+ <property name="text">
321+ <string>No propietary drivers are in use.</string>
322+ </property>
323+ </widget>
324+ </item>
325+ </layout>
326+ </widget>
327+ </item>
328+ <item>
329+ <widget class="QLabel" name="label_disc">
330+ <property name="text">
331+ <string>&lt;small&gt;A proprietary driver has private code that Ubuntu developers can't review or improve. Security and other updates are dependent on the driver vendor.&lt;/small&gt;</string>
332+ </property>
333+ <property name="wordWrap">
334+ <bool>true</bool>
335+ </property>
336+ </widget>
337+ </item>
338+ </layout>
339+ </widget>
340 <widget class="QWidget" name="tab_5">
341 <attribute name="title">
342 <string>Statistics</string>
343 </attribute>
344 <layout class="QGridLayout">
345- <property name="margin">
346+ <property name="leftMargin">
347+ <number>9</number>
348+ </property>
349+ <property name="topMargin">
350+ <number>9</number>
351+ </property>
352+ <property name="rightMargin">
353+ <number>9</number>
354+ </property>
355+ <property name="bottomMargin">
356 <number>9</number>
357 </property>
358 <property name="spacing">
359@@ -482,7 +684,7 @@
360 </widget>
361 </widget>
362 </item>
363- <item row="1" column="0">
364+ <item>
365 <widget class="QDialogButtonBox" name="buttonBox">
366 <property name="orientation">
367 <enum>Qt::Horizontal</enum>
368
369=== modified file 'software-properties-qt'
370--- software-properties-qt 2018-07-14 10:11:32 +0000
371+++ software-properties-qt 2018-12-29 13:41:58 +0000
372@@ -82,6 +82,10 @@
373 _("Enable the specified component of the distro's repositories"),
374 "name");
375 parser.addOption(componentOption);
376+ openTabOption = QCommandLineOption("open-tab",
377+ _("Open specific tab number on startup"),
378+ "open_tab");
379+ parser.addOption(openTabOption);
380 ppaOption = QCommandLineOption("enable-ppa",
381 _("Enable PPA with the given name"),
382 "name");
383@@ -100,7 +104,7 @@
384
385 # Check for root permissions
386 if os.geteuid() != 0:
387- text = "Please run this software with administrative rights. To do so, run this program with pkexec."
388+ text = "Please run this software with administrative rights. To do so, run this program with lxqt-sudo or pkexec."
389 title = "Need administrative powers"
390 msgbox = QMessageBox.critical(None, title, text)
391 sys.exit(1)
392@@ -119,6 +123,7 @@
393 options.debug = parser.isSet(debugOption)
394 options.debug = parser.isSet(massiveDebugOption)
395 options.no_update = parser.isSet(noUpdateOption)
396+ options.open_tab = parser.value(openTabOption)
397
398 attachWinID = None
399 if parser.isSet(winidOption):
400
401=== modified file 'softwareproperties/qt/SoftwarePropertiesQt.py'
402--- softwareproperties/qt/SoftwarePropertiesQt.py 2018-07-14 10:45:16 +0000
403+++ softwareproperties/qt/SoftwarePropertiesQt.py 2018-12-29 13:41:58 +0000
404@@ -49,6 +49,28 @@
405 from .DialogMirror import DialogMirror
406 from .CdromProgress import CdromProgress
407
408+from UbuntuDrivers import detect
409+import sys
410+import apt
411+from functools import partial
412+from aptdaemon import client
413+from aptdaemon.errors import NotAuthorizedError, TransactionFailed
414+import aptsources.distro
415+import logging
416+
417+class DetectDriverThread(QThread):
418+ #WARNING class to run detect_drivers() in separate thread
419+ #so GUI won't freeze.
420+ def __init__(self, swprop):
421+ QThread.__init__(self)
422+ self.swprop = swprop
423+
424+ def __del__(self):
425+ self.wait()
426+
427+ def run(self):
428+ self.swprop.detect_drivers()
429+
430 class SoftwarePropertiesQtUI(QWidget):
431 def __init__(self, datadir):
432 QWidget.__init__(self)
433@@ -101,6 +123,12 @@
434 self.init_isv_sources()
435 self.show_isv_sources()
436 self.show_cdrom_sources()
437+ # Setup and show the Additonal Drivers tab
438+ self.init_drivers()
439+
440+ # Connect to switch-page before setting initial tab. Otherwise the
441+ # first switch goes unnoticed.
442+ self.userinterface.tabWidget.currentChanged.connect(self.tab_switched)
443
444 self.userinterface.checkbutton_source_code.clicked.connect(self.on_checkbutton_source_code_toggled)
445 self.userinterface.button_auth_restore.clicked.connect(self.on_restore_clicked)
446@@ -117,15 +145,36 @@
447 self.userinterface.treeview_sources.itemClicked.connect(self.on_treeview_sources_cursor_changed)
448 self.userinterface.treeview_cdroms.itemChanged.connect(self.on_cdrom_source_toggled)
449 self.userinterface.treeview2.itemClicked.connect(self.on_treeview_keys_cursor_changed)
450- button_close = self.userinterface.buttonBox.button(QDialogButtonBox.Close)
451- button_close.setIcon(QIcon.fromTheme("dialog-close"))
452- button_revert = self.userinterface.buttonBox.button(QDialogButtonBox.Reset)
453- button_revert.setIcon(QIcon.fromTheme("edit-undo"))
454- button_revert.clicked.connect(self.on_button_revert_clicked)
455+
456+ self.button_close = self.userinterface.buttonBox.button(QDialogButtonBox.Close)
457+ self.button_close.setIcon(QIcon.fromTheme("dialog-close"))
458+ self.button_revert = self.userinterface.buttonBox.button(QDialogButtonBox.Reset)
459+ self.button_revert.setIcon(QIcon.fromTheme("edit-undo"))
460+ self.button_revert.clicked.connect(self.on_button_revert_clicked)
461
462 self.init_distro()
463 self.show_distro()
464-
465+
466+ self.apt_client = client.AptClient()
467+
468+ if options and options.open_tab:
469+ self.userinterface.tabWidget.setCurrentIndex(int(options.open_tab))
470+
471+ def tab_switched(self):
472+ # On the additional drivers page, don't show the backend revert button.
473+ if self.userinterface.tabWidget.currentIndex() == 4: #vbox_drivers is 4
474+ self.button_revert.setVisible(False)
475+ if not self.detect_called:
476+ #WARNING detect_drivers() runs in separate thread
477+ #in DetectDriverThread class so GUI won't freeze
478+ #after finish show_drivers() has to run in main thread because it updates the GUI
479+ self.detect_driver_thread = DetectDriverThread(self)
480+ self.detect_driver_thread.finished.connect(self.show_drivers)
481+ self.detect_driver_thread.start()
482+
483+ else:
484+ self.button_revert.setVisible(True)
485+
486 def init_popcon(self):
487 """ If popcon is enabled show the statistics tab and an explanation
488 corresponding to the used distro """
489@@ -136,8 +185,8 @@
490 self.userinterface.label_popcon_desc.setText(text)
491 self.userinterface.checkbutton_popcon.setChecked(is_helpful)
492 else:
493- self.userinterface.tabWidget.removeTab(4)
494-
495+ self.userinterface.tabWidget.removeTab(5)
496+
497 def init_server_chooser(self):
498 """ Set up the widgets that allow to choose an alternate download site """
499 # nothing to do here, set up signal in show_distro()
500@@ -747,3 +796,427 @@
501
502 def run(self):
503 kapp.exec_()
504+
505+ def on_driver_changes_progress(self, transaction, progress):
506+ self.button_driver_revert.setVisible(False)
507+ self.button_driver_apply.setVisible(False)
508+ self.button_driver_restart.setVisible(False)
509+ self.button_driver_cancel.setVisible(True)
510+ self.progress_bar.setVisible(True)
511+
512+ self.userinterface.label_driver_action.setText("Applying changes...")
513+ self.progress_bar.setValue(progress)
514+
515+ def on_driver_changes_finish(self, transaction, exit_state):
516+ self.progress_bar.setVisible(False)
517+ self.clear_changes()
518+ self.apt_cache = apt.Cache()
519+ self.set_driver_action_status()
520+ self.update_label_and_icons_from_status()
521+ self.button_driver_revert.setVisible(True)
522+ self.button_driver_apply.setVisible(True)
523+ self.button_driver_cancel.setVisible(False)
524+ #self.scrolled_window_drivers.set_sensitive(True)
525+
526+ def on_driver_changes_error(self, transaction, error_code, error_details):
527+ self.on_driver_changes_revert()
528+ self.set_driver_action_status()
529+ self.update_label_and_icons_from_status()
530+ self.button_driver_revert.setVisible(True)
531+ self.button_driver_apply.setVisible(True)
532+ self.button_driver_cancel.setVisible(False)
533+ #self.scrolled_window_drivers.set_sensitive(True)
534+
535+ def on_driver_changes_cancellable_changed(self, transaction, cancellable):
536+ self.button_driver_cancel.setEnabled(cancellable)
537+
538+ def on_driver_changes_apply(self, button):
539+ button = self.userinterface.sender()
540+ installs = []
541+ removals = []
542+
543+ for pkg in self.driver_changes:
544+ if pkg.is_installed:
545+ removals.append(pkg.shortname)
546+ # The main NVIDIA package is only a metapackage.
547+ # We need to collect its dependencies, so that
548+ # we can uninstall the driver properly.
549+ if 'nvidia' in pkg.shortname:
550+ for dep in get_dependencies(self.apt_cache, pkg.shortname, 'nvidia'):
551+ dep_pkg = self.apt_cache[dep]
552+ if dep_pkg.is_installed:
553+ removals.append(dep_pkg.shortname)
554+ else:
555+ installs.append(pkg.shortname)
556+
557+ try:
558+ self.transaction = self.apt_client.commit_packages(install=installs, remove=removals, reinstall=[], purge=[], upgrade=[], downgrade=[])
559+ self.transaction.connect('progress-changed', self.on_driver_changes_progress)
560+ self.transaction.connect('cancellable-changed', self.on_driver_changes_cancellable_changed)
561+ self.transaction.connect('finished', self.on_driver_changes_finish)
562+ self.transaction.connect('error', self.on_driver_changes_error)
563+ self.transaction.set_debconf_frontend("gnome")
564+ self.transaction.run()
565+ self.button_driver_revert.setEnabled(False)
566+ self.button_driver_apply.setEnabled(False)
567+ #self.scrolled_window_drivers.set_sensitive(False)
568+ except (NotAuthorizedError, TransactionFailed) as e:
569+ print("Warning: install transaction not completed successfully: {}".format(e))
570+
571+ def on_driver_changes_revert(self, button_revert=None):
572+ # HACK: set all the "Do not use" first; then go through the list of the
573+ # actually selected drivers.
574+ for button in self.no_drv:
575+ button.setChecked(True)
576+
577+ for alias in self.orig_selection:
578+ button = self.orig_selection[alias]
579+ button.setChecked(True)
580+
581+ self.clear_changes()
582+
583+ self.button_driver_revert.setEnabled(False)
584+ self.button_driver_apply.setEnabled(False)
585+
586+ def on_driver_changes_cancel(self, button_cancel):
587+ self.transaction.cancel()
588+ self.clear_changes()
589+
590+ def on_driver_restart_clicked(self, button_restart):
591+ if 'XDG_CURRENT_DESKTOP' in os.environ:
592+ desktop = os.environ['XDG_CURRENT_DESKTOP']
593+ else:
594+ desktop = 'Unknown'
595+
596+ if (desktop == 'ubuntu:GNOME' and os.path.exists('/usr/bin/gnome-session-quit')):
597+ # argument presents a dialog to cancel reboot
598+ subprocess.call(['gnome-session-quit', '--reboot'])
599+ elif (desktop == 'XFCE' and
600+ os.path.exists('/usr/bin/xfce4-session-logout')):
601+ subprocess.call(['xfce4-session-logout'])
602+ elif (desktop == 'LXDE' and os.path.exists('/usr/bin/lubuntu-logout')):
603+ subprocess.call(['lubuntu-logout'])
604+ elif (desktop == 'LXQt' and os.path.exists('/usr/bin/lxqt-leave')):
605+ subprocess.call(['lxqt-leave'])
606+
607+ def clear_changes(self):
608+ self.orig_selection = {}
609+ self.driver_changes = []
610+
611+ def init_drivers(self):
612+ """Additional Drivers tab"""
613+ self.button_driver_revert = QPushButton("Revert")
614+ self.button_driver_apply = QPushButton("Apply Changes")
615+ self.button_driver_cancel = QPushButton("Cancel")
616+ self.button_driver_restart = QPushButton("Restart...")
617+
618+ self.button_driver_revert.clicked.connect(self.on_driver_changes_revert)
619+ self.button_driver_apply.clicked.connect(self.on_driver_changes_apply)
620+ self.button_driver_cancel.clicked.connect(self.on_driver_changes_cancel)
621+ self.button_driver_restart.clicked.connect(self.on_driver_restart_clicked)
622+
623+ self.button_driver_revert.setEnabled(False)
624+ self.button_driver_revert.setVisible(True)
625+ self.button_driver_apply.setEnabled(False)
626+ self.button_driver_apply.setVisible(True)
627+ self.button_driver_cancel.setVisible(False)
628+ self.button_driver_restart.setVisible(False)
629+
630+ #self.userinterface.box_driver_action.addWidget(self.userinterface.label_driver_action)
631+ self.userinterface.box_driver_action.addStretch()
632+ self.userinterface.box_driver_action.addWidget(self.button_driver_apply)
633+ self.userinterface.box_driver_action.addWidget(self.button_driver_revert)
634+ self.userinterface.box_driver_action.addWidget(self.button_driver_restart)
635+ self.userinterface.box_driver_action.addWidget(self.button_driver_cancel)
636+
637+ self.label_driver_detail = QLabel("Searching for available drivers...")
638+ self.label_driver_detail.setAlignment(Qt.AlignCenter)
639+ self.userinterface.box_driver_detail.addWidget(self.label_driver_detail)
640+
641+ self.progress_bar = QProgressBar()
642+
643+ self.userinterface.box_driver_action.addWidget(self.progress_bar)
644+ self.progress_bar.setVisible(False)
645+
646+ self.devices = {}
647+ self.detect_called = False
648+ self.driver_changes = []
649+ self.orig_selection = {}
650+ # HACK: the case where the selection is actually "Do not use"; is a little
651+ # tricky to implement because you can't check for whether a package is
652+ # installed or any such thing. So let's keep a list of all the
653+ # "Do not use" radios, set those active first, then iterate through
654+ # orig_selection when doing a Reset.
655+ self.no_drv = []
656+ self.nonfree_drivers = 0
657+ self.ui_building = False
658+
659+ def detect_drivers(self):
660+ # WARNING: This is run in a separate thread.
661+ self.detect_called = True
662+ try:
663+ self.apt_cache = apt.Cache()
664+ self.devices = detect.system_device_drivers(self.apt_cache)
665+ except:
666+ # Catch all exceptions and feed them to apport.
667+ #GLib.idle_add(self.label_driver_detail.set_text, _("An error occurred while searching for drivers."))
668+ self.label_driver_detail.setText("An error occurred while searching for drivers.")
669+ # For apport to catch this exception. See
670+ # http://bugs.python.org/issue1230540
671+ sys.excepthook(*sys.exc_info())
672+ return
673+ def on_driver_selection_changed(self, modalias, pkg_name=None):
674+ button = self.userinterface.sender()
675+ #print(modalias)
676+ #print(pkg_name)
677+
678+ if self.ui_building:
679+ return
680+
681+ pkg = None
682+ try:
683+ if pkg_name:
684+ pkg = self.apt_cache[pkg_name]
685+ # If the package depends on dkms
686+ # we need to install the correct linux metapackage
687+ # so that users get the latest headers
688+ if 'dkms' in pkg.candidate.record['Depends']:
689+ linux_meta = detect.get_linux(self.apt_cache)
690+ if (linux_meta and linux_meta not in self.driver_changes):
691+ # Install the linux metapackage
692+ lmp = self.apt_cache[linux_meta]
693+ if not lmp.is_installed:
694+ self.driver_changes.append(lmp)
695+ except KeyError:
696+ pass
697+
698+ if button.isChecked():
699+ if pkg in self.driver_changes:
700+ self.driver_changes.remove(pkg)
701+
702+ if (pkg is not None and modalias in self.orig_selection and button is not self.orig_selection[modalias]):
703+ self.driver_changes.append(pkg)
704+ else:
705+ if pkg in self.driver_changes:
706+ self.driver_changes.remove(pkg)
707+
708+ # for revert; to re-activate the original radio buttons.
709+ if modalias not in self.orig_selection:
710+ self.orig_selection[modalias] = button
711+
712+ if (pkg is not None and pkg not in self.driver_changes and pkg.is_installed):
713+ self.driver_changes.append(pkg)
714+
715+ self.button_driver_revert.setEnabled(bool(self.driver_changes))
716+ self.button_driver_apply.setEnabled(bool(self.driver_changes))
717+
718+ def gather_device_data(self, device):
719+ '''Get various device data used to build the GUI.
720+
721+ return a tuple of (overall_status string, icon, drivers dict).
722+ the drivers dict is using this form:
723+ {"recommended/alternative": {pkg_name: {
724+ 'selected': True/False
725+ 'description':
726+'description'
727+ 'builtin': True/False
728+ }
729+ }}
730+ "manually_installed": {"manual": {'selected': True, 'description':
731+description_string}}
732+ "no_driver": {"no_driver": {'selected': True/False, 'description':
733+description_string}}
734+
735+ Please note that either manually_installed and no_driver are set to
736+None if not applicable
737+ (no_driver isn't present if there are builtins)
738+ '''
739+
740+ possible_overall_status = {
741+ 'recommended': (_("This device is using the recommended driver."),
742+"recommended-driver"),
743+ 'alternative': (_("This device is using an alternative driver."),
744+"other-driver"),
745+ 'manually_installed': (_("This device is using a manually-installed driver."), "other-driver"),
746+ 'no_driver': (_("This device is not working."), "disable-device")
747+ }
748+
749+ returned_drivers = {'recommended': {}, 'alternative': {},
750+'manually_installed': {}, 'no_driver': {}}
751+ have_builtin = False
752+ one_selected = False
753+ try:
754+ if device['manual_install']:
755+ returned_drivers['manually_installed'] = {True: {'selected': True,'description': _("Continue using a manually installed driver")}}
756+ except KeyError:
757+ pass
758+
759+ for pkg_driver_name in device['drivers']:
760+ current_driver = device['drivers'][pkg_driver_name]
761+
762+ # get general status
763+ driver_status = 'alternative'
764+ try:
765+ if current_driver['recommended'] and current_driver['from_distro']:
766+ driver_status = 'recommended'
767+ except KeyError:
768+ pass
769+
770+ builtin = False
771+ try:
772+ if current_driver['builtin']:
773+ builtin = True
774+ have_builtin = True
775+ except KeyError:
776+ pass
777+
778+ try:
779+ pkg = self.apt_cache[pkg_driver_name]
780+ installed = pkg.is_installed
781+ if pkg.candidate is not None:
782+ description = _("Using {} from {}").format(pkg.candidate.summary, pkg.shortname)
783+ else:
784+ description = _("Using {}").format(pkg.shortname)
785+ except KeyError:
786+ print("WARNING: a driver ({}) doesn't have any available package associated: {}".format(pkg_driver_name, current_driver))
787+ continue
788+
789+ # gather driver description
790+ if current_driver['free']:
791+ licence = _("open source")
792+ else:
793+ licence = _("proprietary")
794+
795+ if driver_status == 'recommended':
796+ base_string = _("{base_description} ({licence}, tested)")
797+ else:
798+ base_string = _("{base_description} ({licence})")
799+ description = base_string.format(base_description=description,
800+licence=licence)
801+
802+ selected = False
803+ if not builtin and not returned_drivers['manually_installed']:
804+ selected = installed
805+ if installed:
806+ selected = True
807+ one_selected = True
808+
809+ returned_drivers[driver_status].setdefault(pkg_driver_name, {'selected': selected,'description': description,'builtin': builtin})
810+
811+ # adjust making the needed addition
812+ if not have_builtin:
813+ selected = False
814+ if not one_selected:
815+ selected = True
816+ returned_drivers["no_driver"] = {True: {'selected': selected, 'description': _("Do not use the device")}}
817+ else:
818+ # we have a builtin and no selection: builtin is the selected one then
819+ if not one_selected:
820+ for section in ('recommended', 'alternative'):
821+ for pkg_name in returned_drivers[section]:
822+ if returned_drivers[section][pkg_name]['builtin']:
823+ returned_drivers[section][pkg_name]['selected'] = True
824+
825+ # compute overall status
826+ for section in returned_drivers:
827+ for keys in returned_drivers[section]:
828+ if returned_drivers[section][keys]['selected']:
829+ (overall_status, icon) = possible_overall_status[section]
830+
831+ return (overall_status, icon, returned_drivers)
832+
833+ def show_drivers(self):
834+ if not self.devices:
835+ # No drivers found.
836+ self.label_driver_detail.setText("No additional drivers available.")
837+ return
838+ else:
839+ self.label_driver_detail.hide()
840+
841+ self.option_group = {}
842+ self.radio_button = {}
843+ self.ui_building = True
844+ self.dynamic_device_status = {}
845+ for device in sorted(self.devices.keys()):
846+ (overall_status, icon, drivers) = self.gather_device_data(self.devices[device])
847+
848+ driver_status = QLabel()
849+ driver_status.setAlignment(Qt.AlignTop)
850+ driver_status.setAlignment(Qt.AlignHCenter)
851+ pixmap = QIcon.fromTheme(icon).pixmap(QSize(16, 16))
852+ driver_status.setPixmap(pixmap)
853+
854+ device_box = QHBoxLayout()
855+ device_box.addWidget(driver_status)
856+ device_detail = QVBoxLayout()
857+ device_box.addLayout(device_detail,1)#1 for priority over the icon to stretch
858+
859+ widget = QLabel("{}: {}".format(self.devices[device].get('vendor', _('Unknown')), self.devices[device].get('model', _('Unknown'))))
860+ widget.setAlignment(Qt.AlignLeft)
861+ device_detail.addWidget(widget)
862+ widget = QLabel("<small>{}</small>".format(overall_status))
863+ widget.setAlignment(Qt.AlignLeft)
864+ #widget.set_use_markup(True)
865+ device_detail.addWidget(widget)
866+ self.dynamic_device_status[device] = (driver_status, widget)
867+
868+ self.option_group[device] = None
869+ # define the order of introspection
870+ for section in ('recommended', 'alternative', 'manually_installed', 'no_driver'):
871+ for driver in drivers[section]:
872+ self.radio_button = QRadioButton(drivers[section][driver]['description'])
873+ if self.option_group[device]:
874+ self.option_group[device].addButton(self.radio_button)
875+ else:
876+ self.option_group[device] = QButtonGroup()
877+ self.option_group[device].addButton(self.radio_button)
878+
879+ device_detail.addWidget(self.radio_button)
880+ self.radio_button.setChecked(drivers[section][driver]['selected'])
881+
882+ if section == 'no_driver':
883+ self.no_drv.append(self.radio_button)
884+ if section in ('manually_install', 'no_driver') or ('builtin' in drivers[section][driver] and drivers[section][driver]['builtin']):
885+ self.radio_button.toggled.connect(partial( self.on_driver_selection_changed, device))
886+ else:
887+ self.radio_button.toggled.connect(partial( self.on_driver_selection_changed, device, driver))
888+ if drivers['manually_installed'] and section != 'manually_installed':
889+ self.radio_button.setEnabled(False)
890+
891+ self.userinterface.box_driver_detail.addLayout(device_box)
892+
893+ self.userinterface.box_driver_detail.addStretch()
894+ self.ui_building = False
895+ #self.userinterface.box_driver_detail.show_all()
896+ self.set_driver_action_status()
897+
898+ def update_label_and_icons_from_status(self):
899+ '''Update the current label and icon, computing the new device status'''
900+
901+ for device in self.devices:
902+ (overall_status, icon, drivers) = self.gather_device_data(self.devices[device])
903+ (driver_status, widget) = self.dynamic_device_status[device]
904+
905+ pixmap = QIcon.fromTheme(icon).pixmap(QSize(16, 16))
906+ driver_status.setPixmap(pixmap)
907+ widget.setText("<small>{}</small>".format(overall_status))
908+
909+ def set_driver_action_status(self):
910+ # Update the label in case we end up having some kind of proprietary driver in use.
911+ if (os.path.exists('/var/run/reboot-required')):
912+ self.userinterface.label_driver_action.setText("You need to restart the computer to complete the driver changes.")
913+ self.button_driver_restart.setVisible(True)
914+ return
915+
916+ self.nonfree_drivers = 0
917+ for device in self.devices:
918+ for pkg_name in self.devices[device]['drivers']:
919+ pkg = self.apt_cache[pkg_name]
920+ if not self.devices[device]['drivers'][pkg_name]['free'] and pkg.is_installed:
921+ self.nonfree_drivers = self.nonfree_drivers + 1
922+
923+ if self.nonfree_drivers > 0:
924+ text = "%s proprietary driver in use." % ( self.nonfree_drivers)
925+ self.userinterface.label_driver_action.setText(text)
926+ else:
927+ self.userinterface.label_driver_action.setText(_("No proprietary drivers are in use."))

Subscribers

People subscribed via source and target branches

to status/vote changes: