Merge ~azzar1/software-properties:ubuntu/bionic-livepatch into software-properties:ubuntu/bionic
- Git
- lp:~azzar1/software-properties
- ubuntu/bionic-livepatch
- Merge into ubuntu/bionic
Proposed by
Andrea Azzarone
Status: | Merged |
---|---|
Merged at revision: | 03978fc2a7159c878eb2fb33c79ed7ca45f4b08c |
Proposed branch: | ~azzar1/software-properties:ubuntu/bionic-livepatch |
Merge into: | software-properties:ubuntu/bionic |
Diff against target: |
2640 lines (+1452/-626) 23 files modified
data/gtkbuilder/dialog-auth.ui (+93/-77) data/gtkbuilder/dialog-livepatch-error.ui (+96/-15) data/gtkbuilder/main.ui (+221/-81) data/icons/scalable/apps/livepatch.svg (+1/-0) data/software-properties-livepatch.desktop.in (+12/-0) debian/changelog (+12/-0) debian/control (+7/-2) debian/gbp.conf (+3/-0) debian/software-properties-gtk.install (+1/-0) po/POTFILES.in (+6/-0) setup.cfg (+1/-0) softwareproperties/GoaAuth.py (+16/-5) softwareproperties/LivepatchService.py (+254/-0) softwareproperties/LivepatchSnap.py (+135/-0) softwareproperties/SoftwareProperties.py (+0/-152) softwareproperties/dbus/SoftwarePropertiesDBus.py (+8/-2) softwareproperties/gtk/DialogAuth.py (+132/-117) softwareproperties/gtk/DialogLivepatchError.py (+15/-6) softwareproperties/gtk/LivepatchPage.py (+375/-0) softwareproperties/gtk/SimpleGtkbuilderApp.py (+3/-0) softwareproperties/gtk/SoftwarePropertiesGtk.py (+5/-168) softwareproperties/gtk/utils.py (+56/-1) tests/aptroot/etc/apt/apt.conf.d/.keep (+0/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Sebastien Bacher | Approve | ||
Review via email: mp+365684@code.launchpad.net |
Commit message
* Backport Livepatch changes from Disco (LP: #1823761):
- Implement new design for authentication dialog.
- Add livepatch desktop file and icon.
- Move Livepatch UI in a diffrent tab.
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/data/gtkbuilder/dialog-auth.ui b/data/gtkbuilder/dialog-auth.ui | |||
2 | index b630fff..ab664ce 100644 | |||
3 | --- a/data/gtkbuilder/dialog-auth.ui | |||
4 | +++ b/data/gtkbuilder/dialog-auth.ui | |||
5 | @@ -1,114 +1,130 @@ | |||
6 | 1 | <?xml version="1.0" encoding="UTF-8"?> | 1 | <?xml version="1.0" encoding="UTF-8"?> |
8 | 2 | <!-- Generated with glade 3.20.4 --> | 2 | <!-- Generated with glade 3.22.1 --> |
9 | 3 | <interface> | 3 | <interface> |
10 | 4 | <requires lib="gtk+" version="3.10"/> | 4 | <requires lib="gtk+" version="3.10"/> |
11 | 5 | <object class="GtkListStore" id="liststore_account"> | ||
12 | 6 | <columns> | ||
13 | 7 | <!-- column-name Id --> | ||
14 | 8 | <column type="gchararray"/> | ||
15 | 9 | <!-- column-name Email --> | ||
16 | 10 | <column type="gchararray"/> | ||
17 | 11 | <!-- column-name Account --> | ||
18 | 12 | <column type="GObject"/> | ||
19 | 13 | </columns> | ||
20 | 14 | </object> | ||
21 | 5 | <object class="GtkDialog" id="dialog_auth"> | 15 | <object class="GtkDialog" id="dialog_auth"> |
22 | 6 | <property name="can_focus">False</property> | ||
23 | 7 | <property name="resizable">False</property> | 16 | <property name="resizable">False</property> |
24 | 8 | <property name="modal">True</property> | ||
25 | 9 | <property name="destroy_with_parent">True</property> | ||
26 | 10 | <property name="type_hint">dialog</property> | 17 | <property name="type_hint">dialog</property> |
28 | 11 | <property name="deletable">False</property> | 18 | <child> |
29 | 19 | <placeholder/> | ||
30 | 20 | </child> | ||
31 | 12 | <child internal-child="vbox"> | 21 | <child internal-child="vbox"> |
34 | 13 | <object class="GtkBox" id="box_dialog"> | 22 | <object class="GtkBox"> |
35 | 14 | <property name="can_focus">False</property> | 23 | <property name="border_width">6</property> |
36 | 15 | <property name="orientation">vertical</property> | 24 | <property name="orientation">vertical</property> |
37 | 16 | <property name="spacing">2</property> | 25 | <property name="spacing">2</property> |
38 | 17 | <child internal-child="action_area"> | 26 | <child internal-child="action_area"> |
41 | 18 | <object class="GtkButtonBox" id="dialog-action_area1"> | 27 | <object class="GtkButtonBox"> |
42 | 19 | <property name="can_focus">False</property> | 28 | <child> |
43 | 29 | <object class="GtkButton" id="button_add_another"> | ||
44 | 30 | <property name="label" translatable="yes">Add another…</property> | ||
45 | 31 | <property name="visible">True</property> | ||
46 | 32 | <property name="can_focus">True</property> | ||
47 | 33 | <property name="receives_default">True</property> | ||
48 | 34 | <signal name="clicked" handler="_button_add_another_clicked_cb" swapped="no"/> | ||
49 | 35 | </object> | ||
50 | 36 | <packing> | ||
51 | 37 | <property name="expand">True</property> | ||
52 | 38 | <property name="secondary">True</property> | ||
53 | 39 | <property name="non_homogeneous">True</property> | ||
54 | 40 | </packing> | ||
55 | 41 | </child> | ||
56 | 42 | <child> | ||
57 | 43 | <object class="GtkButton" id="button_cancel"> | ||
58 | 44 | <property name="label" translatable="yes">Cancel</property> | ||
59 | 45 | <property name="visible">True</property> | ||
60 | 46 | <property name="can_focus">True</property> | ||
61 | 47 | <property name="receives_default">True</property> | ||
62 | 48 | <signal name="clicked" handler="_button_cancel_clicked_cb" swapped="no"/> | ||
63 | 49 | </object> | ||
64 | 50 | <packing> | ||
65 | 51 | <property name="expand">True</property> | ||
66 | 52 | <property name="non_homogeneous">True</property> | ||
67 | 53 | </packing> | ||
68 | 54 | </child> | ||
69 | 55 | <child> | ||
70 | 56 | <object class="GtkButton" id="button_continue"> | ||
71 | 57 | <property name="visible">True</property> | ||
72 | 58 | <property name="can_focus">True</property> | ||
73 | 59 | <property name="receives_default">True</property> | ||
74 | 60 | <signal name="clicked" handler="_button_continue_clicked_cb" swapped="no"/> | ||
75 | 61 | </object> | ||
76 | 62 | <packing> | ||
77 | 63 | <property name="expand">True</property> | ||
78 | 64 | <property name="non_homogeneous">True</property> | ||
79 | 65 | </packing> | ||
80 | 66 | </child> | ||
81 | 20 | </object> | 67 | </object> |
82 | 21 | <packing> | 68 | <packing> |
86 | 22 | <property name="expand">False</property> | 69 | <property name="expand">True</property> |
84 | 23 | <property name="fill">False</property> | ||
85 | 24 | <property name="position">0</property> | ||
87 | 25 | </packing> | 70 | </packing> |
88 | 26 | </child> | 71 | </child> |
89 | 27 | <child> | 72 | <child> |
91 | 28 | <object class="GtkGrid" id="main_grid"> | 73 | <object class="GtkBox"> |
92 | 29 | <property name="visible">True</property> | 74 | <property name="visible">True</property> |
94 | 30 | <property name="can_focus">False</property> | 75 | <property name="halign">start</property> |
95 | 31 | <property name="border_width">12</property> | 76 | <property name="border_width">12</property> |
98 | 32 | <property name="row_spacing">12</property> | 77 | <property name="spacing">18</property> |
97 | 33 | <property name="column_spacing">12</property> | ||
99 | 34 | <child> | 78 | <child> |
101 | 35 | <object class="GtkLabel" id="label_title"> | 79 | <object class="GtkImage"> |
102 | 36 | <property name="visible">True</property> | 80 | <property name="visible">True</property> |
108 | 37 | <property name="can_focus">False</property> | 81 | <property name="halign">start</property> |
109 | 38 | <property name="hexpand">True</property> | 82 | <property name="valign">start</property> |
110 | 39 | <property name="label" translatable="yes">To enable Livepatch choose an Ubuntu Single Sign-on account.</property> | 83 | <property name="icon_name">software-properties</property> |
111 | 40 | <property name="wrap">True</property> | 84 | <property name="icon_size">6</property> |
107 | 41 | <property name="xalign">0</property> | ||
112 | 42 | </object> | 85 | </object> |
113 | 43 | <packing> | ||
114 | 44 | <property name="left_attach">0</property> | ||
115 | 45 | <property name="top_attach">0</property> | ||
116 | 46 | </packing> | ||
117 | 47 | </child> | 86 | </child> |
118 | 48 | <child> | 87 | <child> |
120 | 49 | <object class="GtkFrame" id="main_frame"> | 88 | <object class="GtkBox" id="box_auth"> |
121 | 50 | <property name="visible">True</property> | 89 | <property name="visible">True</property> |
124 | 51 | <property name="can_focus">False</property> | 90 | <property name="halign">start</property> |
125 | 52 | <property name="label_xalign">0</property> | 91 | <property name="orientation">vertical</property> |
126 | 92 | <property name="spacing">12</property> | ||
127 | 53 | <child> | 93 | <child> |
129 | 54 | <object class="GtkListBox" id="listbox_accounts"> | 94 | <object class="GtkLabel" id="label_header"> |
130 | 95 | <property name="name">label_header</property> | ||
131 | 55 | <property name="visible">True</property> | 96 | <property name="visible">True</property> |
134 | 56 | <property name="can_focus">False</property> | 97 | <property name="halign">start</property> |
135 | 57 | <property name="selection_mode">none</property> | 98 | <property name="valign">start</property> |
136 | 99 | <property name="use_markup">True</property> | ||
137 | 100 | <property name="justify">fill</property> | ||
138 | 101 | <property name="wrap">True</property> | ||
139 | 102 | <property name="max_width_chars">40</property> | ||
140 | 103 | </object> | ||
141 | 104 | </child> | ||
142 | 105 | <child> | ||
143 | 106 | <object class="GtkComboBox" id="combobox_account"> | ||
144 | 107 | <property name="halign">start</property> | ||
145 | 108 | <property name="model">liststore_account</property> | ||
146 | 58 | <child> | 109 | <child> |
163 | 59 | <object class="GtkListBoxRow" id="listboxrow_new_account"> | 110 | <object class="GtkCellRendererText"/> |
164 | 60 | <property name="visible">True</property> | 111 | <attributes> |
165 | 61 | <property name="can_focus">False</property> | 112 | <attribute name="text">1</attribute> |
166 | 62 | <child> | 113 | </attributes> |
151 | 63 | <object class="GtkLabel" id="label_new_account"> | ||
152 | 64 | <property name="height_request">48</property> | ||
153 | 65 | <property name="visible">True</property> | ||
154 | 66 | <property name="can_focus">False</property> | ||
155 | 67 | <property name="halign">center</property> | ||
156 | 68 | <property name="valign">center</property> | ||
157 | 69 | <property name="label" translatable="yes"><b>Use another account...</b></property> | ||
158 | 70 | <property name="use_markup">True</property> | ||
159 | 71 | <property name="justify">center</property> | ||
160 | 72 | </object> | ||
161 | 73 | </child> | ||
162 | 74 | </object> | ||
167 | 75 | </child> | 114 | </child> |
168 | 76 | </object> | 115 | </object> |
169 | 77 | </child> | 116 | </child> |
170 | 117 | <child> | ||
171 | 118 | <object class="GtkLabel" id="label_account"> | ||
172 | 119 | <property name="halign">start</property> | ||
173 | 120 | </object> | ||
174 | 121 | </child> | ||
175 | 78 | </object> | 122 | </object> |
176 | 79 | <packing> | ||
177 | 80 | <property name="left_attach">0</property> | ||
178 | 81 | <property name="top_attach">1</property> | ||
179 | 82 | <property name="width">2</property> | ||
180 | 83 | </packing> | ||
181 | 84 | </child> | 123 | </child> |
182 | 85 | </object> | 124 | </object> |
183 | 86 | <packing> | ||
184 | 87 | <property name="expand">False</property> | ||
185 | 88 | <property name="fill">True</property> | ||
186 | 89 | <property name="position">1</property> | ||
187 | 90 | </packing> | ||
188 | 91 | </child> | ||
189 | 92 | </object> | ||
190 | 93 | </child> | ||
191 | 94 | <child type="titlebar"> | ||
192 | 95 | <object class="GtkHeaderBar"> | ||
193 | 96 | <property name="visible">True</property> | ||
194 | 97 | <property name="can_focus">False</property> | ||
195 | 98 | <property name="title" translatable="yes">Choose an account</property> | ||
196 | 99 | <child> | ||
197 | 100 | <object class="GtkButton" id="button_cancel"> | ||
198 | 101 | <property name="label">gtk-cancel</property> | ||
199 | 102 | <property name="visible">True</property> | ||
200 | 103 | <property name="can_focus">True</property> | ||
201 | 104 | <property name="receives_default">True</property> | ||
202 | 105 | <property name="use_stock">True</property> | ||
203 | 106 | </object> | ||
204 | 107 | </child> | 125 | </child> |
205 | 108 | </object> | 126 | </object> |
206 | 109 | </child> | 127 | </child> |
207 | 110 | <action-widgets> | ||
208 | 111 | <action-widget response="-6">button_cancel</action-widget> | ||
209 | 112 | </action-widgets> | ||
210 | 113 | </object> | 128 | </object> |
211 | 114 | </interface> | 129 | </interface> |
212 | 130 | |||
213 | diff --git a/data/gtkbuilder/dialog-livepatch-error.ui b/data/gtkbuilder/dialog-livepatch-error.ui | |||
214 | index 9d64306..6ee1f44 100644 | |||
215 | --- a/data/gtkbuilder/dialog-livepatch-error.ui | |||
216 | +++ b/data/gtkbuilder/dialog-livepatch-error.ui | |||
217 | @@ -1,58 +1,139 @@ | |||
218 | 1 | <?xml version="1.0" encoding="UTF-8"?> | 1 | <?xml version="1.0" encoding="UTF-8"?> |
220 | 2 | <!-- Generated with glade 3.18.3 --> | 2 | <!-- Generated with glade 3.22.0 --> |
221 | 3 | <interface> | 3 | <interface> |
222 | 4 | <requires lib="gtk+" version="3.12"/> | 4 | <requires lib="gtk+" version="3.12"/> |
225 | 5 | <object class="GtkMessageDialog" id="messagedialog_livepatch"> | 5 | <object class="GtkTextBuffer" id="textbuffer_message"/> |
226 | 6 | <property name="can_focus">False</property> | 6 | <object class="GtkDialog" id="messagedialog_livepatch"> |
227 | 7 | <property name="title">Livepatch</property> | ||
228 | 8 | <property name="resizable">False</property> | ||
229 | 9 | <property name="modal">True</property> | ||
230 | 7 | <property name="type_hint">dialog</property> | 10 | <property name="type_hint">dialog</property> |
233 | 8 | <property name="message_type">error</property> | 11 | <property name="urgency_hint">True</property> |
234 | 9 | <property name="text" translatable="yes">Sorry, there’s been a problem in setting up Canonical Livepatch.</property> | 12 | <property name="deletable">False</property> |
235 | 13 | <property name="skip_taskbar_hint">True</property> | ||
236 | 14 | <property name="skip_pager_hint">True</property> | ||
237 | 10 | <child internal-child="vbox"> | 15 | <child internal-child="vbox"> |
240 | 11 | <object class="GtkBox" id="messagedialog-vbox1"> | 16 | <object class="GtkBox"> |
239 | 12 | <property name="can_focus">False</property> | ||
241 | 13 | <property name="orientation">vertical</property> | 17 | <property name="orientation">vertical</property> |
242 | 14 | <property name="spacing">2</property> | 18 | <property name="spacing">2</property> |
243 | 15 | <child internal-child="action_area"> | 19 | <child internal-child="action_area"> |
246 | 16 | <object class="GtkButtonBox" id="messagedialog-action_area1"> | 20 | <object class="GtkButtonBox"> |
245 | 17 | <property name="can_focus">False</property> | ||
247 | 18 | <property name="layout_style">end</property> | 21 | <property name="layout_style">end</property> |
248 | 19 | <child> | 22 | <child> |
249 | 20 | <object class="GtkButton" id="button_settings"> | 23 | <object class="GtkButton" id="button_settings"> |
250 | 21 | <property name="label" translatable="yes">Settings…</property> | 24 | <property name="label" translatable="yes">Settings…</property> |
251 | 22 | <property name="visible">True</property> | ||
252 | 23 | <property name="can_focus">True</property> | 25 | <property name="can_focus">True</property> |
254 | 24 | <property name="receives_default">True</property> | 26 | <property name="receives_default">False</property> |
255 | 25 | <signal name="clicked" handler="on_button_settings_clicked" swapped="no"/> | 27 | <signal name="clicked" handler="on_button_settings_clicked" swapped="no"/> |
256 | 26 | </object> | 28 | </object> |
257 | 27 | <packing> | 29 | <packing> |
258 | 28 | <property name="expand">True</property> | 30 | <property name="expand">True</property> |
259 | 29 | <property name="fill">True</property> | 31 | <property name="fill">True</property> |
260 | 30 | <property name="position">0</property> | ||
261 | 31 | </packing> | 32 | </packing> |
262 | 32 | </child> | 33 | </child> |
263 | 33 | <child> | 34 | <child> |
264 | 34 | <object class="GtkButton" id="button_ignore"> | 35 | <object class="GtkButton" id="button_ignore"> |
265 | 35 | <property name="label" translatable="yes">Ignore</property> | 36 | <property name="label" translatable="yes">Ignore</property> |
266 | 36 | <property name="visible">True</property> | 37 | <property name="visible">True</property> |
267 | 38 | <property name="has_focus">True</property> | ||
268 | 37 | <property name="can_focus">True</property> | 39 | <property name="can_focus">True</property> |
269 | 38 | <property name="receives_default">True</property> | 40 | <property name="receives_default">True</property> |
270 | 39 | <property name="yalign">0.51999998092651367</property> | ||
271 | 40 | <signal name="clicked" handler="on_button_ignore_clicked" swapped="no"/> | 41 | <signal name="clicked" handler="on_button_ignore_clicked" swapped="no"/> |
272 | 41 | </object> | 42 | </object> |
273 | 42 | <packing> | 43 | <packing> |
274 | 43 | <property name="expand">True</property> | 44 | <property name="expand">True</property> |
275 | 44 | <property name="fill">True</property> | 45 | <property name="fill">True</property> |
276 | 45 | <property name="position">1</property> | ||
277 | 46 | </packing> | 46 | </packing> |
278 | 47 | </child> | 47 | </child> |
279 | 48 | </object> | 48 | </object> |
280 | 49 | <packing> | 49 | <packing> |
281 | 50 | <property name="expand">False</property> | 50 | <property name="expand">False</property> |
282 | 51 | <property name="fill">False</property> | 51 | <property name="fill">False</property> |
284 | 52 | <property name="position">0</property> | 52 | </packing> |
285 | 53 | </child> | ||
286 | 54 | <child> | ||
287 | 55 | <object class="GtkBox"> | ||
288 | 56 | <property name="visible">True</property> | ||
289 | 57 | <property name="border_width">12</property> | ||
290 | 58 | <property name="orientation">vertical</property> | ||
291 | 59 | <child> | ||
292 | 60 | <object class="GtkGrid"> | ||
293 | 61 | <property name="visible">True</property> | ||
294 | 62 | <property name="row_spacing">12</property> | ||
295 | 63 | <property name="column_spacing">12</property> | ||
296 | 64 | <child> | ||
297 | 65 | <object class="GtkLabel" id="label_primary"> | ||
298 | 66 | <property name="visible">True</property> | ||
299 | 67 | <property name="xalign">0</property> | ||
300 | 68 | </object> | ||
301 | 69 | <packing> | ||
302 | 70 | <property name="left_attach">1</property> | ||
303 | 71 | <property name="top_attach">0</property> | ||
304 | 72 | </packing> | ||
305 | 73 | </child> | ||
306 | 74 | <child> | ||
307 | 75 | <object class="GtkImage"> | ||
308 | 76 | <property name="visible">True</property> | ||
309 | 77 | <property name="halign">center</property> | ||
310 | 78 | <property name="valign">start</property> | ||
311 | 79 | <property name="stock">gtk-dialog-error</property> | ||
312 | 80 | <property name="use_fallback">True</property> | ||
313 | 81 | <property name="icon_size">6</property> | ||
314 | 82 | </object> | ||
315 | 83 | <packing> | ||
316 | 84 | <property name="left_attach">0</property> | ||
317 | 85 | <property name="top_attach">0</property> | ||
318 | 86 | <property name="height">3</property> | ||
319 | 87 | </packing> | ||
320 | 88 | </child> | ||
321 | 89 | <child> | ||
322 | 90 | <object class="GtkLabel"> | ||
323 | 91 | <property name="visible">True</property> | ||
324 | 92 | <property name="label" translatable="yes">The error was:</property> | ||
325 | 93 | <property name="xalign">0</property> | ||
326 | 94 | </object> | ||
327 | 95 | <packing> | ||
328 | 96 | <property name="left_attach">1</property> | ||
329 | 97 | <property name="top_attach">1</property> | ||
330 | 98 | </packing> | ||
331 | 99 | </child> | ||
332 | 100 | <child> | ||
333 | 101 | <object class="GtkTextView" id="treeview_message"> | ||
334 | 102 | <property name="height_request">100</property> | ||
335 | 103 | <property name="visible">True</property> | ||
336 | 104 | <property name="hexpand">True</property> | ||
337 | 105 | <property name="vexpand">True</property> | ||
338 | 106 | <property name="pixels_above_lines">6</property> | ||
339 | 107 | <property name="pixels_below_lines">6</property> | ||
340 | 108 | <property name="editable">False</property> | ||
341 | 109 | <property name="wrap_mode">word</property> | ||
342 | 110 | <property name="left_margin">6</property> | ||
343 | 111 | <property name="right_margin">6</property> | ||
344 | 112 | <property name="cursor_visible">False</property> | ||
345 | 113 | <property name="buffer">textbuffer_message</property> | ||
346 | 114 | <property name="accepts_tab">False</property> | ||
347 | 115 | </object> | ||
348 | 116 | <packing> | ||
349 | 117 | <property name="left_attach">1</property> | ||
350 | 118 | <property name="top_attach">2</property> | ||
351 | 119 | </packing> | ||
352 | 120 | </child> | ||
353 | 121 | </object> | ||
354 | 122 | <packing> | ||
355 | 123 | <property name="expand">True</property> | ||
356 | 124 | <property name="fill">True</property> | ||
357 | 125 | </packing> | ||
358 | 126 | </child> | ||
359 | 127 | </object> | ||
360 | 128 | <packing> | ||
361 | 129 | <property name="expand">True</property> | ||
362 | 130 | <property name="fill">True</property> | ||
363 | 53 | </packing> | 131 | </packing> |
364 | 54 | </child> | 132 | </child> |
365 | 55 | </object> | 133 | </object> |
366 | 56 | </child> | 134 | </child> |
367 | 135 | <child type="titlebar"> | ||
368 | 136 | <placeholder/> | ||
369 | 137 | </child> | ||
370 | 57 | </object> | 138 | </object> |
371 | 58 | </interface> | 139 | </interface> |
372 | diff --git a/data/gtkbuilder/main.ui b/data/gtkbuilder/main.ui | |||
373 | index 146a9ed..557e844 100644 | |||
374 | --- a/data/gtkbuilder/main.ui | |||
375 | +++ b/data/gtkbuilder/main.ui | |||
376 | @@ -1,7 +1,13 @@ | |||
377 | 1 | <?xml version="1.0" encoding="UTF-8"?> | 1 | <?xml version="1.0" encoding="UTF-8"?> |
379 | 2 | <!-- Generated with glade 3.18.3 --> | 2 | <!-- Generated with glade 3.22.0 --> |
380 | 3 | <interface> | 3 | <interface> |
382 | 4 | <requires lib="gtk+" version="3.0"/> | 4 | <requires lib="gtk+" version="3.22"/> |
383 | 5 | <object class="GtkListStore" id="model_livepatch_fixes"> | ||
384 | 6 | <columns> | ||
385 | 7 | <!-- column-name fix --> | ||
386 | 8 | <column type="gchararray"/> | ||
387 | 9 | </columns> | ||
388 | 10 | </object> | ||
389 | 5 | <object class="GtkListStore" id="model_normal_updates_display"> | 11 | <object class="GtkListStore" id="model_normal_updates_display"> |
390 | 6 | <columns> | 12 | <columns> |
391 | 7 | <!-- column-name text --> | 13 | <!-- column-name text --> |
392 | @@ -91,6 +97,7 @@ | |||
393 | 91 | <object class="GtkTextBuffer" id="textbuffer1"> | 97 | <object class="GtkTextBuffer" id="textbuffer1"> |
394 | 92 | <property name="text" translatable="yes">To install from a CD-ROM or DVD, insert the medium into the drive.</property> | 98 | <property name="text" translatable="yes">To install from a CD-ROM or DVD, insert the medium into the drive.</property> |
395 | 93 | </object> | 99 | </object> |
396 | 100 | <object class="GtkTextBuffer" id="textbuffer_livepatch"/> | ||
397 | 94 | <object class="GtkWindow" id="window_main"> | 101 | <object class="GtkWindow" id="window_main"> |
398 | 95 | <property name="can_focus">False</property> | 102 | <property name="can_focus">False</property> |
399 | 96 | <property name="border_width">6</property> | 103 | <property name="border_width">6</property> |
400 | @@ -696,84 +703,6 @@ | |||
401 | 696 | </packing> | 703 | </packing> |
402 | 697 | </child> | 704 | </child> |
403 | 698 | <child> | 705 | <child> |
404 | 699 | <object class="GtkAlignment" id="alignment3"> | ||
405 | 700 | <property name="visible">True</property> | ||
406 | 701 | <property name="can_focus">False</property> | ||
407 | 702 | <property name="left_padding">12</property> | ||
408 | 703 | <child> | ||
409 | 704 | <object class="GtkGrid" id="grid_livepatch"> | ||
410 | 705 | <property name="visible">True</property> | ||
411 | 706 | <property name="can_focus">False</property> | ||
412 | 707 | <property name="halign">center</property> | ||
413 | 708 | <property name="hexpand">False</property> | ||
414 | 709 | <property name="vexpand">False</property> | ||
415 | 710 | <property name="row_spacing">6</property> | ||
416 | 711 | <property name="column_spacing">6</property> | ||
417 | 712 | <child> | ||
418 | 713 | <object class="GtkBox" id="hbox_livepatch"> | ||
419 | 714 | <property name="visible">True</property> | ||
420 | 715 | <property name="can_focus">False</property> | ||
421 | 716 | <property name="halign">center</property> | ||
422 | 717 | <property name="spacing">6</property> | ||
423 | 718 | <child> | ||
424 | 719 | <object class="GtkLabel" id="label_livepatch_login"> | ||
425 | 720 | <property name="visible">True</property> | ||
426 | 721 | <property name="can_focus">False</property> | ||
427 | 722 | </object> | ||
428 | 723 | <packing> | ||
429 | 724 | <property name="expand">False</property> | ||
430 | 725 | <property name="fill">True</property> | ||
431 | 726 | <property name="position">0</property> | ||
432 | 727 | </packing> | ||
433 | 728 | </child> | ||
434 | 729 | <child> | ||
435 | 730 | <object class="GtkButton" id="button_ubuntuone"> | ||
436 | 731 | <property name="visible">True</property> | ||
437 | 732 | <property name="can_focus">True</property> | ||
438 | 733 | <property name="receives_default">True</property> | ||
439 | 734 | <property name="xalign">0</property> | ||
440 | 735 | </object> | ||
441 | 736 | <packing> | ||
442 | 737 | <property name="expand">False</property> | ||
443 | 738 | <property name="fill">True</property> | ||
444 | 739 | <property name="position">2</property> | ||
445 | 740 | </packing> | ||
446 | 741 | </child> | ||
447 | 742 | </object> | ||
448 | 743 | <packing> | ||
449 | 744 | <property name="left_attach">1</property> | ||
450 | 745 | <property name="top_attach">1</property> | ||
451 | 746 | </packing> | ||
452 | 747 | </child> | ||
453 | 748 | <child> | ||
454 | 749 | <object class="GtkCheckButton" id="checkbutton_livepatch"> | ||
455 | 750 | <property name="label" translatable="yes">Use Canonical Livepatch to increase security between restarts</property> | ||
456 | 751 | <property name="use_action_appearance">False</property> | ||
457 | 752 | <property name="visible">True</property> | ||
458 | 753 | <property name="can_focus">True</property> | ||
459 | 754 | <property name="receives_default">False</property> | ||
460 | 755 | <property name="use_underline">True</property> | ||
461 | 756 | <property name="xalign">0</property> | ||
462 | 757 | <property name="draw_indicator">True</property> | ||
463 | 758 | </object> | ||
464 | 759 | <packing> | ||
465 | 760 | <property name="left_attach">1</property> | ||
466 | 761 | <property name="top_attach">0</property> | ||
467 | 762 | </packing> | ||
468 | 763 | </child> | ||
469 | 764 | <child> | ||
470 | 765 | <placeholder/> | ||
471 | 766 | </child> | ||
472 | 767 | </object> | ||
473 | 768 | </child> | ||
474 | 769 | </object> | ||
475 | 770 | <packing> | ||
476 | 771 | <property name="expand">True</property> | ||
477 | 772 | <property name="fill">True</property> | ||
478 | 773 | <property name="position">2</property> | ||
479 | 774 | </packing> | ||
480 | 775 | </child> | ||
481 | 776 | <child> | ||
482 | 777 | <object class="GtkAlignment" id="alignment15"> | 706 | <object class="GtkAlignment" id="alignment15"> |
483 | 778 | <property name="visible">True</property> | 707 | <property name="visible">True</property> |
484 | 779 | <property name="can_focus">False</property> | 708 | <property name="can_focus">False</property> |
485 | @@ -820,7 +749,7 @@ | |||
486 | 820 | <packing> | 749 | <packing> |
487 | 821 | <property name="expand">False</property> | 750 | <property name="expand">False</property> |
488 | 822 | <property name="fill">False</property> | 751 | <property name="fill">False</property> |
490 | 823 | <property name="position">3</property> | 752 | <property name="position">2</property> |
491 | 824 | </packing> | 753 | </packing> |
492 | 825 | </child> | 754 | </child> |
493 | 826 | </object> | 755 | </object> |
494 | @@ -1187,6 +1116,214 @@ | |||
495 | 1187 | <property name="tab_fill">False</property> | 1116 | <property name="tab_fill">False</property> |
496 | 1188 | </packing> | 1117 | </packing> |
497 | 1189 | </child> | 1118 | </child> |
498 | 1119 | <child> | ||
499 | 1120 | <object class="GtkBox" id="vbox_livepatch"> | ||
500 | 1121 | <property name="visible">True</property> | ||
501 | 1122 | <property name="can_focus">False</property> | ||
502 | 1123 | <property name="border_width">12</property> | ||
503 | 1124 | <property name="orientation">vertical</property> | ||
504 | 1125 | <property name="spacing">12</property> | ||
505 | 1126 | <child> | ||
506 | 1127 | <object class="GtkLabel" id="label_livepatch_description"> | ||
507 | 1128 | <property name="visible">True</property> | ||
508 | 1129 | <property name="can_focus">False</property> | ||
509 | 1130 | <property name="label" translatable="yes">Canonical Livepatch helps keep your system secure by applying security updates that don't require a restart. <a href="https://www.ubuntu.com/livepatch">Learn More</a></property> | ||
510 | 1131 | <property name="use_markup">True</property> | ||
511 | 1132 | <property name="wrap">True</property> | ||
512 | 1133 | <property name="max_width_chars">1</property> | ||
513 | 1134 | <property name="xalign">0</property> | ||
514 | 1135 | </object> | ||
515 | 1136 | <packing> | ||
516 | 1137 | <property name="expand">False</property> | ||
517 | 1138 | <property name="fill">True</property> | ||
518 | 1139 | <property name="position">0</property> | ||
519 | 1140 | </packing> | ||
520 | 1141 | </child> | ||
521 | 1142 | <child> | ||
522 | 1143 | <object class="GtkBox" id="hbox_switch"> | ||
523 | 1144 | <property name="visible">True</property> | ||
524 | 1145 | <property name="can_focus">False</property> | ||
525 | 1146 | <property name="spacing">6</property> | ||
526 | 1147 | <child> | ||
527 | 1148 | <object class="GtkSwitch" id="switch_livepatch"> | ||
528 | 1149 | <property name="visible">True</property> | ||
529 | 1150 | <property name="sensitive">False</property> | ||
530 | 1151 | <property name="can_focus">True</property> | ||
531 | 1152 | </object> | ||
532 | 1153 | <packing> | ||
533 | 1154 | <property name="expand">False</property> | ||
534 | 1155 | <property name="fill">True</property> | ||
535 | 1156 | <property name="position">0</property> | ||
536 | 1157 | </packing> | ||
537 | 1158 | </child> | ||
538 | 1159 | <child> | ||
539 | 1160 | <object class="GtkSpinner" id="spinner_livepatch"> | ||
540 | 1161 | <property name="can_focus">False</property> | ||
541 | 1162 | </object> | ||
542 | 1163 | <packing> | ||
543 | 1164 | <property name="expand">False</property> | ||
544 | 1165 | <property name="fill">True</property> | ||
545 | 1166 | <property name="position">1</property> | ||
546 | 1167 | </packing> | ||
547 | 1168 | </child> | ||
548 | 1169 | <child> | ||
549 | 1170 | <object class="GtkLabel" id="label_livepatch_switch"> | ||
550 | 1171 | <property name="visible">True</property> | ||
551 | 1172 | <property name="can_focus">False</property> | ||
552 | 1173 | </object> | ||
553 | 1174 | <packing> | ||
554 | 1175 | <property name="expand">False</property> | ||
555 | 1176 | <property name="fill">True</property> | ||
556 | 1177 | <property name="position">2</property> | ||
557 | 1178 | </packing> | ||
558 | 1179 | </child> | ||
559 | 1180 | <child> | ||
560 | 1181 | <object class="GtkButton" id="button_livepatch_login"> | ||
561 | 1182 | <property name="can_focus">True</property> | ||
562 | 1183 | <property name="receives_default">True</property> | ||
563 | 1184 | </object> | ||
564 | 1185 | <packing> | ||
565 | 1186 | <property name="expand">False</property> | ||
566 | 1187 | <property name="fill">True</property> | ||
567 | 1188 | <property name="pack_type">end</property> | ||
568 | 1189 | <property name="position">3</property> | ||
569 | 1190 | </packing> | ||
570 | 1191 | </child> | ||
571 | 1192 | </object> | ||
572 | 1193 | <packing> | ||
573 | 1194 | <property name="expand">False</property> | ||
574 | 1195 | <property name="fill">True</property> | ||
575 | 1196 | <property name="position">1</property> | ||
576 | 1197 | </packing> | ||
577 | 1198 | </child> | ||
578 | 1199 | <child> | ||
579 | 1200 | <object class="GtkStack" id="stack_livepatch"> | ||
580 | 1201 | <property name="can_focus">False</property> | ||
581 | 1202 | <property name="transition_type">crossfade</property> | ||
582 | 1203 | <property name="interpolate_size">True</property> | ||
583 | 1204 | <child> | ||
584 | 1205 | <object class="GtkScrolledWindow"> | ||
585 | 1206 | <property name="visible">True</property> | ||
586 | 1207 | <property name="can_focus">True</property> | ||
587 | 1208 | <property name="shadow_type">in</property> | ||
588 | 1209 | <child> | ||
589 | 1210 | <object class="GtkTextView" id="textview_livepatch"> | ||
590 | 1211 | <property name="visible">True</property> | ||
591 | 1212 | <property name="can_focus">True</property> | ||
592 | 1213 | <property name="pixels_above_lines">6</property> | ||
593 | 1214 | <property name="editable">False</property> | ||
594 | 1215 | <property name="wrap_mode">word</property> | ||
595 | 1216 | <property name="left_margin">6</property> | ||
596 | 1217 | <property name="right_margin">6</property> | ||
597 | 1218 | <property name="cursor_visible">False</property> | ||
598 | 1219 | <property name="buffer">textbuffer_livepatch</property> | ||
599 | 1220 | <property name="accepts_tab">False</property> | ||
600 | 1221 | </object> | ||
601 | 1222 | </child> | ||
602 | 1223 | </object> | ||
603 | 1224 | <packing> | ||
604 | 1225 | <property name="name">page_livepatch_message</property> | ||
605 | 1226 | </packing> | ||
606 | 1227 | </child> | ||
607 | 1228 | <child> | ||
608 | 1229 | <object class="GtkBox"> | ||
609 | 1230 | <property name="visible">True</property> | ||
610 | 1231 | <property name="can_focus">False</property> | ||
611 | 1232 | <property name="orientation">vertical</property> | ||
612 | 1233 | <property name="spacing">12</property> | ||
613 | 1234 | <child> | ||
614 | 1235 | <object class="GtkLabel" id="label_livepatch_last_update"> | ||
615 | 1236 | <property name="visible">True</property> | ||
616 | 1237 | <property name="can_focus">False</property> | ||
617 | 1238 | <property name="xalign">0</property> | ||
618 | 1239 | </object> | ||
619 | 1240 | <packing> | ||
620 | 1241 | <property name="expand">False</property> | ||
621 | 1242 | <property name="fill">True</property> | ||
622 | 1243 | <property name="position">0</property> | ||
623 | 1244 | </packing> | ||
624 | 1245 | </child> | ||
625 | 1246 | <child> | ||
626 | 1247 | <object class="GtkLabel" id="label_livepatch_header"> | ||
627 | 1248 | <property name="visible">True</property> | ||
628 | 1249 | <property name="can_focus">False</property> | ||
629 | 1250 | <property name="xalign">0</property> | ||
630 | 1251 | </object> | ||
631 | 1252 | <packing> | ||
632 | 1253 | <property name="expand">False</property> | ||
633 | 1254 | <property name="fill">True</property> | ||
634 | 1255 | <property name="position">1</property> | ||
635 | 1256 | </packing> | ||
636 | 1257 | </child> | ||
637 | 1258 | <child> | ||
638 | 1259 | <object class="GtkScrolledWindow" id="scrolledwindow_livepatch_fixes"> | ||
639 | 1260 | <property name="visible">True</property> | ||
640 | 1261 | <property name="can_focus">True</property> | ||
641 | 1262 | <property name="shadow_type">in</property> | ||
642 | 1263 | <child> | ||
643 | 1264 | <object class="GtkTreeView" id="treeview_livepatch"> | ||
644 | 1265 | <property name="visible">True</property> | ||
645 | 1266 | <property name="can_focus">True</property> | ||
646 | 1267 | <property name="model">model_livepatch_fixes</property> | ||
647 | 1268 | <property name="headers_visible">False</property> | ||
648 | 1269 | <property name="enable_search">False</property> | ||
649 | 1270 | <property name="show_expanders">False</property> | ||
650 | 1271 | <child internal-child="selection"> | ||
651 | 1272 | <object class="GtkTreeSelection"/> | ||
652 | 1273 | </child> | ||
653 | 1274 | <child> | ||
654 | 1275 | <object class="GtkTreeViewColumn"> | ||
655 | 1276 | <property name="title" translatable="yes">column</property> | ||
656 | 1277 | <child> | ||
657 | 1278 | <object class="GtkCellRendererText"> | ||
658 | 1279 | <property name="width_chars">100</property> | ||
659 | 1280 | <property name="wrap_mode">word</property> | ||
660 | 1281 | <property name="wrap_width">100</property> | ||
661 | 1282 | </object> | ||
662 | 1283 | <attributes> | ||
663 | 1284 | <attribute name="markup">0</attribute> | ||
664 | 1285 | </attributes> | ||
665 | 1286 | </child> | ||
666 | 1287 | </object> | ||
667 | 1288 | </child> | ||
668 | 1289 | </object> | ||
669 | 1290 | </child> | ||
670 | 1291 | </object> | ||
671 | 1292 | <packing> | ||
672 | 1293 | <property name="expand">True</property> | ||
673 | 1294 | <property name="fill">True</property> | ||
674 | 1295 | <property name="position">2</property> | ||
675 | 1296 | </packing> | ||
676 | 1297 | </child> | ||
677 | 1298 | </object> | ||
678 | 1299 | <packing> | ||
679 | 1300 | <property name="name">page_livepatch_status</property> | ||
680 | 1301 | <property name="position">1</property> | ||
681 | 1302 | </packing> | ||
682 | 1303 | </child> | ||
683 | 1304 | </object> | ||
684 | 1305 | <packing> | ||
685 | 1306 | <property name="expand">True</property> | ||
686 | 1307 | <property name="fill">True</property> | ||
687 | 1308 | <property name="position">2</property> | ||
688 | 1309 | </packing> | ||
689 | 1310 | </child> | ||
690 | 1311 | </object> | ||
691 | 1312 | <packing> | ||
692 | 1313 | <property name="position">6</property> | ||
693 | 1314 | </packing> | ||
694 | 1315 | </child> | ||
695 | 1316 | <child type="tab"> | ||
696 | 1317 | <object class="GtkLabel" id="label_livepatch"> | ||
697 | 1318 | <property name="visible">True</property> | ||
698 | 1319 | <property name="can_focus">False</property> | ||
699 | 1320 | <property name="label">Livepatch</property> | ||
700 | 1321 | </object> | ||
701 | 1322 | <packing> | ||
702 | 1323 | <property name="position">6</property> | ||
703 | 1324 | <property name="tab_fill">False</property> | ||
704 | 1325 | </packing> | ||
705 | 1326 | </child> | ||
706 | 1190 | </object> | 1327 | </object> |
707 | 1191 | <packing> | 1328 | <packing> |
708 | 1192 | <property name="expand">True</property> | 1329 | <property name="expand">True</property> |
709 | @@ -1246,6 +1383,9 @@ | |||
710 | 1246 | </child> | 1383 | </child> |
711 | 1247 | </object> | 1384 | </object> |
712 | 1248 | </child> | 1385 | </child> |
713 | 1386 | <child type="titlebar"> | ||
714 | 1387 | <placeholder/> | ||
715 | 1388 | </child> | ||
716 | 1249 | </object> | 1389 | </object> |
717 | 1250 | <object class="GtkSizeGroup" id="sizegroup1"> | 1390 | <object class="GtkSizeGroup" id="sizegroup1"> |
718 | 1251 | <widgets> | 1391 | <widgets> |
719 | diff --git a/data/icons/16x16/apps/livepatch.svg b/data/icons/16x16/apps/livepatch.svg | |||
720 | 1252 | new file mode 100644 | 1392 | new file mode 100644 |
721 | index 0000000..6d82412 | |||
722 | 1253 | Binary files /dev/null and b/data/icons/16x16/apps/livepatch.svg differ | 1393 | Binary files /dev/null and b/data/icons/16x16/apps/livepatch.svg differ |
723 | diff --git a/data/icons/24x24/apps/livepatch.svg b/data/icons/24x24/apps/livepatch.svg | |||
724 | 1254 | new file mode 100644 | 1394 | new file mode 100644 |
725 | index 0000000..8c13c57 | |||
726 | 1255 | Binary files /dev/null and b/data/icons/24x24/apps/livepatch.svg differ | 1395 | Binary files /dev/null and b/data/icons/24x24/apps/livepatch.svg differ |
727 | diff --git a/data/icons/48x48/apps/livepatch.svg b/data/icons/48x48/apps/livepatch.svg | |||
728 | 1256 | new file mode 100644 | 1396 | new file mode 100644 |
729 | index 0000000..65a3049 | |||
730 | 1257 | Binary files /dev/null and b/data/icons/48x48/apps/livepatch.svg differ | 1397 | Binary files /dev/null and b/data/icons/48x48/apps/livepatch.svg differ |
731 | diff --git a/data/icons/64x64/apps/livepatch.svg b/data/icons/64x64/apps/livepatch.svg | |||
732 | 1258 | new file mode 100644 | 1398 | new file mode 100644 |
733 | index 0000000..46c3dea | |||
734 | 1259 | Binary files /dev/null and b/data/icons/64x64/apps/livepatch.svg differ | 1399 | Binary files /dev/null and b/data/icons/64x64/apps/livepatch.svg differ |
735 | diff --git a/data/icons/scalable/apps/livepatch.svg b/data/icons/scalable/apps/livepatch.svg | |||
736 | 1260 | new file mode 100644 | 1400 | new file mode 100644 |
737 | index 0000000..8bdc8b5 | |||
738 | --- /dev/null | |||
739 | +++ b/data/icons/scalable/apps/livepatch.svg | |||
740 | @@ -0,0 +1 @@ | |||
741 | 1 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="Layer_1" x="0px" y="0px" width="400px" height="400px" viewBox="0 0 400 400" style="enable-background:new 0 0 400 400;" xml:space="preserve"> <style type="text/css"> .st0{fill:#E95420;} </style> <path class="st0" d="M200,0C89.7,0,0,89.7,0,200s89.7,200,200,200s200-89.7,200-200S310.3,0,200,0L200,0z M200,325 c-64.6,0-117.9-49.3-124.4-112.2v-0.1H38.8c-5.1,0-9.6-3-11.5-7.7s-0.9-10.1,2.7-13.6l61.2-61.3c4.9-4.9,12.8-4.9,17.7,0l60.6,60.6 c2.6,2.3,4.3,5.7,4.3,9.5c0,6.9-5.6,12.5-12.5,12.5h-35.2c6,35.4,36.9,62.4,73.9,62.4c41.4,0,75-33.6,75-75c0-41.3-33.6-75-75-75 c-13.8,0-25-11.2-25-25s11.2-25,25-25c68.9,0,125,56.1,125,125C325,268.9,268.9,325,200,325z"/> </svg> | ||
742 | 0 | \ No newline at end of file | 2 | \ No newline at end of file |
743 | diff --git a/data/software-properties-livepatch.desktop.in b/data/software-properties-livepatch.desktop.in | |||
744 | 1 | new file mode 100644 | 3 | new file mode 100644 |
745 | index 0000000..c9f168a | |||
746 | --- /dev/null | |||
747 | +++ b/data/software-properties-livepatch.desktop.in | |||
748 | @@ -0,0 +1,12 @@ | |||
749 | 1 | [Desktop Entry] | ||
750 | 2 | Keywords=Livepatch; | ||
751 | 3 | Exec=/usr/bin/software-properties-gtk --open-tab=6 | ||
752 | 4 | Icon=livepatch | ||
753 | 5 | Terminal=false | ||
754 | 6 | Type=Application | ||
755 | 7 | OnlyShowIn=GNOME; | ||
756 | 8 | Categories=GTK;Settings;HardwareSettings | ||
757 | 9 | X-AppStream-Ignore=true | ||
758 | 10 | Name=Livepatch | ||
759 | 11 | _Comment=Manage Canonical Livepatch | ||
760 | 12 | _Keywords=Security;Update; | ||
761 | diff --git a/debian/changelog b/debian/changelog | |||
762 | index a1449b1..1d7a3e8 100644 | |||
763 | --- a/debian/changelog | |||
764 | +++ b/debian/changelog | |||
765 | @@ -1,3 +1,15 @@ | |||
766 | 1 | software-properties (0.96.24.32.8) UNRELEASED; urgency=medium | ||
767 | 2 | |||
768 | 3 | * debian/control: Update Vcs-*: URLs | ||
769 | 4 | * debian/gbp.conf: Add default configuration | ||
770 | 5 | * Add .keep files to preserve empty directories | ||
771 | 6 | * Backport Livepatch changes from Disco (LP: #1823761): | ||
772 | 7 | - Implement new design for authentication dialog. | ||
773 | 8 | - Add livepatch desktop file and icon. | ||
774 | 9 | - Move Livepatch UI in a diffrent tab. | ||
775 | 10 | |||
776 | 11 | -- Andrea Azzarone <andrea.azzarone@canonical.com> Mon, 08 Apr 2019 16:52:15 +0100 | ||
777 | 12 | |||
778 | 1 | software-properties (0.96.24.32.7) bionic; urgency=medium | 13 | software-properties (0.96.24.32.7) bionic; urgency=medium |
779 | 2 | 14 | ||
780 | 3 | * SoftwarePropertiesGtk.py: when checking a package's depends for DKMS also | 15 | * SoftwarePropertiesGtk.py: when checking a package's depends for DKMS also |
781 | diff --git a/debian/control b/debian/control | |||
782 | index fd8d887..cd54cef 100644 | |||
783 | --- a/debian/control | |||
784 | +++ b/debian/control | |||
785 | @@ -7,13 +7,17 @@ Build-Depends: debhelper (>= 9), | |||
786 | 7 | libxml-parser-perl, | 7 | libxml-parser-perl, |
787 | 8 | intltool, | 8 | intltool, |
788 | 9 | dbus-x11 <!nocheck>, | 9 | dbus-x11 <!nocheck>, |
789 | 10 | gir1.2-gtk-3.0 <!nocheck>, | ||
790 | 10 | gir1.2-snapd-1 <!nocheck>, | 11 | gir1.2-snapd-1 <!nocheck>, |
791 | 11 | lsb-release <!nocheck>, | 12 | lsb-release <!nocheck>, |
792 | 12 | pyflakes3 <!nocheck>, | 13 | pyflakes3 <!nocheck>, |
793 | 13 | python3-apt <!nocheck>, | 14 | python3-apt <!nocheck>, |
794 | 15 | python3-dateutil <!nocheck>, | ||
795 | 14 | python3-dbus <!nocheck>, | 16 | python3-dbus <!nocheck>, |
796 | 17 | python3-distro-info <!nocheck>, | ||
797 | 15 | python3-gi <!nocheck>, | 18 | python3-gi <!nocheck>, |
798 | 16 | python3-mock <!nocheck>, | 19 | python3-mock <!nocheck>, |
799 | 20 | python3-requests-unixsocket <!nocheck>, | ||
800 | 17 | xauth <!nocheck>, | 21 | xauth <!nocheck>, |
801 | 18 | xvfb <!nocheck>, | 22 | xvfb <!nocheck>, |
802 | 19 | python3-all, | 23 | python3-all, |
803 | @@ -22,7 +26,7 @@ Build-Depends: debhelper (>= 9), | |||
804 | 22 | dh-migrations, | 26 | dh-migrations, |
805 | 23 | dh-translations | 27 | dh-translations |
806 | 24 | Standards-Version: 3.9.6 | 28 | Standards-Version: 3.9.6 |
808 | 25 | Vcs-Bzr: http://code.launchpad.net/~ubuntu-core-dev/software-properties/main | 29 | Vcs-Git: https://git.launchpad.net/software-properties -b ubuntu/bionic |
809 | 26 | XS-Testsuite: autopkgtest | 30 | XS-Testsuite: autopkgtest |
810 | 27 | 31 | ||
811 | 28 | Package: python3-software-properties | 32 | Package: python3-software-properties |
812 | @@ -58,10 +62,11 @@ Depends: ${python3:Depends}, ${misc:Depends}, python3, | |||
813 | 58 | python3-gi, | 62 | python3-gi, |
814 | 59 | gir1.2-gtk-3.0, | 63 | gir1.2-gtk-3.0, |
815 | 60 | gir1.2-goa-1.0 (>= 3.27.92-1ubuntu1), | 64 | gir1.2-goa-1.0 (>= 3.27.92-1ubuntu1), |
816 | 61 | gir1.2-secret-1, | ||
817 | 62 | gir1.2-snapd-1, | 65 | gir1.2-snapd-1, |
818 | 63 | python3-aptdaemon.gtk3widgets, | 66 | python3-aptdaemon.gtk3widgets, |
819 | 67 | python3-dateutil, | ||
820 | 64 | python3-distro-info, | 68 | python3-distro-info, |
821 | 69 | python3-requests-unixsocket, | ||
822 | 65 | software-properties-common, | 70 | software-properties-common, |
823 | 66 | ubuntu-drivers-common (>= 1:0.2.75), | 71 | ubuntu-drivers-common (>= 1:0.2.75), |
824 | 67 | python3-gi, | 72 | python3-gi, |
825 | diff --git a/debian/gbp.conf b/debian/gbp.conf | |||
826 | 68 | new file mode 100644 | 73 | new file mode 100644 |
827 | index 0000000..0bb5f1a | |||
828 | --- /dev/null | |||
829 | +++ b/debian/gbp.conf | |||
830 | @@ -0,0 +1,3 @@ | |||
831 | 1 | [DEFAULT] | ||
832 | 2 | debian-branch = ubuntu/bionic | ||
833 | 3 | debian-tag = %(version)s | ||
834 | diff --git a/debian/software-properties-gtk.install b/debian/software-properties-gtk.install | |||
835 | index c71d9d2..cfc4d82 100644 | |||
836 | --- a/debian/software-properties-gtk.install | |||
837 | +++ b/debian/software-properties-gtk.install | |||
838 | @@ -5,6 +5,7 @@ debian/tmp/usr/share/mime/packages | |||
839 | 5 | debian/tmp/usr/share/icons | 5 | debian/tmp/usr/share/icons |
840 | 6 | debian/tmp/usr/share/applications/software-properties-gtk.desktop | 6 | debian/tmp/usr/share/applications/software-properties-gtk.desktop |
841 | 7 | debian/tmp/usr/share/applications/software-properties-drivers.desktop | 7 | debian/tmp/usr/share/applications/software-properties-drivers.desktop |
842 | 8 | debian/tmp/usr/share/applications/software-properties-livepatch.desktop | ||
843 | 8 | debian/tmp/usr/share/metainfo/software-properties-gtk.appdata.xml | 9 | debian/tmp/usr/share/metainfo/software-properties-gtk.appdata.xml |
844 | 9 | debian/tmp/usr/share/glib-2.0/schemas | 10 | debian/tmp/usr/share/glib-2.0/schemas |
845 | 10 | #debian/tmp/usr/share/gnome/help/software-properties | 11 | #debian/tmp/usr/share/gnome/help/software-properties |
846 | diff --git a/po/POTFILES.in b/po/POTFILES.in | |||
847 | index d1917fd..47830e4 100644 | |||
848 | --- a/po/POTFILES.in | |||
849 | +++ b/po/POTFILES.in | |||
850 | @@ -5,6 +5,7 @@ data/software-properties-gtk.desktop.in | |||
851 | 5 | data/software-properties-gtk.appdata.xml.in | 5 | data/software-properties-gtk.appdata.xml.in |
852 | 6 | data/software-properties-kde.desktop.in | 6 | data/software-properties-kde.desktop.in |
853 | 7 | data/software-properties-drivers.desktop.in | 7 | data/software-properties-drivers.desktop.in |
854 | 8 | data/software-properties-livepatch.desktop.in | ||
855 | 8 | software-properties-gtk | 9 | software-properties-gtk |
856 | 9 | software-properties-kde | 10 | software-properties-kde |
857 | 10 | add-apt-repository | 11 | add-apt-repository |
858 | @@ -28,8 +29,12 @@ softwareproperties/gtk/dialogs.py | |||
859 | 28 | softwareproperties/gtk/DialogEdit.py | 29 | softwareproperties/gtk/DialogEdit.py |
860 | 29 | softwareproperties/gtk/DialogAdd.py | 30 | softwareproperties/gtk/DialogAdd.py |
861 | 30 | softwareproperties/gtk/DialogCacheOutdated.py | 31 | softwareproperties/gtk/DialogCacheOutdated.py |
862 | 32 | softwareproperties/gtk/DialogLivepatchError.py | ||
863 | 33 | softwareproperties/gtk/LivepatchPage.py | ||
864 | 31 | softwareproperties/CountryInformation.py | 34 | softwareproperties/CountryInformation.py |
865 | 32 | softwareproperties/AptAuth.py | 35 | softwareproperties/AptAuth.py |
866 | 36 | softwareproperties/LivepatchService.py | ||
867 | 37 | softwareproperties/LivepatchSnap.py | ||
868 | 33 | [type: gettext/glade]data/designer/dialog_mirror.ui | 38 | [type: gettext/glade]data/designer/dialog_mirror.ui |
869 | 34 | [type: gettext/glade]data/designer/dialog_edit.ui | 39 | [type: gettext/glade]data/designer/dialog_edit.ui |
870 | 35 | [type: gettext/glade]data/designer/main.ui | 40 | [type: gettext/glade]data/designer/main.ui |
871 | @@ -41,3 +46,4 @@ softwareproperties/AptAuth.py | |||
872 | 41 | [type: gettext/glade]data/gtkbuilder/dialog-mirror.ui | 46 | [type: gettext/glade]data/gtkbuilder/dialog-mirror.ui |
873 | 42 | [type: gettext/glade]data/gtkbuilder/dialog-add.ui | 47 | [type: gettext/glade]data/gtkbuilder/dialog-add.ui |
874 | 43 | [type: gettext/glade]data/gtkbuilder/dialog-auth.ui | 48 | [type: gettext/glade]data/gtkbuilder/dialog-auth.ui |
875 | 49 | [type: gettext/glade]data/gtkbuilder/dialog-livepatch-error.ui | ||
876 | diff --git a/setup.cfg b/setup.cfg | |||
877 | index ef8c2a0..9b28335 100644 | |||
878 | --- a/setup.cfg | |||
879 | +++ b/setup.cfg | |||
880 | @@ -4,6 +4,7 @@ domain=software-properties | |||
881 | 4 | desktop_files=[("share/applications", | 4 | desktop_files=[("share/applications", |
882 | 5 | ("data/software-properties-gtk.desktop.in", | 5 | ("data/software-properties-gtk.desktop.in", |
883 | 6 | "data/software-properties-drivers.desktop.in", | 6 | "data/software-properties-drivers.desktop.in", |
884 | 7 | "data/software-properties-livepatch.desktop.in", | ||
885 | 7 | "data/software-properties-kde.desktop.in",), | 8 | "data/software-properties-kde.desktop.in",), |
886 | 8 | ) | 9 | ) |
887 | 9 | ] | 10 | ] |
888 | diff --git a/softwareproperties/GoaAuth.py b/softwareproperties/GoaAuth.py | |||
889 | index 6583a4a..eb45bf7 100644 | |||
890 | --- a/softwareproperties/GoaAuth.py | |||
891 | +++ b/softwareproperties/GoaAuth.py | |||
892 | @@ -21,7 +21,9 @@ | |||
893 | 21 | 21 | ||
894 | 22 | import gi | 22 | import gi |
895 | 23 | gi.require_version('Goa', '1.0') | 23 | gi.require_version('Goa', '1.0') |
897 | 24 | from gi.repository import Gio, Goa, GObject | 24 | from gi.repository import Gio, GLib, Goa, GObject |
898 | 25 | |||
899 | 26 | import logging | ||
900 | 25 | 27 | ||
901 | 26 | class GoaAuth(GObject.GObject): | 28 | class GoaAuth(GObject.GObject): |
902 | 27 | 29 | ||
903 | @@ -32,12 +34,21 @@ class GoaAuth(GObject.GObject): | |||
904 | 32 | def __init__(self): | 34 | def __init__(self): |
905 | 33 | GObject.GObject.__init__(self) | 35 | GObject.GObject.__init__(self) |
906 | 34 | 36 | ||
907 | 35 | self.goa_client = Goa.Client.new_sync(None) | ||
908 | 36 | self.account = None | 37 | self.account = None |
909 | 38 | self.cancellable = Gio.Cancellable() | ||
910 | 39 | Goa.Client.new(self.cancellable, self._on_goa_client_ready) | ||
911 | 37 | 40 | ||
912 | 38 | self.settings = Gio.Settings.new('com.ubuntu.SoftwareProperties') | 41 | self.settings = Gio.Settings.new('com.ubuntu.SoftwareProperties') |
913 | 39 | self.settings.connect('changed::goa-account-id', self._on_settings_changed) | 42 | self.settings.connect('changed::goa-account-id', self._on_settings_changed) |
915 | 40 | self._load() | 43 | |
916 | 44 | def _on_goa_client_ready(self, source, res): | ||
917 | 45 | try: | ||
918 | 46 | self.goa_client = Goa.Client.new_finish(res) | ||
919 | 47 | except GLib.Error as e: | ||
920 | 48 | logging.error('Failed to get a Gnome Online Account: {}'.format(e.message)) | ||
921 | 49 | self.goa_client = None | ||
922 | 50 | else: | ||
923 | 51 | self._load() | ||
924 | 41 | 52 | ||
925 | 42 | def login(self, account): | 53 | def login(self, account): |
926 | 43 | assert(account) | 54 | assert(account) |
927 | @@ -50,7 +61,7 @@ class GoaAuth(GObject.GObject): | |||
928 | 50 | 61 | ||
929 | 51 | @GObject.Property | 62 | @GObject.Property |
930 | 52 | def token(self): | 63 | def token(self): |
932 | 53 | if self.account is None: | 64 | if self.account is None or self.goa_client is None: |
933 | 54 | return None | 65 | return None |
934 | 55 | 66 | ||
935 | 56 | obj = self.goa_client.lookup_by_id(self.account.props.id) | 67 | obj = self.goa_client.lookup_by_id(self.account.props.id) |
936 | @@ -64,7 +75,7 @@ class GoaAuth(GObject.GObject): | |||
937 | 64 | return pbased.call_get_password_sync('livepatch') | 75 | return pbased.call_get_password_sync('livepatch') |
938 | 65 | 76 | ||
939 | 66 | def _update_state_from_account_id(self, account_id): | 77 | def _update_state_from_account_id(self, account_id): |
941 | 67 | if account_id: | 78 | if account_id and self.goa_client is not None: |
942 | 68 | # Make sure the account-id is valid | 79 | # Make sure the account-id is valid |
943 | 69 | obj = self.goa_client.lookup_by_id(account_id) | 80 | obj = self.goa_client.lookup_by_id(account_id) |
944 | 70 | if obj is None: | 81 | if obj is None: |
945 | diff --git a/softwareproperties/LivepatchService.py b/softwareproperties/LivepatchService.py | |||
946 | 71 | new file mode 100644 | 82 | new file mode 100644 |
947 | index 0000000..694e64c | |||
948 | --- /dev/null | |||
949 | +++ b/softwareproperties/LivepatchService.py | |||
950 | @@ -0,0 +1,254 @@ | |||
951 | 1 | # | ||
952 | 2 | # Copyright (c) 2019 Canonical | ||
953 | 3 | # | ||
954 | 4 | # Authors: | ||
955 | 5 | # Andrea Azzarone <andrea.azzarone@canonical.com> | ||
956 | 6 | # | ||
957 | 7 | # This program is free software; you can redistribute it and/or | ||
958 | 8 | # modify it under the terms of the GNU General Public License as | ||
959 | 9 | # published by the Free Software Foundation; either version 2 of the | ||
960 | 10 | # License, or (at your option) any later version. | ||
961 | 11 | # | ||
962 | 12 | # This program is distributed in the hope that it will be useful, | ||
963 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
964 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
965 | 15 | # GNU General Public License for more details. | ||
966 | 16 | # | ||
967 | 17 | # You should have received a copy of the GNU General Public License | ||
968 | 18 | # along with this program; if not, write to the Free Software | ||
969 | 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | ||
970 | 20 | # USA | ||
971 | 21 | |||
972 | 22 | from gettext import gettext as _ | ||
973 | 23 | import logging | ||
974 | 24 | |||
975 | 25 | import gi | ||
976 | 26 | from gi.repository import Gio, GLib, GObject | ||
977 | 27 | |||
978 | 28 | try: | ||
979 | 29 | import dateutil.parser | ||
980 | 30 | import requests_unixsocket | ||
981 | 31 | |||
982 | 32 | gi.require_version('Snapd', '1') | ||
983 | 33 | from gi.repository import Snapd | ||
984 | 34 | except(ImportError, ValueError): | ||
985 | 35 | pass | ||
986 | 36 | |||
987 | 37 | from softwareproperties.gtk.utils import ( | ||
988 | 38 | has_gnome_online_accounts, | ||
989 | 39 | is_current_distro_lts, | ||
990 | 40 | is_current_distro_supported, | ||
991 | 41 | retry | ||
992 | 42 | ) | ||
993 | 43 | |||
994 | 44 | from softwareproperties.LivepatchSnap import LivepatchSnap | ||
995 | 45 | |||
996 | 46 | |||
997 | 47 | def datetime_parser(json_dict): | ||
998 | 48 | for (key, value) in json_dict.items(): | ||
999 | 49 | try: | ||
1000 | 50 | json_dict[key] = dateutil.parser.parse(value) | ||
1001 | 51 | except (ValueError, TypeError): | ||
1002 | 52 | pass | ||
1003 | 53 | return json_dict | ||
1004 | 54 | |||
1005 | 55 | class LivepatchAvailability: | ||
1006 | 56 | FALSE = 0 | ||
1007 | 57 | TRUE = 1 | ||
1008 | 58 | NO_CONNECTIVITY=3 | ||
1009 | 59 | CHECKING = 2 | ||
1010 | 60 | |||
1011 | 61 | |||
1012 | 62 | class LivepatchService(GObject.GObject): | ||
1013 | 63 | |||
1014 | 64 | # Constants | ||
1015 | 65 | STATUS_ENDPOINT = 'http+unix://%2Fvar%2Fsnap%2Fcanonical-livepatch%2Fcurrent%2Flivepatchd.sock/status' | ||
1016 | 66 | ENABLE_ENDPOINT = 'http+unix://%2Fvar%2Fsnap%2Fcanonical-livepatch%2Fcurrent%2Flivepatchd-priv.sock/enable' | ||
1017 | 67 | DISABLE_ENDPOINT = 'http+unix://%2Fvar%2Fsnap%2Fcanonical-livepatch%2Fcurrent%2Flivepatchd-priv.sock/disable' | ||
1018 | 68 | LIVEPATCH_RUNNING_FILE = '/var/snap/canonical-livepatch/common/machine-token' | ||
1019 | 69 | |||
1020 | 70 | ENABLE_ERROR_MSG = _('Failed to enable Livepatch: {}') | ||
1021 | 71 | DISABLE_ERROR_MSG = _('Failed to disable Livepatch: {}') | ||
1022 | 72 | |||
1023 | 73 | # GObject.GObject | ||
1024 | 74 | __gproperties__ = { | ||
1025 | 75 | 'availability': ( | ||
1026 | 76 | int, None, None, | ||
1027 | 77 | LivepatchAvailability.FALSE, | ||
1028 | 78 | LivepatchAvailability.CHECKING, | ||
1029 | 79 | LivepatchAvailability.FALSE, | ||
1030 | 80 | GObject.PARAM_READABLE), | ||
1031 | 81 | 'availability-message': ( | ||
1032 | 82 | str, None, None, None, GObject.PARAM_READABLE), | ||
1033 | 83 | 'enabled': ( | ||
1034 | 84 | bool, None, None, False, GObject.PARAM_READABLE), | ||
1035 | 85 | } | ||
1036 | 86 | |||
1037 | 87 | def __init__(self): | ||
1038 | 88 | GObject.GObject.__init__(self) | ||
1039 | 89 | |||
1040 | 90 | self._timeout_id = 0 | ||
1041 | 91 | |||
1042 | 92 | self._snap = LivepatchSnap() | ||
1043 | 93 | self._session = requests_unixsocket.Session() | ||
1044 | 94 | |||
1045 | 95 | # Init Properties | ||
1046 | 96 | self._availability = LivepatchAvailability.FALSE | ||
1047 | 97 | self._availability_message = None | ||
1048 | 98 | lp_file = Gio.File.new_for_path(path=self.LIVEPATCH_RUNNING_FILE) | ||
1049 | 99 | self._enabled = lp_file.query_exists() | ||
1050 | 100 | |||
1051 | 101 | # Monitor connectivity status | ||
1052 | 102 | self._nm = Gio.NetworkMonitor.get_default() | ||
1053 | 103 | self._nm.connect('notify::connectivity', self._network_changed_cb) | ||
1054 | 104 | |||
1055 | 105 | # Monitor status of canonical-livepatch | ||
1056 | 106 | self._lp_monitor = lp_file.monitor_file(Gio.FileMonitorFlags.NONE) | ||
1057 | 107 | self._lp_monitor.connect('changed', self._livepatch_enabled_changed_cb) | ||
1058 | 108 | |||
1059 | 109 | def do_get_property(self, pspec): | ||
1060 | 110 | if pspec.name == 'availability': | ||
1061 | 111 | return self._availability | ||
1062 | 112 | elif pspec.name == 'availability-message': | ||
1063 | 113 | return self._availability_message | ||
1064 | 114 | elif pspec.name == 'enabled': | ||
1065 | 115 | return self._enabled | ||
1066 | 116 | else: | ||
1067 | 117 | raise AssertionError | ||
1068 | 118 | |||
1069 | 119 | # Public API | ||
1070 | 120 | def trigger_availability_check(self): | ||
1071 | 121 | """Trigger a Livepatch availability check to be executed after a short | ||
1072 | 122 | timeout. Multiple triggers will result in a single request. | ||
1073 | 123 | |||
1074 | 124 | A notify::availability will be emitted when the check starts, and | ||
1075 | 125 | another one when the check ends. | ||
1076 | 126 | """ | ||
1077 | 127 | def _update_availability(): | ||
1078 | 128 | # each rule is a tuple of two elements, a callable and a string. The | ||
1079 | 129 | # string rapresents the error message that needs to be shown if the | ||
1080 | 130 | # callable returns false. | ||
1081 | 131 | rules = [ | ||
1082 | 132 | (lambda: self._snap.get_status() != Snapd.SnapStatus.UNKNOWN, | ||
1083 | 133 | _('Canonical Livepatch snap is not available.')), | ||
1084 | 134 | (has_gnome_online_accounts, | ||
1085 | 135 | _('Gnome Online Accounts is required to enable Livepatch.')), | ||
1086 | 136 | (is_current_distro_lts, | ||
1087 | 137 | _('Livepatch is not available for this release.')), | ||
1088 | 138 | (is_current_distro_supported, | ||
1089 | 139 | _('The current release is no longer supported.'))] | ||
1090 | 140 | |||
1091 | 141 | if self._nm.props.connectivity != Gio.NetworkConnectivity.FULL: | ||
1092 | 142 | self._availability = LivepatchAvailability.NO_CONNECTIVITY | ||
1093 | 143 | self._availability_message = None | ||
1094 | 144 | else: | ||
1095 | 145 | for func, message in rules: | ||
1096 | 146 | if not func(): | ||
1097 | 147 | self._availability = LivepatchAvailability.FALSE | ||
1098 | 148 | self._availability_message = message | ||
1099 | 149 | break | ||
1100 | 150 | else: | ||
1101 | 151 | self._availability = LivepatchAvailability.TRUE | ||
1102 | 152 | self._availability_message = None | ||
1103 | 153 | |||
1104 | 154 | self.notify('availability') | ||
1105 | 155 | self.notify('availability-message') | ||
1106 | 156 | |||
1107 | 157 | self._timeout_id = 0 | ||
1108 | 158 | return False | ||
1109 | 159 | |||
1110 | 160 | self._availability = LivepatchAvailability.CHECKING | ||
1111 | 161 | self._availability_message = None | ||
1112 | 162 | self.notify('availability') | ||
1113 | 163 | self.notify('availability-message') | ||
1114 | 164 | |||
1115 | 165 | if self._timeout_id == 0: | ||
1116 | 166 | self._timeout_id = GLib.timeout_add_seconds(3, _update_availability) | ||
1117 | 167 | |||
1118 | 168 | def set_enabled(self, enabled, token): | ||
1119 | 169 | """Enable or disable Canonical Livepatch in the current system. This | ||
1120 | 170 | function will return once the operation succeeded or failed. | ||
1121 | 171 | |||
1122 | 172 | Args: | ||
1123 | 173 | enabled(bool): wheater to enable or disable the service. | ||
1124 | 174 | token(str): the authentication token to be used to enable Canonical | ||
1125 | 175 | Livepatch service. | ||
1126 | 176 | |||
1127 | 177 | Returns: | ||
1128 | 178 | (False, '') if successful, (True, error_message) otherwise. | ||
1129 | 179 | """ | ||
1130 | 180 | if self._enabled == enabled: | ||
1131 | 181 | return False, '' | ||
1132 | 182 | |||
1133 | 183 | if not enabled: | ||
1134 | 184 | return self._disable_service() | ||
1135 | 185 | elif self._snap.get_status() == Snapd.SnapStatus.ACTIVE: | ||
1136 | 186 | return self._enable_service(token) | ||
1137 | 187 | else: | ||
1138 | 188 | success, msg = self._snap.enable_or_install() | ||
1139 | 189 | return self._enable_service(token) if success else (True, msg) | ||
1140 | 190 | |||
1141 | 191 | def get_status(self): | ||
1142 | 192 | """Synchronously retrieve the status of Canonical Livepatch. | ||
1143 | 193 | |||
1144 | 194 | Returns: | ||
1145 | 195 | str: The status. A valid string for success, None otherwise. | ||
1146 | 196 | """ | ||
1147 | 197 | try: | ||
1148 | 198 | params = {'verbosity': 3, 'format': 'json'} | ||
1149 | 199 | r = self._session.get(self.STATUS_ENDPOINT, params=params) | ||
1150 | 200 | return r.json(object_hook=datetime_parser) | ||
1151 | 201 | except Exception as e: | ||
1152 | 202 | logging.debug('Failed to get Livepatch status: {}'.format(str(e))) | ||
1153 | 203 | return None | ||
1154 | 204 | |||
1155 | 205 | # Private methods | ||
1156 | 206 | def _enable_service(self, token): | ||
1157 | 207 | """Enable Canonical Livepatch in the current system. This function will | ||
1158 | 208 | return once the operation succeeded or failed. | ||
1159 | 209 | |||
1160 | 210 | Args: | ||
1161 | 211 | token(str): the authentication token to be used to enable Canonical | ||
1162 | 212 | Livepatch service. | ||
1163 | 213 | |||
1164 | 214 | Returns: | ||
1165 | 215 | (False, '') if successful, (True, error_message) otherwise. | ||
1166 | 216 | """ | ||
1167 | 217 | try: | ||
1168 | 218 | return self._enable_service_with_retry(token) | ||
1169 | 219 | except Exception as e: | ||
1170 | 220 | return True, self.ENABLE_ERROR_MSG.format(str(e)) | ||
1171 | 221 | |||
1172 | 222 | @retry(Exception) | ||
1173 | 223 | def _enable_service_with_retry(self, token): | ||
1174 | 224 | params = {'auth-token': token} | ||
1175 | 225 | r = self._session.put(self.ENABLE_ENDPOINT, params=params) | ||
1176 | 226 | return not r.ok, '' if r.ok else self.ENABLE_ERROR_MSG.format(r.text) | ||
1177 | 227 | |||
1178 | 228 | def _disable_service(self): | ||
1179 | 229 | """Disable Canonical Livepatch in the current system. This function will | ||
1180 | 230 | return once the operation succeeded or failed. | ||
1181 | 231 | |||
1182 | 232 | Returns: | ||
1183 | 233 | (False, '') if successful, (True, error_message) otherwise. | ||
1184 | 234 | """ | ||
1185 | 235 | try: | ||
1186 | 236 | return self._disable_service_with_retry() | ||
1187 | 237 | except Exception as e: | ||
1188 | 238 | return True, self.DISABLE_ERROR_MSG.format(str(e)) | ||
1189 | 239 | |||
1190 | 240 | |||
1191 | 241 | @retry(Exception) | ||
1192 | 242 | def _disable_service_with_retry(self): | ||
1193 | 243 | r = self._session.put(self.DISABLE_ENDPOINT) | ||
1194 | 244 | return not r.ok, '' if r.ok else self.DISABLE_ERROR_MSG.format(r.text) | ||
1195 | 245 | |||
1196 | 246 | # Signals handlers | ||
1197 | 247 | def _network_changed_cb(self, monitor, network_available): | ||
1198 | 248 | self.trigger_availability_check() | ||
1199 | 249 | |||
1200 | 250 | def _livepatch_enabled_changed_cb(self, fm, file, other_file, event_type): | ||
1201 | 251 | enabled = file.query_exists() | ||
1202 | 252 | if self._enabled != enabled: | ||
1203 | 253 | self._enabled = enabled | ||
1204 | 254 | self.notify('enabled') | ||
1205 | diff --git a/softwareproperties/LivepatchSnap.py b/softwareproperties/LivepatchSnap.py | |||
1206 | 0 | new file mode 100644 | 255 | new file mode 100644 |
1207 | index 0000000..32b76be | |||
1208 | --- /dev/null | |||
1209 | +++ b/softwareproperties/LivepatchSnap.py | |||
1210 | @@ -0,0 +1,135 @@ | |||
1211 | 1 | # | ||
1212 | 2 | # Copyright (c) 2019 Canonical | ||
1213 | 3 | # | ||
1214 | 4 | # Authors: | ||
1215 | 5 | # Andrea Azzarone <andrea.azzarone@canonical.com> | ||
1216 | 6 | # | ||
1217 | 7 | # This program is free software; you can redistribute it and/or | ||
1218 | 8 | # modify it under the terms of the GNU General Public License as | ||
1219 | 9 | # published by the Free Software Foundation; either version 2 of the | ||
1220 | 10 | # License, or (at your option) any later version. | ||
1221 | 11 | # | ||
1222 | 12 | # This program is distributed in the hope that it will be useful, | ||
1223 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1224 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1225 | 15 | # GNU General Public License for more details. | ||
1226 | 16 | # | ||
1227 | 17 | # You should have received a copy of the GNU General Public License | ||
1228 | 18 | # along with this program; if not, write to the Free Software | ||
1229 | 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | ||
1230 | 20 | # USA | ||
1231 | 21 | |||
1232 | 22 | from gettext import gettext as _ | ||
1233 | 23 | import logging | ||
1234 | 24 | |||
1235 | 25 | import gi | ||
1236 | 26 | from gi.repository import Gio, GLib | ||
1237 | 27 | |||
1238 | 28 | try: | ||
1239 | 29 | gi.require_version('Snapd', '1') | ||
1240 | 30 | from gi.repository import Snapd | ||
1241 | 31 | except(ImportError, ValueError): | ||
1242 | 32 | pass | ||
1243 | 33 | |||
1244 | 34 | |||
1245 | 35 | class LivepatchSnap(object): | ||
1246 | 36 | |||
1247 | 37 | # Constants | ||
1248 | 38 | SNAP_NAME = 'canonical-livepatch' | ||
1249 | 39 | |||
1250 | 40 | # Public API | ||
1251 | 41 | def __init__(self): | ||
1252 | 42 | self._snapd_client = Snapd.Client() | ||
1253 | 43 | self._cancellable = Gio.Cancellable() | ||
1254 | 44 | |||
1255 | 45 | def get_status(self): | ||
1256 | 46 | """ Get the status of canonical-livepatch snap. | ||
1257 | 47 | |||
1258 | 48 | Returns: | ||
1259 | 49 | Snapd.SnapStatus.Enun: An enum indicating the status of the snap. | ||
1260 | 50 | """ | ||
1261 | 51 | snap = self._get_raw_snap() | ||
1262 | 52 | return snap.get_status() if snap else Snapd.SnapStatus.UNKNOWN | ||
1263 | 53 | |||
1264 | 54 | def enable_or_install(self): | ||
1265 | 55 | """Enable or install canonical-livepatch snap. | ||
1266 | 56 | |||
1267 | 57 | Returns: | ||
1268 | 58 | (True, '') if successful, (False, error_message) otherwise. | ||
1269 | 59 | """ | ||
1270 | 60 | status = self.get_status() | ||
1271 | 61 | |||
1272 | 62 | if status == Snapd.SnapStatus.ACTIVE: | ||
1273 | 63 | logging.warning('{} snap is already active'.format(self.SNAP_NAME)) | ||
1274 | 64 | return True, '' | ||
1275 | 65 | elif status == Snapd.SnapStatus.AVAILABLE: | ||
1276 | 66 | return self._install() | ||
1277 | 67 | elif status == Snapd.SnapStatus.INSTALLED: | ||
1278 | 68 | return self._enable() | ||
1279 | 69 | else: | ||
1280 | 70 | logging.warning('{} snap is in an unknown state'.format(self.SNAP_NAME)) | ||
1281 | 71 | return False, _('Canonical Livepatch snap cannot be installed.') | ||
1282 | 72 | |||
1283 | 73 | # Private methods | ||
1284 | 74 | def _get_raw_snap(self): | ||
1285 | 75 | """Get the Sanpd.Snap raw object of the canonical-livepatch snapd. | ||
1286 | 76 | |||
1287 | 77 | Returns: | ||
1288 | 78 | Sanpd.Snap if successful, None otherwise. | ||
1289 | 79 | """ | ||
1290 | 80 | try: | ||
1291 | 81 | snap = self._snapd_client.get_snap_sync( | ||
1292 | 82 | name=self.SNAP_NAME, | ||
1293 | 83 | cancellable=self._cancellable) | ||
1294 | 84 | except GLib.Error as e: | ||
1295 | 85 | logging.debug('Snapd.Client.get_snap_sync failed: {}'.format(e.message)) | ||
1296 | 86 | snap = None | ||
1297 | 87 | |||
1298 | 88 | if snap: | ||
1299 | 89 | return snap | ||
1300 | 90 | |||
1301 | 91 | try: | ||
1302 | 92 | (snaps, ignored) = self._snapd_client.find_sync( | ||
1303 | 93 | flags=Snapd.FindFlags.MATCH_NAME, | ||
1304 | 94 | query=self.SNAP_NAME, | ||
1305 | 95 | cancellable=self._cancellable) | ||
1306 | 96 | snap = snaps[0] | ||
1307 | 97 | except GLib.Error as e: | ||
1308 | 98 | logging.debug('Snapd.Client.find_sync failed: {}'.format(e.message)) | ||
1309 | 99 | |||
1310 | 100 | return snap | ||
1311 | 101 | |||
1312 | 102 | def _install(self): | ||
1313 | 103 | """Install canonical-livepatch snap. | ||
1314 | 104 | |||
1315 | 105 | Returns: | ||
1316 | 106 | (True, '') if successful, (False, error_message) otherwise. | ||
1317 | 107 | """ | ||
1318 | 108 | assert self.get_status() == Snapd.SnapStatus.AVAILABLE | ||
1319 | 109 | |||
1320 | 110 | try: | ||
1321 | 111 | self._snapd_client.install2_sync( | ||
1322 | 112 | flags=Snapd.InstallFlags.NONE, | ||
1323 | 113 | name=self.SNAP_NAME, | ||
1324 | 114 | cancellable=self._cancellable) | ||
1325 | 115 | except GLib.Error as e: | ||
1326 | 116 | return False, _('Canonical Livepatch snap cannot be installed: {}'.format(e.message)) | ||
1327 | 117 | else: | ||
1328 | 118 | return True, '' | ||
1329 | 119 | |||
1330 | 120 | def _enable(self): | ||
1331 | 121 | """Enable the canonical-livepatch snap. | ||
1332 | 122 | |||
1333 | 123 | Returns: | ||
1334 | 124 | (True, '') if successful, (False, error_message) otherwise. | ||
1335 | 125 | """ | ||
1336 | 126 | assert self.get_status() == Snapd.SnapStatus.INSTALLED | ||
1337 | 127 | |||
1338 | 128 | try: | ||
1339 | 129 | self._snapd_client.enable_sync( | ||
1340 | 130 | name=self.SNAP_NAME, | ||
1341 | 131 | cancellable=self._cancellable) | ||
1342 | 132 | except GLib.Error as e: | ||
1343 | 133 | return False, _('Canonical Livepatch snap cannot be enabled: {}'.format(e.message)) | ||
1344 | 134 | else: | ||
1345 | 135 | return True, '' | ||
1346 | diff --git a/softwareproperties/SoftwareProperties.py b/softwareproperties/SoftwareProperties.py | |||
1347 | index 20d20e9..46ed68c 100644 | |||
1348 | --- a/softwareproperties/SoftwareProperties.py | |||
1349 | +++ b/softwareproperties/SoftwareProperties.py | |||
1350 | @@ -32,7 +32,6 @@ import re | |||
1351 | 32 | import os | 32 | import os |
1352 | 33 | import glob | 33 | import glob |
1353 | 34 | import shutil | 34 | import shutil |
1354 | 35 | import subprocess | ||
1355 | 36 | import threading | 35 | import threading |
1356 | 37 | import atexit | 36 | import atexit |
1357 | 38 | import tempfile | 37 | import tempfile |
1358 | @@ -65,15 +64,8 @@ from . import shortcuts | |||
1359 | 65 | from . import ppa | 64 | from . import ppa |
1360 | 66 | from . import cloudarchive | 65 | from . import cloudarchive |
1361 | 67 | 66 | ||
1362 | 68 | import gi | ||
1363 | 69 | from gi.repository import Gio | 67 | from gi.repository import Gio |
1364 | 70 | 68 | ||
1365 | 71 | try: | ||
1366 | 72 | gi.require_version('Snapd', '1') | ||
1367 | 73 | from gi.repository import Snapd | ||
1368 | 74 | except (ImportError, ValueError): | ||
1369 | 75 | pass | ||
1370 | 76 | |||
1371 | 77 | _SHORTCUT_FACTORIES = [ | 69 | _SHORTCUT_FACTORIES = [ |
1372 | 78 | ppa.shortcut_handler, | 70 | ppa.shortcut_handler, |
1373 | 79 | cloudarchive.shortcut_handler, | 71 | cloudarchive.shortcut_handler, |
1374 | @@ -100,9 +92,6 @@ class SoftwareProperties(object): | |||
1375 | 100 | RELEASE_UPGRADES_NEVER : 'never', | 92 | RELEASE_UPGRADES_NEVER : 'never', |
1376 | 101 | } | 93 | } |
1377 | 102 | 94 | ||
1378 | 103 | # file to monitor canonical-livepatch status | ||
1379 | 104 | LIVEPATCH_RUNNING_FILE = '/var/snap/canonical-livepatch/common/machine-token' | ||
1380 | 105 | |||
1381 | 106 | def __init__(self, datadir=None, options=None, rootdir="/"): | 95 | def __init__(self, datadir=None, options=None, rootdir="/"): |
1382 | 107 | """ Provides the core functionality to configure the used software | 96 | """ Provides the core functionality to configure the used software |
1383 | 108 | repositories, the corresponding authentication keys and | 97 | repositories, the corresponding authentication keys and |
1384 | @@ -874,147 +863,6 @@ class SoftwareProperties(object): | |||
1385 | 874 | except: | 863 | except: |
1386 | 875 | return False | 864 | return False |
1387 | 876 | 865 | ||
1388 | 877 | # | ||
1389 | 878 | # Livepatch | ||
1390 | 879 | # | ||
1391 | 880 | def init_snapd(self): | ||
1392 | 881 | self.snapd_client = Snapd.Client() | ||
1393 | 882 | |||
1394 | 883 | def get_livepatch_snap_async(self, callback): | ||
1395 | 884 | assert self.snapd_client | ||
1396 | 885 | self.snapd_client.list_one_async('canonical-livepatch', | ||
1397 | 886 | self.cancellable, | ||
1398 | 887 | self.on_list_one_ready_cb, | ||
1399 | 888 | callback) | ||
1400 | 889 | |||
1401 | 890 | def on_list_one_ready_cb(self, source_object, result, user_data): | ||
1402 | 891 | callback = user_data | ||
1403 | 892 | try: | ||
1404 | 893 | snap = source_object.list_one_finish(result) | ||
1405 | 894 | except: | ||
1406 | 895 | snap = None | ||
1407 | 896 | if snap: | ||
1408 | 897 | if callback: | ||
1409 | 898 | callback(snap) | ||
1410 | 899 | return | ||
1411 | 900 | else: | ||
1412 | 901 | assert self.snapd_client | ||
1413 | 902 | self.snapd_client.find_async(Snapd.FindFlags.MATCH_NAME, | ||
1414 | 903 | 'canonical-livepatch', | ||
1415 | 904 | self.cancellable, | ||
1416 | 905 | self.on_find_ready_cb, | ||
1417 | 906 | callback) | ||
1418 | 907 | |||
1419 | 908 | def on_find_ready_cb(self, source_object, result, user_data): | ||
1420 | 909 | callback = user_data | ||
1421 | 910 | try: | ||
1422 | 911 | snaps = source_object.find_finish(result)[0] | ||
1423 | 912 | except: | ||
1424 | 913 | snaps = list() | ||
1425 | 914 | snap = snaps[0] if len(snaps) else None | ||
1426 | 915 | if callback: | ||
1427 | 916 | callback(snap) | ||
1428 | 917 | |||
1429 | 918 | def get_livepatch_snap_status(self, snap): | ||
1430 | 919 | if snap is None: | ||
1431 | 920 | return Snapd.SnapStatus.UNKNOWN | ||
1432 | 921 | return snap.get_status() | ||
1433 | 922 | |||
1434 | 923 | # glib-snapd does not keep track of the status of the snap. Use this decorator | ||
1435 | 924 | # to make it easy to write async functions that will always have an updated | ||
1436 | 925 | # snap object. | ||
1437 | 926 | def require_livepatch_snap(func): | ||
1438 | 927 | def get_livepatch_snap_and_call(*args, **kwargs): | ||
1439 | 928 | return args[0].get_livepatch_snap_async(lambda snap: func(snap=snap, *args, **kwargs)) | ||
1440 | 929 | return get_livepatch_snap_and_call | ||
1441 | 930 | |||
1442 | 931 | def is_livepatch_enabled(self): | ||
1443 | 932 | file = Gio.File.new_for_path(path=self.LIVEPATCH_RUNNING_FILE) | ||
1444 | 933 | return file.query_exists(None) | ||
1445 | 934 | |||
1446 | 935 | @require_livepatch_snap | ||
1447 | 936 | def set_livepatch_enabled_async(self, enabled, token, callback, snap=None): | ||
1448 | 937 | status = self.get_livepatch_snap_status(snap) | ||
1449 | 938 | if status == Snapd.SnapStatus.UNKNOWN: | ||
1450 | 939 | if callback: | ||
1451 | 940 | callback(True, _("Canonical Livepatch snap cannot be installed.")) | ||
1452 | 941 | elif status == Snapd.SnapStatus.ACTIVE: | ||
1453 | 942 | if enabled: | ||
1454 | 943 | error = self.enable_livepatch_service(token) | ||
1455 | 944 | else: | ||
1456 | 945 | error = self.disable_livepatch_service() | ||
1457 | 946 | if callback: | ||
1458 | 947 | callback(len(error) > 0, error) | ||
1459 | 948 | elif status == Snapd.SnapStatus.INSTALLED: | ||
1460 | 949 | if enabled: | ||
1461 | 950 | self.snapd_client.enable_async(name='canonical-livepatch', | ||
1462 | 951 | cancellable=self.cancellable, | ||
1463 | 952 | callback=self.livepatch_enable_snap_cb, | ||
1464 | 953 | user_data=(callback, token)) | ||
1465 | 954 | else: | ||
1466 | 955 | if callback: | ||
1467 | 956 | callback(False, "") | ||
1468 | 957 | elif status == Snapd.SnapStatus.AVAILABLE: | ||
1469 | 958 | if enabled: | ||
1470 | 959 | self.snapd_client.install_async(name='canonical-livepatch', | ||
1471 | 960 | cancellable=self.cancellable, | ||
1472 | 961 | callback=self.livepatch_install_snap_cb, | ||
1473 | 962 | user_data=(callback, token)) | ||
1474 | 963 | else: | ||
1475 | 964 | if callback: | ||
1476 | 965 | callback(False, "") | ||
1477 | 966 | |||
1478 | 967 | def livepatch_enable_snap_cb(self, source_object, result, user_data): | ||
1479 | 968 | (callback, token) = user_data | ||
1480 | 969 | try: | ||
1481 | 970 | if source_object.enable_finish(result): | ||
1482 | 971 | error = self.enable_livepatch_service(token) | ||
1483 | 972 | if callback: | ||
1484 | 973 | callback(len(error) > 0, error) | ||
1485 | 974 | except Exception: | ||
1486 | 975 | if callback: | ||
1487 | 976 | callback(True, _("Canonical Livepatch snap cannot be enabled.")) | ||
1488 | 977 | |||
1489 | 978 | def livepatch_install_snap_cb(self, source_object, result, user_data): | ||
1490 | 979 | (callback, token) = user_data | ||
1491 | 980 | try: | ||
1492 | 981 | if source_object.install_finish(result): | ||
1493 | 982 | error = self.enable_livepatch_service(token) | ||
1494 | 983 | if callback: | ||
1495 | 984 | callback(len(error) > 0, error) | ||
1496 | 985 | except Exception: | ||
1497 | 986 | if callback: | ||
1498 | 987 | callback(True, _("Canonical Livepatch snap cannot be installed.")) | ||
1499 | 988 | |||
1500 | 989 | def enable_livepatch_service(self, token): | ||
1501 | 990 | generic_error = _("Canonical Livepatch cannot be enabled.") | ||
1502 | 991 | |||
1503 | 992 | if self.is_livepatch_enabled(): | ||
1504 | 993 | return "" | ||
1505 | 994 | |||
1506 | 995 | try: | ||
1507 | 996 | subprocess.check_output(['/snap/bin/canonical-livepatch', 'enable', token], stderr=subprocess.STDOUT) | ||
1508 | 997 | return "" | ||
1509 | 998 | except subprocess.CalledProcessError as e: | ||
1510 | 999 | return e.output if e.output else generic_error | ||
1511 | 1000 | except: | ||
1512 | 1001 | return generic_error | ||
1513 | 1002 | |||
1514 | 1003 | |||
1515 | 1004 | def disable_livepatch_service(self): | ||
1516 | 1005 | generic_error = _("Canonical Livepatch cannot be disabled.") | ||
1517 | 1006 | |||
1518 | 1007 | if not self.is_livepatch_enabled(): | ||
1519 | 1008 | return "" | ||
1520 | 1009 | |||
1521 | 1010 | try: | ||
1522 | 1011 | subprocess.check_output(['/snap/bin/canonical-livepatch', 'disable'], stderr=subprocess.STDOUT) | ||
1523 | 1012 | return "" | ||
1524 | 1013 | except subprocess.CalledProcessError as e: | ||
1525 | 1014 | return e.output if e.output else generic_error | ||
1526 | 1015 | except: | ||
1527 | 1016 | return generic_error | ||
1528 | 1017 | |||
1529 | 1018 | def shortcut_handler(shortcut): | 866 | def shortcut_handler(shortcut): |
1530 | 1019 | for factory in _SHORTCUT_FACTORIES: | 867 | for factory in _SHORTCUT_FACTORIES: |
1531 | 1020 | ret = factory(shortcut) | 868 | ret = factory(shortcut) |
1532 | diff --git a/softwareproperties/dbus/SoftwarePropertiesDBus.py b/softwareproperties/dbus/SoftwarePropertiesDBus.py | |||
1533 | index ace8733..2653cd8 100644 | |||
1534 | --- a/softwareproperties/dbus/SoftwarePropertiesDBus.py | |||
1535 | +++ b/softwareproperties/dbus/SoftwarePropertiesDBus.py | |||
1536 | @@ -25,10 +25,12 @@ import logging | |||
1537 | 25 | import subprocess | 25 | import subprocess |
1538 | 26 | import tempfile | 26 | import tempfile |
1539 | 27 | import sys | 27 | import sys |
1540 | 28 | import threading | ||
1541 | 28 | 29 | ||
1542 | 29 | from aptsources.sourceslist import SourceEntry | 30 | from aptsources.sourceslist import SourceEntry |
1543 | 30 | 31 | ||
1544 | 31 | from dbus.mainloop.glib import DBusGMainLoop | 32 | from dbus.mainloop.glib import DBusGMainLoop |
1545 | 33 | from softwareproperties.LivepatchService import LivepatchService | ||
1546 | 32 | from softwareproperties.SoftwareProperties import SoftwareProperties | 34 | from softwareproperties.SoftwareProperties import SoftwareProperties |
1547 | 33 | 35 | ||
1548 | 34 | DBUS_BUS_NAME = 'com.ubuntu.SoftwareProperties' | 36 | DBUS_BUS_NAME = 'com.ubuntu.SoftwareProperties' |
1549 | @@ -61,7 +63,7 @@ class SoftwarePropertiesDBus(dbus.service.Object, SoftwareProperties): | |||
1550 | 61 | self.enforce_polkit = True | 63 | self.enforce_polkit = True |
1551 | 62 | logging.debug("waiting for connections") | 64 | logging.debug("waiting for connections") |
1552 | 63 | 65 | ||
1554 | 64 | self.init_snapd() | 66 | self._livepatch_service = LivepatchService() |
1555 | 65 | 67 | ||
1556 | 66 | # override set_modified_sourceslist to emit a signal | 68 | # override set_modified_sourceslist to emit a signal |
1557 | 67 | def save_sourceslist(self): | 69 | def save_sourceslist(self): |
1558 | @@ -324,9 +326,13 @@ class SoftwarePropertiesDBus(dbus.service.Object, SoftwareProperties): | |||
1559 | 324 | sender_keyword="sender", connection_keyword="conn", | 326 | sender_keyword="sender", connection_keyword="conn", |
1560 | 325 | in_signature='bs', out_signature='bs', async_callbacks=('reply_handler', 'error_handler')) | 327 | in_signature='bs', out_signature='bs', async_callbacks=('reply_handler', 'error_handler')) |
1561 | 326 | def SetLivepatchEnabled(self, enabled, token, reply_handler, error_handler, sender=None, conn=None): | 328 | def SetLivepatchEnabled(self, enabled, token, reply_handler, error_handler, sender=None, conn=None): |
1562 | 329 | def enable_thread_func(): | ||
1563 | 330 | ret = self._livepatch_service.set_enabled(enabled, token) | ||
1564 | 331 | GLib.idle_add(lambda: reply_handler(*ret)) | ||
1565 | 332 | |||
1566 | 327 | self._check_policykit_privilege( | 333 | self._check_policykit_privilege( |
1567 | 328 | sender, conn, "com.ubuntu.softwareproperties.applychanges") | 334 | sender, conn, "com.ubuntu.softwareproperties.applychanges") |
1569 | 329 | self.set_livepatch_enabled_async(enabled, token, reply_handler) | 335 | threading.Thread(target=enable_thread_func).start() |
1570 | 330 | 336 | ||
1571 | 331 | # helper from jockey | 337 | # helper from jockey |
1572 | 332 | def _check_policykit_privilege(self, sender, conn, privilege): | 338 | def _check_policykit_privilege(self, sender, conn, privilege): |
1573 | diff --git a/softwareproperties/gtk/DialogAuth.py b/softwareproperties/gtk/DialogAuth.py | |||
1574 | index d62e518..421daef 100644 | |||
1575 | --- a/softwareproperties/gtk/DialogAuth.py | |||
1576 | +++ b/softwareproperties/gtk/DialogAuth.py | |||
1577 | @@ -19,6 +19,7 @@ | |||
1578 | 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
1579 | 20 | # USA | 20 | # USA |
1580 | 21 | 21 | ||
1581 | 22 | from enum import IntEnum | ||
1582 | 22 | import os | 23 | import os |
1583 | 23 | 24 | ||
1584 | 24 | from gettext import gettext as _ | 25 | from gettext import gettext as _ |
1585 | @@ -28,9 +29,13 @@ from softwareproperties.gtk.utils import ( | |||
1586 | 28 | 29 | ||
1587 | 29 | import gi | 30 | import gi |
1588 | 30 | gi.require_version('Goa', '1.0') | 31 | gi.require_version('Goa', '1.0') |
1590 | 31 | from gi.repository import Gio, GLib, Goa, GObject, Gtk | 32 | from gi.repository import Gio, GLib, Goa, Gtk |
1591 | 32 | import logging | 33 | import logging |
1592 | 33 | 34 | ||
1593 | 35 | class Column(IntEnum): | ||
1594 | 36 | ID = 0 | ||
1595 | 37 | MAIL = 1 | ||
1596 | 38 | ACCOUNT = 2 | ||
1597 | 34 | 39 | ||
1598 | 35 | class DialogAuth: | 40 | class DialogAuth: |
1599 | 36 | 41 | ||
1600 | @@ -38,68 +43,128 @@ class DialogAuth: | |||
1601 | 38 | """setup up the gtk dialog""" | 43 | """setup up the gtk dialog""" |
1602 | 39 | self.parent = parent | 44 | self.parent = parent |
1603 | 40 | 45 | ||
1607 | 41 | setup_ui(self, os.path.join(datadir, "gtkbuilder", | 46 | setup_ui(self, os.path.join(datadir, "gtkbuilder", "dialog-auth.ui"), |
1608 | 42 | "dialog-auth.ui"), domain="software-properties") | 47 | domain="software-properties") |
1606 | 43 | self.label_title.set_max_width_chars(50) | ||
1609 | 44 | 48 | ||
1610 | 45 | self.dialog = self.dialog_auth | 49 | self.dialog = self.dialog_auth |
1612 | 46 | self.dialog.use_header_bar = True | 50 | self.dialog.set_title('') |
1613 | 51 | self.dialog.set_deletable(False) | ||
1614 | 47 | self.dialog.set_transient_for(parent) | 52 | self.dialog.set_transient_for(parent) |
1615 | 48 | 53 | ||
1617 | 49 | self.listboxrow_new_account.account = None | 54 | self.button_continue.grab_focus() |
1618 | 50 | 55 | ||
1619 | 51 | self.account = None | 56 | self.account = None |
1620 | 52 | self.dispose_on_new_account = False | 57 | self.dispose_on_new_account = False |
1621 | 53 | self.goa_client = Goa.Client.new_sync(None) | 58 | self.goa_client = Goa.Client.new_sync(None) |
1622 | 54 | 59 | ||
1624 | 55 | self.listbox_accounts.connect('row-activated', self._listbox_accounts_row_activated_cb) | 60 | self._setup_model() |
1625 | 61 | self._check_ui(select=False) | ||
1626 | 56 | 62 | ||
1627 | 57 | # Be ready to other accounts | 63 | # Be ready to other accounts |
1628 | 58 | self.goa_client.connect('account-added', self._account_added_cb) | 64 | self.goa_client.connect('account-added', self._account_added_cb) |
1629 | 59 | self.goa_client.connect('account-removed', self._account_removed_cb) | 65 | self.goa_client.connect('account-removed', self._account_removed_cb) |
1630 | 60 | 66 | ||
1631 | 61 | self._setup_listbox_accounts() | ||
1632 | 62 | self._check_ui() | ||
1633 | 63 | |||
1634 | 64 | def run(self): | 67 | def run(self): |
1635 | 65 | res = self.dialog.run() | 68 | res = self.dialog.run() |
1636 | 66 | self.dialog.hide() | 69 | self.dialog.hide() |
1637 | 67 | return res | 70 | return res |
1638 | 68 | 71 | ||
1646 | 69 | def _check_ui(self): | 72 | def _setup_model(self): |
1647 | 70 | rows = self.listbox_accounts.get_children() | 73 | for obj in self.goa_client.get_accounts(): |
1648 | 71 | has_accounts = len(rows) > 1 | 74 | self._add_account(obj.get_account(), select=False) |
1649 | 72 | 75 | ||
1650 | 73 | if has_accounts: | 76 | def _set_header(self, label): |
1651 | 74 | title = _('To continue choose an Ubuntu Single Sign-On account.') | 77 | self.label_header.set_markup( |
1652 | 75 | new_account = _('Use another account…') | 78 | "<span size='larger' weight='bold'>%s</span>" % label) |
1653 | 79 | |||
1654 | 80 | def _check_ui(self, select): | ||
1655 | 81 | naccounts = len(self.liststore_account) | ||
1656 | 82 | |||
1657 | 83 | if naccounts == 0: | ||
1658 | 84 | self._set_header( | ||
1659 | 85 | _('To use Livepatch, you need to use an Ubuntu One account.')) | ||
1660 | 86 | self.combobox_account.set_visible(False) | ||
1661 | 87 | self.label_account.set_visible(False) | ||
1662 | 88 | self.button_add_another.set_visible(False) | ||
1663 | 89 | self.button_continue.set_label(_('Sign In / Register…')) | ||
1664 | 90 | elif naccounts == 1: | ||
1665 | 91 | self._set_header( | ||
1666 | 92 | _('To use Livepatch, you need to use your Ubuntu One account.')) | ||
1667 | 93 | self.combobox_account.set_visible(False) | ||
1668 | 94 | self.label_account.set_visible(True) | ||
1669 | 95 | self.label_account.set_text(self.liststore_account[0][Column.MAIL]) | ||
1670 | 96 | self.button_add_another.set_visible(True) | ||
1671 | 97 | self.button_continue.set_label(_('Continue')) | ||
1672 | 76 | else: | 98 | else: |
1675 | 77 | title = _('To continue you need an Ubuntu Single Sign-On account.') | 99 | self._set_header( |
1676 | 78 | new_account = _('Sign In…') | 100 | _('To use Livepatch, you need to use an Ubuntu One account.')) |
1677 | 101 | self.button_add_another.set_visible(True) | ||
1678 | 102 | self.combobox_account.set_visible(True) | ||
1679 | 103 | self.label_account.set_visible(False) | ||
1680 | 104 | self.button_continue.set_label(_('Use')) | ||
1681 | 105 | if select: | ||
1682 | 106 | self.combobox_account.set_active(naccounts-1) | ||
1683 | 107 | elif self.combobox_account.get_active() == -1: | ||
1684 | 108 | self.combobox_account.set_active(0) | ||
1685 | 109 | |||
1686 | 110 | def _ignore_account(self, account): | ||
1687 | 111 | return account.props.provider_type != 'ubuntusso' | ||
1688 | 112 | |||
1689 | 113 | def _get_account_iter(self, account): | ||
1690 | 114 | row = self.liststore_account.get_iter_first() | ||
1691 | 115 | while row is not None: | ||
1692 | 116 | account_id = self.liststore_account.get_value(row, Column.ID) | ||
1693 | 117 | if account_id == account.props.id: | ||
1694 | 118 | return row | ||
1695 | 119 | row = self.liststore_account.iter_next(row) | ||
1696 | 120 | return None | ||
1697 | 121 | |||
1698 | 122 | def _add_account(self, account, select): | ||
1699 | 123 | if self._ignore_account(account): | ||
1700 | 124 | return | ||
1701 | 79 | 125 | ||
1704 | 80 | self.label_title.set_text(title) | 126 | account_iter = self._get_account_iter(account) |
1705 | 81 | self.label_new_account.set_markup('<b>{}</b>'.format(new_account)) | 127 | if account_iter is not None: |
1706 | 128 | return | ||
1707 | 82 | 129 | ||
1713 | 83 | def _setup_listbox_accounts(self): | 130 | account_iter = self.liststore_account.append() |
1714 | 84 | for obj in self.goa_client.get_accounts(): | 131 | self.liststore_account.set(account_iter, |
1715 | 85 | account = obj.get_account() | 132 | [Column.ID, Column.MAIL, Column.ACCOUNT], |
1716 | 86 | if self._is_account_supported(account): | 133 | [account.props.id, account.props.presentation_identity, account]) |
1717 | 87 | self._add_account(account) | 134 | self._check_ui(select) |
1718 | 88 | 135 | ||
1721 | 89 | def _is_account_supported(self, account): | 136 | def _remove_account(self, account): |
1722 | 90 | return account.props.provider_type == 'ubuntusso' | 137 | if self._ignore_account(account): |
1723 | 138 | return | ||
1724 | 139 | |||
1725 | 140 | account_iter = self._get_account_iter(account) | ||
1726 | 141 | if account_iter is None: | ||
1727 | 142 | return | ||
1728 | 91 | 143 | ||
1733 | 92 | def _add_account(self, account): | 144 | self.liststore_account.remove(account_iter) |
1734 | 93 | row = self._create_row(account) | 145 | self._check_ui(select=False) |
1731 | 94 | self.listbox_accounts.prepend(row) | ||
1732 | 95 | self._check_ui() | ||
1735 | 96 | 146 | ||
1742 | 97 | def _remove_account(self, account): | 147 | def _response_if_valid(self, account): |
1743 | 98 | for row in self.listbox_accounts.get_children(): | 148 | def cb(source, res, data): |
1744 | 99 | if row.account == account: | 149 | try: |
1745 | 100 | row.destroy() | 150 | source.call_ensure_credentials_finish(res) |
1746 | 101 | self._check_ui() | 151 | valid = True |
1747 | 102 | break | 152 | except GLib.Error as e: |
1748 | 153 | logging.warning("call_ensure_credentials_finish exception: %s", | ||
1749 | 154 | e.message) | ||
1750 | 155 | valid = False | ||
1751 | 156 | |||
1752 | 157 | if not valid: | ||
1753 | 158 | try: | ||
1754 | 159 | self._spawn_goa_with_args(account.props.id, None) | ||
1755 | 160 | except GLib.Error as e: | ||
1756 | 161 | logging.warning ('Failed to spawn gnome-control-center: %s', | ||
1757 | 162 | e.message) | ||
1758 | 163 | else: | ||
1759 | 164 | self.account = account | ||
1760 | 165 | self.dialog.response(Gtk.ResponseType.OK) | ||
1761 | 166 | |||
1762 | 167 | account.call_ensure_credentials(None, cb, None) | ||
1763 | 103 | 168 | ||
1764 | 104 | def _build_dbus_params(self, action, arg): | 169 | def _build_dbus_params(self, action, arg): |
1765 | 105 | builder = GLib.VariantBuilder.new(GLib.VariantType.new('av')) | 170 | builder = GLib.VariantBuilder.new(GLib.VariantType.new('av')) |
1766 | @@ -118,7 +183,8 @@ class DialogAuth: | |||
1767 | 118 | v = GLib.Variant.new_variant(s) | 183 | v = GLib.Variant.new_variant(s) |
1768 | 119 | builder.add_value(v) | 184 | builder.add_value(v) |
1769 | 120 | 185 | ||
1771 | 121 | array = GLib.Variant.new_tuple(GLib.Variant.new_string('online-accounts'), builder.end()) | 186 | array = GLib.Variant.new_tuple( |
1772 | 187 | GLib.Variant.new_string('online-accounts'), builder.end()) | ||
1773 | 122 | array = GLib.Variant.new_variant(array) | 188 | array = GLib.Variant.new_variant(array) |
1774 | 123 | 189 | ||
1775 | 124 | param = GLib.Variant.new_tuple( | 190 | param = GLib.Variant.new_tuple( |
1776 | @@ -135,94 +201,43 @@ class DialogAuth: | |||
1777 | 135 | 'org.gtk.Actions', None) | 201 | 'org.gtk.Actions', None) |
1778 | 136 | 202 | ||
1779 | 137 | param = self._build_dbus_params(action, arg) | 203 | param = self._build_dbus_params(action, arg) |
1853 | 138 | timeout = 10*60*1000 # 10 minutes should be enough to create an account | 204 | proxy.call_sync('Activate', param, Gio.DBusCallFlags.NONE, -1, None) |
1781 | 139 | proxy.call_sync('Activate', param, Gio.DBusCallFlags.NONE, timeout, None) | ||
1782 | 140 | |||
1783 | 141 | def _create_row(self, account): | ||
1784 | 142 | identity = account.props.presentation_identity | ||
1785 | 143 | provider_name = account.props.provider_name | ||
1786 | 144 | |||
1787 | 145 | row = Gtk.ListBoxRow.new() | ||
1788 | 146 | row.show() | ||
1789 | 147 | |||
1790 | 148 | hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 6) | ||
1791 | 149 | hbox.set_hexpand(True) | ||
1792 | 150 | hbox.show() | ||
1793 | 151 | |||
1794 | 152 | image = Gtk.Image.new_from_icon_name('avatar-default', Gtk.IconSize.DIALOG) | ||
1795 | 153 | image.set_pixel_size(48) | ||
1796 | 154 | image.show() | ||
1797 | 155 | hbox.pack_start(image, False, False, 0) | ||
1798 | 156 | |||
1799 | 157 | vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 2) | ||
1800 | 158 | vbox.set_valign(Gtk.Align.CENTER) | ||
1801 | 159 | vbox.show() | ||
1802 | 160 | hbox.pack_start(vbox, False, False, 0) | ||
1803 | 161 | |||
1804 | 162 | if identity: | ||
1805 | 163 | ilabel = Gtk.Label.new() | ||
1806 | 164 | ilabel.set_halign(Gtk.Align.START) | ||
1807 | 165 | ilabel.set_markup('<b>{}</b>'.format(identity)) | ||
1808 | 166 | ilabel.show() | ||
1809 | 167 | vbox.pack_start(ilabel, True, True, 0) | ||
1810 | 168 | |||
1811 | 169 | if provider_name: | ||
1812 | 170 | plabel = Gtk.Label.new() | ||
1813 | 171 | plabel.set_halign(Gtk.Align.START) | ||
1814 | 172 | plabel.set_markup('<small><span foreground=\"#555555\">{}</span></small>'.format(provider_name)) | ||
1815 | 173 | plabel.show() | ||
1816 | 174 | vbox.pack_start(plabel, True, True, 0) | ||
1817 | 175 | |||
1818 | 176 | warning_icon = Gtk.Image.new_from_icon_name('dialog-warning-symbolic', Gtk.IconSize.BUTTON) | ||
1819 | 177 | warning_icon.set_no_show_all(True) | ||
1820 | 178 | warning_icon.set_margin_end (15) | ||
1821 | 179 | hbox.pack_end(warning_icon, False, False, 0) | ||
1822 | 180 | |||
1823 | 181 | row.add(hbox) | ||
1824 | 182 | |||
1825 | 183 | account.bind_property('attention-needed', warning_icon, 'visible', | ||
1826 | 184 | GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE) | ||
1827 | 185 | |||
1828 | 186 | row.account = account | ||
1829 | 187 | return row | ||
1830 | 188 | |||
1831 | 189 | # Signals handlers | ||
1832 | 190 | def _listbox_accounts_row_activated_cb(self, listbox, row): | ||
1833 | 191 | account = row.account | ||
1834 | 192 | |||
1835 | 193 | if account is None: | ||
1836 | 194 | # TODO (azzar1): there is no easy way to put this to false | ||
1837 | 195 | # if the user close the windows without adding an account. | ||
1838 | 196 | # We need to discuss with goa's upstream to support such usercases | ||
1839 | 197 | try: | ||
1840 | 198 | self._spawn_goa_with_args('add', 'ubuntusso') | ||
1841 | 199 | self.dispose_on_new_account = True | ||
1842 | 200 | except GLib.Error as e: | ||
1843 | 201 | logging.warning ('Failed to spawing gnome-control-center: %s', e.message) | ||
1844 | 202 | else: | ||
1845 | 203 | if account.props.attention_needed: | ||
1846 | 204 | try: | ||
1847 | 205 | self._spawn_goa_with_args(account.props.id, None) | ||
1848 | 206 | except GLib.Error as e: | ||
1849 | 207 | logging.warning ('Failed to spawing gnome-control-center: %s', e.message) | ||
1850 | 208 | else: | ||
1851 | 209 | self.account = account | ||
1852 | 210 | self.dialog.response(Gtk.ResponseType.OK) | ||
1854 | 211 | 205 | ||
1855 | 212 | def _account_added_cb(self, goa_client, goa_object): | 206 | def _account_added_cb(self, goa_client, goa_object): |
1856 | 213 | account = goa_object.get_account() | 207 | account = goa_object.get_account() |
1858 | 214 | if not self._is_account_supported(account): | 208 | if self._ignore_account(account): |
1859 | 215 | return | 209 | return |
1860 | 216 | if not self.dispose_on_new_account: | 210 | if not self.dispose_on_new_account: |
1862 | 217 | self._add_account(account) | 211 | self._add_account(account, True) |
1863 | 218 | else: | 212 | else: |
1866 | 219 | self.account = account | 213 | self._response_if_valid(account) |
1865 | 220 | self.dialog.response(Gtk.ResponseType.OK) | ||
1867 | 221 | 214 | ||
1868 | 222 | def _account_removed_cb(self, goa_client, goa_object): | 215 | def _account_removed_cb(self, goa_client, goa_object): |
1869 | 223 | account = goa_object.get_account() | 216 | account = goa_object.get_account() |
1871 | 224 | if self._is_account_supported(account): | 217 | if not self._ignore_account(account): |
1872 | 225 | self._remove_account(account) | 218 | self._remove_account(account) |
1873 | 226 | 219 | ||
1874 | 220 | def _button_add_another_clicked_cb(self, button): | ||
1875 | 221 | try: | ||
1876 | 222 | # There is no easy way to put this to false if the user close the | ||
1877 | 223 | # windows without adding an account. | ||
1878 | 224 | self._spawn_goa_with_args('add', 'ubuntusso') | ||
1879 | 225 | self.dispose_on_new_account = True | ||
1880 | 226 | except GLib.Error as e: | ||
1881 | 227 | logging.warning ('Failed to spawn control-center: %s', e.message) | ||
1882 | 228 | |||
1883 | 229 | def _button_cancel_clicked_cb(self, button): | ||
1884 | 230 | self.dialog.response(Gtk.ResponseType.CANCEL) | ||
1885 | 231 | |||
1886 | 232 | def _button_continue_clicked_cb(self, button): | ||
1887 | 233 | naccounts = len(self.liststore_account) | ||
1888 | 227 | 234 | ||
1889 | 235 | account = None | ||
1890 | 236 | if naccounts >= 1: | ||
1891 | 237 | active_index = self.combobox_account.get_active() | ||
1892 | 238 | account = self.liststore_account[active_index][Column.ACCOUNT] | ||
1893 | 228 | 239 | ||
1894 | 240 | if account is None: | ||
1895 | 241 | self._button_add_another_clicked_cb(self.button_add_another) | ||
1896 | 242 | else: | ||
1897 | 243 | self._response_if_valid(account) | ||
1898 | diff --git a/softwareproperties/gtk/DialogLivepatchError.py b/softwareproperties/gtk/DialogLivepatchError.py | |||
1899 | index 2d6688c..e27bb69 100644 | |||
1900 | --- a/softwareproperties/gtk/DialogLivepatchError.py | |||
1901 | +++ b/softwareproperties/gtk/DialogLivepatchError.py | |||
1902 | @@ -1,5 +1,5 @@ | |||
1903 | 1 | # | 1 | # |
1905 | 2 | # Copyright (c) 2017-2018 Canonical | 2 | # Copyright (c) 2017-2019 Canonical |
1906 | 3 | # | 3 | # |
1907 | 4 | # Authors: | 4 | # Authors: |
1908 | 5 | # Andrea Azzarone <andrea.azzarone@canonical.com> | 5 | # Andrea Azzarone <andrea.azzarone@canonical.com> |
1909 | @@ -21,6 +21,8 @@ | |||
1910 | 21 | 21 | ||
1911 | 22 | import os | 22 | import os |
1912 | 23 | 23 | ||
1913 | 24 | from gettext import gettext as _ | ||
1914 | 25 | |||
1915 | 24 | from softwareproperties.gtk.utils import ( | 26 | from softwareproperties.gtk.utils import ( |
1916 | 25 | setup_ui, | 27 | setup_ui, |
1917 | 26 | ) | 28 | ) |
1918 | @@ -31,20 +33,27 @@ class DialogLivepatchError: | |||
1919 | 31 | RESPONSE_SETTINGS = 100 | 33 | RESPONSE_SETTINGS = 100 |
1920 | 32 | RESPONSE_IGNORE = 101 | 34 | RESPONSE_IGNORE = 101 |
1921 | 33 | 35 | ||
1922 | 36 | primary = _("Sorry, there's been a problem with setting up Canonical Livepatch.") | ||
1923 | 37 | |||
1924 | 34 | def __init__(self, parent, datadir): | 38 | def __init__(self, parent, datadir): |
1925 | 35 | """setup up the gtk dialog""" | 39 | """setup up the gtk dialog""" |
1926 | 36 | self.parent = parent | 40 | self.parent = parent |
1927 | 37 | 41 | ||
1930 | 38 | setup_ui(self, os.path.join(datadir, "gtkbuilder", | 42 | setup_ui( |
1931 | 39 | "dialog-livepatch-error.ui"), domain="software-properties") | 43 | self, |
1932 | 44 | os.path.join(datadir, "gtkbuilder", "dialog-livepatch-error.ui"), | ||
1933 | 45 | domain="software-properties") | ||
1934 | 40 | 46 | ||
1935 | 41 | self.dialog = self.messagedialog_livepatch | 47 | self.dialog = self.messagedialog_livepatch |
1936 | 42 | self.dialog.use_header_bar = True | ||
1937 | 43 | self.dialog.set_transient_for(parent) | 48 | self.dialog.set_transient_for(parent) |
1938 | 44 | 49 | ||
1939 | 45 | def run(self, error, show_settings_button): | 50 | def run(self, error, show_settings_button): |
1942 | 46 | self.dialog.format_secondary_markup( | 51 | p = "<span weight=\"bold\" size=\"larger\">{}</span>".format(self.primary) |
1943 | 47 | "The error was: \"%s\"" % error.strip()) | 52 | self.label_primary.set_markup(p) |
1944 | 53 | |||
1945 | 54 | textbuffer = self.treeview_message.get_buffer() | ||
1946 | 55 | textbuffer.set_text(error) | ||
1947 | 56 | |||
1948 | 48 | self.button_settings.set_visible(show_settings_button) | 57 | self.button_settings.set_visible(show_settings_button) |
1949 | 49 | res = self.dialog.run() | 58 | res = self.dialog.run() |
1950 | 50 | self.dialog.hide() | 59 | self.dialog.hide() |
1951 | diff --git a/softwareproperties/gtk/LivepatchPage.py b/softwareproperties/gtk/LivepatchPage.py | |||
1952 | 51 | new file mode 100644 | 60 | new file mode 100644 |
1953 | index 0000000..6c3ace9 | |||
1954 | --- /dev/null | |||
1955 | +++ b/softwareproperties/gtk/LivepatchPage.py | |||
1956 | @@ -0,0 +1,375 @@ | |||
1957 | 1 | # | ||
1958 | 2 | # Copyright (c) 2019 Canonical | ||
1959 | 3 | # | ||
1960 | 4 | # Authors: | ||
1961 | 5 | # Andrea Azzarone <andrea.azzarone@canonical.com> | ||
1962 | 6 | # | ||
1963 | 7 | # This program is free software; you can redistribute it and/or | ||
1964 | 8 | # modify it under the terms of the GNU General Public License as | ||
1965 | 9 | # published by the Free Software Foundation; either version 2 of the | ||
1966 | 10 | # License, or (at your option) any later version. | ||
1967 | 11 | # | ||
1968 | 12 | # This program is distributed in the hope that it will be useful, | ||
1969 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1970 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1971 | 15 | # GNU General Public License for more details. | ||
1972 | 16 | # | ||
1973 | 17 | # You should have received a copy of the GNU General Public License | ||
1974 | 18 | # along with this program; if not, write to the Free Software | ||
1975 | 19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | ||
1976 | 20 | # USA | ||
1977 | 21 | |||
1978 | 22 | import datetime | ||
1979 | 23 | import gettext | ||
1980 | 24 | from gettext import gettext as _ | ||
1981 | 25 | import gi | ||
1982 | 26 | gi.require_version("Gtk", "3.0") | ||
1983 | 27 | from gi.repository import GLib, GObject, Gtk | ||
1984 | 28 | import logging | ||
1985 | 29 | |||
1986 | 30 | from softwareproperties.GoaAuth import GoaAuth | ||
1987 | 31 | from softwareproperties.LivepatchService import ( | ||
1988 | 32 | LivepatchService, | ||
1989 | 33 | LivepatchAvailability) | ||
1990 | 34 | from .DialogAuth import DialogAuth | ||
1991 | 35 | from .DialogLivepatchError import DialogLivepatchError | ||
1992 | 36 | |||
1993 | 37 | |||
1994 | 38 | class LivepatchPage(object): | ||
1995 | 39 | |||
1996 | 40 | # Constants | ||
1997 | 41 | COMMON_ISSUE_URL = 'https://wiki.ubuntu.com/Kernel/Livepatch#CommonIssues' | ||
1998 | 42 | GENERIC_ERR_MSG = _('Canonical Livepatch has experienced an internal error.' | ||
1999 | 43 | ' Please refer to {} for further information.'.format(COMMON_ISSUE_URL)) | ||
2000 | 44 | |||
2001 | 45 | def __init__(self, parent): | ||
2002 | 46 | self._parent = parent | ||
2003 | 47 | |||
2004 | 48 | self._timeout_handler = -1 | ||
2005 | 49 | self._waiting_livepatch_response = False | ||
2006 | 50 | |||
2007 | 51 | self._lps = LivepatchService() | ||
2008 | 52 | self._auth = GoaAuth() | ||
2009 | 53 | |||
2010 | 54 | # Connect signals | ||
2011 | 55 | self._lps.connect( | ||
2012 | 56 | 'notify::availability', self._lps_availability_changed_cb) | ||
2013 | 57 | self._lps.connect( | ||
2014 | 58 | 'notify::enabled', self._lps_enabled_changed_cb) | ||
2015 | 59 | self._auth.connect( | ||
2016 | 60 | 'notify', self._auth_changed_cb) | ||
2017 | 61 | self._state_set_handler = self._parent.switch_livepatch.connect( | ||
2018 | 62 | 'state-set', self._switch_state_set_cb) | ||
2019 | 63 | self._parent.button_livepatch_login.connect( | ||
2020 | 64 | 'clicked', self._button_livepatch_login_clicked_cb) | ||
2021 | 65 | |||
2022 | 66 | self._lps.trigger_availability_check() | ||
2023 | 67 | |||
2024 | 68 | @property | ||
2025 | 69 | def waiting_livepatch_response(self): | ||
2026 | 70 | return self._waiting_livepatch_response | ||
2027 | 71 | |||
2028 | 72 | # Private methods | ||
2029 | 73 | def _trigger_ui_update(self, skip=False, error_message=None): | ||
2030 | 74 | """Trigger the update of every single user interface component according | ||
2031 | 75 | to the current state. | ||
2032 | 76 | |||
2033 | 77 | Args: | ||
2034 | 78 | skip (bool): whether to trigger the update after a small timeout. | ||
2035 | 79 | Defaults to False. | ||
2036 | 80 | error_message (str): error message to display. Defaults to None. | ||
2037 | 81 | """ | ||
2038 | 82 | def do_ui_update(): | ||
2039 | 83 | self._timeout_handler = -1 | ||
2040 | 84 | |||
2041 | 85 | self._update_switch() | ||
2042 | 86 | self._update_spinner() | ||
2043 | 87 | self._update_switch_label() | ||
2044 | 88 | self._update_auth_button() | ||
2045 | 89 | self._update_stack(error_message) | ||
2046 | 90 | |||
2047 | 91 | return False | ||
2048 | 92 | |||
2049 | 93 | if self._timeout_handler > 0: | ||
2050 | 94 | GObject.source_remove(self._timeout_handler) | ||
2051 | 95 | self._timeout_handler = -1 | ||
2052 | 96 | |||
2053 | 97 | if skip: | ||
2054 | 98 | do_ui_update() | ||
2055 | 99 | else: | ||
2056 | 100 | self._timeout_handler = GLib.timeout_add_seconds(2, do_ui_update) | ||
2057 | 101 | |||
2058 | 102 | def _update_switch(self): | ||
2059 | 103 | """Update the state of the on/off switch.""" | ||
2060 | 104 | switch = self._parent.switch_livepatch | ||
2061 | 105 | |||
2062 | 106 | availability = self._lps.props.availability | ||
2063 | 107 | enabled = self._lps.props.enabled | ||
2064 | 108 | logged = self._auth.logged | ||
2065 | 109 | |||
2066 | 110 | switch.set_sensitive( | ||
2067 | 111 | availability == LivepatchAvailability.TRUE and | ||
2068 | 112 | (enabled or logged)) | ||
2069 | 113 | |||
2070 | 114 | if self._waiting_livepatch_response: | ||
2071 | 115 | return | ||
2072 | 116 | |||
2073 | 117 | self._parent.switch_livepatch.handler_block(self._state_set_handler) | ||
2074 | 118 | switch.set_state(switch.get_sensitive() and enabled) | ||
2075 | 119 | self._parent.switch_livepatch.handler_unblock(self._state_set_handler) | ||
2076 | 120 | |||
2077 | 121 | def _update_spinner(self): | ||
2078 | 122 | """Update the state of the in-progress spinner.""" | ||
2079 | 123 | spinner = self._parent.spinner_livepatch | ||
2080 | 124 | availability = self._lps.props.availability | ||
2081 | 125 | |||
2082 | 126 | spinner.set_visible(availability == LivepatchAvailability.CHECKING) | ||
2083 | 127 | spinner.props.active = (availability == LivepatchAvailability.CHECKING) | ||
2084 | 128 | |||
2085 | 129 | def _update_switch_label(self): | ||
2086 | 130 | """Update the text of the label next to the on/off switch.""" | ||
2087 | 131 | availability = self._lps.props.availability | ||
2088 | 132 | logged = self._auth.logged | ||
2089 | 133 | |||
2090 | 134 | if availability == LivepatchAvailability.CHECKING: | ||
2091 | 135 | msg = _('Checking availability…') | ||
2092 | 136 | elif availability == LivepatchAvailability.NO_CONNECTIVITY: | ||
2093 | 137 | msg = _('Livepatch requires an Internet connection.') | ||
2094 | 138 | elif availability == LivepatchAvailability.FALSE: | ||
2095 | 139 | msg = _('Livepatch is not available for this system.') | ||
2096 | 140 | else: | ||
2097 | 141 | if self._parent.switch_livepatch.get_active(): | ||
2098 | 142 | msg = _("Livepatch is on.") | ||
2099 | 143 | elif not logged: | ||
2100 | 144 | msg = _("To use Livepatch you need to sign in.") | ||
2101 | 145 | else: | ||
2102 | 146 | msg = _("Livepatch is off.") | ||
2103 | 147 | |||
2104 | 148 | self._parent.label_livepatch_switch.set_label(msg) | ||
2105 | 149 | |||
2106 | 150 | def _update_auth_button(self): | ||
2107 | 151 | """Update the state and the label of the authentication button.""" | ||
2108 | 152 | button = self._parent.button_livepatch_login | ||
2109 | 153 | |||
2110 | 154 | availability = self._lps.props.availability | ||
2111 | 155 | logged = self._auth.logged | ||
2112 | 156 | |||
2113 | 157 | button.set_visible( | ||
2114 | 158 | availability == LivepatchAvailability.TRUE and | ||
2115 | 159 | not self._parent.switch_livepatch.get_active()) | ||
2116 | 160 | button.set_label(_('Sign Out') if logged else _('Sign In…')) | ||
2117 | 161 | |||
2118 | 162 | def _update_stack(self, error_message): | ||
2119 | 163 | """Update the state of the stack. | ||
2120 | 164 | |||
2121 | 165 | If livepatch is not available nothing will be shown, if an error | ||
2122 | 166 | occurred an error message will be shown in a text view, otherwise the | ||
2123 | 167 | current livepatch status (e.g. a list of CVE fixes) will be shown. | ||
2124 | 168 | |||
2125 | 169 | Args: | ||
2126 | 170 | error_message (str): error message to display. | ||
2127 | 171 | """ | ||
2128 | 172 | availability = self._lps.props.availability | ||
2129 | 173 | availability_message = self._lps.props.availability_message | ||
2130 | 174 | |||
2131 | 175 | has_error = ( | ||
2132 | 176 | error_message is not None or | ||
2133 | 177 | (availability == LivepatchAvailability.FALSE and | ||
2134 | 178 | availability_message is not None)) | ||
2135 | 179 | |||
2136 | 180 | if has_error: | ||
2137 | 181 | self._parent.stack_livepatch.set_visible_child_name('page_livepatch_message') | ||
2138 | 182 | self._parent.stack_livepatch.set_visible(True) | ||
2139 | 183 | text_buffer = self._parent.textview_livepatch.get_buffer() | ||
2140 | 184 | text_buffer.delete( | ||
2141 | 185 | text_buffer.get_start_iter(), text_buffer.get_end_iter()) | ||
2142 | 186 | text_buffer.insert_markup( | ||
2143 | 187 | text_buffer.get_end_iter(), | ||
2144 | 188 | error_message or availability_message, -1) | ||
2145 | 189 | return | ||
2146 | 190 | |||
2147 | 191 | if availability == LivepatchAvailability.CHECKING or not self._parent.switch_livepatch.get_active(): | ||
2148 | 192 | self._parent.stack_livepatch.set_visible(False) | ||
2149 | 193 | else: | ||
2150 | 194 | self._update_status() | ||
2151 | 195 | |||
2152 | 196 | def _format_timedelta(self, td): | ||
2153 | 197 | days = td.days | ||
2154 | 198 | hours = td.seconds // 3600 | ||
2155 | 199 | minutes = td.seconds // 60 | ||
2156 | 200 | |||
2157 | 201 | if days > 0: | ||
2158 | 202 | return gettext.ngettext( | ||
2159 | 203 | '({} day ago)', | ||
2160 | 204 | '({} days ago)', | ||
2161 | 205 | days).format(days) | ||
2162 | 206 | elif hours > 0: | ||
2163 | 207 | return gettext.ngettext( | ||
2164 | 208 | '({} hour ago)', | ||
2165 | 209 | '({} hours ago)', | ||
2166 | 210 | hours).format(hours) | ||
2167 | 211 | elif minutes > 0: | ||
2168 | 212 | return gettext.ngettext( | ||
2169 | 213 | '({} minute ago)', | ||
2170 | 214 | '({} minutes ago)', | ||
2171 | 215 | minutes).format(minutes) | ||
2172 | 216 | else: | ||
2173 | 217 | return '' | ||
2174 | 218 | |||
2175 | 219 | def _datetime_to_str(self, dt): | ||
2176 | 220 | gdt = GLib.DateTime.new_from_unix_utc(dt.timestamp()) | ||
2177 | 221 | td = datetime.datetime.now(dt.tzinfo) - dt | ||
2178 | 222 | return '{} {}'.format( | ||
2179 | 223 | gdt.to_local().format('%x %H:%M'), | ||
2180 | 224 | self._format_timedelta(td)) | ||
2181 | 225 | |||
2182 | 226 | def _update_status(self): | ||
2183 | 227 | """Populate the UI to reflect the Livepatch status""" | ||
2184 | 228 | status = self._lps.get_status() | ||
2185 | 229 | |||
2186 | 230 | if status is None: | ||
2187 | 231 | if not self._waiting_livepatch_response: | ||
2188 | 232 | self._trigger_ui_update(skip=True, error_message=_('Failed to retrieve Livepatch status.')) | ||
2189 | 233 | return | ||
2190 | 234 | |||
2191 | 235 | self._parent.stack_livepatch.set_visible_child_name('page_livepatch_status') | ||
2192 | 236 | self._parent.stack_livepatch.set_visible(True) | ||
2193 | 237 | |||
2194 | 238 | check_state = status['Status'][0]['Livepatch']['CheckState'] if status else None | ||
2195 | 239 | state = status['Status'][0]['Livepatch']['State'] if status else None | ||
2196 | 240 | |||
2197 | 241 | if check_state == 'check-failed': | ||
2198 | 242 | self._trigger_ui_update(skip=True, error_message=self.GENERIC_ERR_MSG) | ||
2199 | 243 | return | ||
2200 | 244 | |||
2201 | 245 | if state in ['applied-with-bug', 'apply-failed', 'unknown']: | ||
2202 | 246 | self._trigger_ui_update(skip=True, error_message=self.GENERIC_ERR_MSG) | ||
2203 | 247 | return | ||
2204 | 248 | |||
2205 | 249 | self._parent.label_livepatch_last_update.set_label( | ||
2206 | 250 | _("Last check for updates: {}").format( | ||
2207 | 251 | self._datetime_to_str(status['Last-Check']) if status else _('None yet'))) | ||
2208 | 252 | |||
2209 | 253 | if state in ['unapplied', 'nothing-to-apply'] or state == None: | ||
2210 | 254 | self._parent.label_livepatch_header.set_label(_('No updates currently applied.')) | ||
2211 | 255 | self._parent.scrolledwindow_livepatch_fixes.set_visible(False) | ||
2212 | 256 | elif state == 'applied': | ||
2213 | 257 | self._parent.label_livepatch_header.set_label(_('Updates currently applied:')) | ||
2214 | 258 | self._parent.scrolledwindow_livepatch_fixes.set_visible(True) | ||
2215 | 259 | self._update_fixes(status['Status'][0]['Livepatch']['Fixes']) | ||
2216 | 260 | else: | ||
2217 | 261 | logging.warning('Livepatch status contains an invalid state: {}'.format(state)) | ||
2218 | 262 | |||
2219 | 263 | if check_state == 'needs-check' or state == 'unapplied': | ||
2220 | 264 | self._trigger_ui_update() | ||
2221 | 265 | |||
2222 | 266 | def _update_fixes(self, fixes): | ||
2223 | 267 | """Populate the UI to show the list of applied CVE fixes.""" | ||
2224 | 268 | treeview = self._parent.treeview_livepatch | ||
2225 | 269 | liststore = treeview.get_model() | ||
2226 | 270 | liststore.clear() | ||
2227 | 271 | |||
2228 | 272 | for fix in fixes: | ||
2229 | 273 | fix_iter = liststore.append() | ||
2230 | 274 | liststore.set(fix_iter, [0], [self._format_fix(fix)]) | ||
2231 | 275 | |||
2232 | 276 | def _format_fix(self, fix): | ||
2233 | 277 | """Format a fix in a UI friendly text.""" | ||
2234 | 278 | return '<b>{}</b>\n{}'.format( | ||
2235 | 279 | fix['Name'], fix['Description'].replace('\n', ' ')) | ||
2236 | 280 | |||
2237 | 281 | def _do_login(self): | ||
2238 | 282 | """Start the authentication flow to retrieve the livepatch token.""" | ||
2239 | 283 | dialog = DialogAuth(self._parent.window_main, self._parent.datadir) | ||
2240 | 284 | |||
2241 | 285 | if dialog.run() == Gtk.ResponseType.OK: | ||
2242 | 286 | self._auth.login(dialog.account) | ||
2243 | 287 | self._parent.switch_livepatch.set_state(True) | ||
2244 | 288 | |||
2245 | 289 | def _do_logout(self): | ||
2246 | 290 | """Start the de-authentication flow.""" | ||
2247 | 291 | self._auth.logout() | ||
2248 | 292 | |||
2249 | 293 | # Signals handler | ||
2250 | 294 | def _lps_availability_changed_cb(self, o, v): | ||
2251 | 295 | self._trigger_ui_update(skip=True) | ||
2252 | 296 | |||
2253 | 297 | def _lps_enabled_changed_cb(self, o, v): | ||
2254 | 298 | if self._waiting_livepatch_response: | ||
2255 | 299 | return | ||
2256 | 300 | self._trigger_ui_update(skip=False) | ||
2257 | 301 | |||
2258 | 302 | def _auth_changed_cb(self, o, v): | ||
2259 | 303 | self._trigger_ui_update(skip=True) | ||
2260 | 304 | |||
2261 | 305 | def _switch_state_set_cb(self, widget, state): | ||
2262 | 306 | if not self._waiting_livepatch_response: | ||
2263 | 307 | self._waiting_livepatch_response = True | ||
2264 | 308 | |||
2265 | 309 | token = self._auth.token or '' | ||
2266 | 310 | self._parent.backend.SetLivepatchEnabled( | ||
2267 | 311 | state, token, | ||
2268 | 312 | reply_handler=self._enabled_reply_handler, | ||
2269 | 313 | error_handler=self._enabled_error_handler, | ||
2270 | 314 | timeout=1200) | ||
2271 | 315 | |||
2272 | 316 | self._trigger_ui_update(skip=True) | ||
2273 | 317 | self._parent.switch_livepatch.set_state(state) | ||
2274 | 318 | |||
2275 | 319 | return False | ||
2276 | 320 | |||
2277 | 321 | def _button_livepatch_login_clicked_cb(self, button): | ||
2278 | 322 | if self._auth.logged: | ||
2279 | 323 | self._do_logout() | ||
2280 | 324 | else: | ||
2281 | 325 | self._do_login() | ||
2282 | 326 | |||
2283 | 327 | def _show_error_dialog(self, message): | ||
2284 | 328 | dialog = DialogLivepatchError( | ||
2285 | 329 | self._parent.window_main, | ||
2286 | 330 | self._parent.datadir) | ||
2287 | 331 | |||
2288 | 332 | response = dialog.run( | ||
2289 | 333 | error=message, | ||
2290 | 334 | show_settings_button=not self._parent.window_main.is_visible()) | ||
2291 | 335 | |||
2292 | 336 | if response == DialogLivepatchError.RESPONSE_SETTINGS: | ||
2293 | 337 | self._parent.window_main.show() | ||
2294 | 338 | self._parent.notebook_main.set_current_page(6) | ||
2295 | 339 | elif not self._parent.window_main.is_visible(): | ||
2296 | 340 | self._parent.on_close_button(None) | ||
2297 | 341 | |||
2298 | 342 | # DBus replay handlers | ||
2299 | 343 | def _enabled_reply_handler(self, is_error, prompt): | ||
2300 | 344 | if self._parent.switch_livepatch.get_active() == self._lps.props.enabled: | ||
2301 | 345 | self._waiting_livepatch_response = False | ||
2302 | 346 | self._trigger_ui_update(skip=True) | ||
2303 | 347 | |||
2304 | 348 | if not self._parent.window_main.is_visible(): | ||
2305 | 349 | self._parent.on_close_button(None) | ||
2306 | 350 | else: | ||
2307 | 351 | if is_error: | ||
2308 | 352 | self._waiting_livepatch_response = False | ||
2309 | 353 | self._trigger_ui_update(skip=True) | ||
2310 | 354 | self._show_error_dialog(prompt) | ||
2311 | 355 | else: | ||
2312 | 356 | # The user tooggled on/off the switch while we were waiting | ||
2313 | 357 | # livepatch to respond back. | ||
2314 | 358 | self._parent.backend.SetLivepatchEnabled( | ||
2315 | 359 | self._parent.switch_livepatch.get_active(), | ||
2316 | 360 | self._auth.token, | ||
2317 | 361 | reply_handler=self._enabled_reply_handler, | ||
2318 | 362 | error_handler=self._enabled_error_handler, | ||
2319 | 363 | timeout=1200) | ||
2320 | 364 | |||
2321 | 365 | def _enabled_error_handler(self, e): | ||
2322 | 366 | self._waiting_livepatch_response = False | ||
2323 | 367 | self._trigger_ui_update(skip=True) | ||
2324 | 368 | |||
2325 | 369 | if e._dbus_error_name == 'com.ubuntu.SoftwareProperties.PermissionDeniedByPolicy': | ||
2326 | 370 | logging.warning("Authentication canceled, changes have not been saved") | ||
2327 | 371 | |||
2328 | 372 | if not self._parent.window_main.is_visible(): | ||
2329 | 373 | self._parent.on_close_button(None) | ||
2330 | 374 | else: | ||
2331 | 375 | self._show_error_dialog(str(e)) | ||
2332 | diff --git a/softwareproperties/gtk/SimpleGtkbuilderApp.py b/softwareproperties/gtk/SimpleGtkbuilderApp.py | |||
2333 | index 06375ff..e14f840 100644 | |||
2334 | --- a/softwareproperties/gtk/SimpleGtkbuilderApp.py | |||
2335 | +++ b/softwareproperties/gtk/SimpleGtkbuilderApp.py | |||
2336 | @@ -38,6 +38,9 @@ class SimpleGtkbuilderApp(Gtk.Application): | |||
2337 | 38 | def on_activate(self, data=None): | 38 | def on_activate(self, data=None): |
2338 | 39 | self.add_window(self.window_main) | 39 | self.add_window(self.window_main) |
2339 | 40 | 40 | ||
2340 | 41 | if not self.window_main.is_visible(): | ||
2341 | 42 | self.window_main.show() | ||
2342 | 43 | |||
2343 | 41 | def run(self): | 44 | def run(self): |
2344 | 42 | """ | 45 | """ |
2345 | 43 | Starts the main loop of processing events checking for Control-C. | 46 | Starts the main loop of processing events checking for Control-C. |
2346 | diff --git a/softwareproperties/gtk/SoftwarePropertiesGtk.py b/softwareproperties/gtk/SoftwarePropertiesGtk.py | |||
2347 | index 75ff85d..9271f54 100644 | |||
2348 | --- a/softwareproperties/gtk/SoftwarePropertiesGtk.py | |||
2349 | +++ b/softwareproperties/gtk/SoftwarePropertiesGtk.py | |||
2350 | @@ -27,9 +27,6 @@ from __future__ import absolute_import, print_function | |||
2351 | 27 | 27 | ||
2352 | 28 | import apt | 28 | import apt |
2353 | 29 | import apt_pkg | 29 | import apt_pkg |
2354 | 30 | import aptsources.distro | ||
2355 | 31 | from datetime import datetime | ||
2356 | 32 | import distro_info | ||
2357 | 33 | import dbus | 30 | import dbus |
2358 | 34 | from gettext import gettext as _ | 31 | from gettext import gettext as _ |
2359 | 35 | import gettext | 32 | import gettext |
2360 | @@ -52,11 +49,9 @@ from .DialogMirror import DialogMirror | |||
2361 | 52 | from .DialogEdit import DialogEdit | 49 | from .DialogEdit import DialogEdit |
2362 | 53 | from .DialogCacheOutdated import DialogCacheOutdated | 50 | from .DialogCacheOutdated import DialogCacheOutdated |
2363 | 54 | from .DialogAddSourcesList import DialogAddSourcesList | 51 | from .DialogAddSourcesList import DialogAddSourcesList |
2366 | 55 | from .DialogLivepatchError import DialogLivepatchError | 52 | from .LivepatchPage import LivepatchPage |
2365 | 56 | from .DialogAuth import DialogAuth | ||
2367 | 57 | 53 | ||
2368 | 58 | import softwareproperties | 54 | import softwareproperties |
2369 | 59 | from softwareproperties.GoaAuth import GoaAuth | ||
2370 | 60 | import softwareproperties.distro | 55 | import softwareproperties.distro |
2371 | 61 | from softwareproperties.SoftwareProperties import SoftwareProperties | 56 | from softwareproperties.SoftwareProperties import SoftwareProperties |
2372 | 62 | import softwareproperties.SoftwareProperties | 57 | import softwareproperties.SoftwareProperties |
2373 | @@ -85,8 +80,6 @@ RESPONSE_ADD = 2 | |||
2374 | 85 | STORE_VISIBLE | 80 | STORE_VISIBLE |
2375 | 86 | ) = list(range(5)) | 81 | ) = list(range(5)) |
2376 | 87 | 82 | ||
2377 | 88 | LIVEPATCH_TIMEOUT = 1200 | ||
2378 | 89 | |||
2379 | 90 | 83 | ||
2380 | 91 | def error(parent_window, summary, msg): | 84 | def error(parent_window, summary, msg): |
2381 | 92 | """ show a error dialog """ | 85 | """ show a error dialog """ |
2382 | @@ -1045,7 +1038,9 @@ class SoftwarePropertiesGtk(SoftwareProperties, SimpleGtkbuilderApp): | |||
2383 | 1045 | d = DialogCacheOutdated(self.window_main, | 1038 | d = DialogCacheOutdated(self.window_main, |
2384 | 1046 | self.datadir) | 1039 | self.datadir) |
2385 | 1047 | d.run() | 1040 | d.run() |
2387 | 1048 | if self.waiting_livepatch_response: | 1041 | |
2388 | 1042 | self.quit_when_livepatch_responds = False | ||
2389 | 1043 | if self.livepatch_page.waiting_livepatch_response: | ||
2390 | 1049 | self.quit_when_livepatch_responds = True | 1044 | self.quit_when_livepatch_responds = True |
2391 | 1050 | self.hide() | 1045 | self.hide() |
2392 | 1051 | else: | 1046 | else: |
2393 | @@ -1483,163 +1478,5 @@ class SoftwarePropertiesGtk(SoftwareProperties, SimpleGtkbuilderApp): | |||
2394 | 1483 | else: | 1478 | else: |
2395 | 1484 | self.label_driver_action.set_label(_("No proprietary drivers are in use.")) | 1479 | self.label_driver_action.set_label(_("No proprietary drivers are in use.")) |
2396 | 1485 | 1480 | ||
2397 | 1486 | # | ||
2398 | 1487 | # Livepatch | ||
2399 | 1488 | # | ||
2400 | 1489 | def init_livepatch(self): | 1481 | def init_livepatch(self): |
2557 | 1490 | self.goa_auth = GoaAuth() | 1482 | self.livepatch_page = LivepatchPage(self) |
2402 | 1491 | self.waiting_livepatch_response = False | ||
2403 | 1492 | self.quit_when_livepatch_responds = False | ||
2404 | 1493 | |||
2405 | 1494 | if not self.is_livepatch_supported(): | ||
2406 | 1495 | self.grid_livepatch.set_visible(False) | ||
2407 | 1496 | return | ||
2408 | 1497 | |||
2409 | 1498 | self.checkbutton_livepatch.set_active(self.is_livepatch_enabled()) | ||
2410 | 1499 | self.on_goa_auth_changed() | ||
2411 | 1500 | |||
2412 | 1501 | # hacky way to monitor if livepatch is enabled or not | ||
2413 | 1502 | file = Gio.File.new_for_path(path=self.LIVEPATCH_RUNNING_FILE) | ||
2414 | 1503 | self.lp_monitor = file.monitor_file(Gio.FileMonitorFlags.NONE) | ||
2415 | 1504 | |||
2416 | 1505 | # connect to signals | ||
2417 | 1506 | self.handlers[self.goa_auth] = \ | ||
2418 | 1507 | self.goa_auth.connect('notify', lambda o, p: self.on_goa_auth_changed()) | ||
2419 | 1508 | self.handlers[self.checkbutton_livepatch] = \ | ||
2420 | 1509 | self.checkbutton_livepatch.connect('toggled', self.on_checkbutton_livepatch_toggled) | ||
2421 | 1510 | self.handlers[self.button_ubuntuone] = \ | ||
2422 | 1511 | self.button_ubuntuone.connect('clicked', self.on_button_ubuntuone_clicked) | ||
2423 | 1512 | self.handlers[self.lp_monitor] = \ | ||
2424 | 1513 | self.lp_monitor.connect('changed', self.on_livepatch_status_changed) | ||
2425 | 1514 | |||
2426 | 1515 | def has_online_accounts(self): | ||
2427 | 1516 | try: | ||
2428 | 1517 | d = Gio.DesktopAppInfo.new('gnome-online-accounts-panel.desktop') | ||
2429 | 1518 | return d != None | ||
2430 | 1519 | except Exception: | ||
2431 | 1520 | return False | ||
2432 | 1521 | |||
2433 | 1522 | def is_livepatch_supported(self): | ||
2434 | 1523 | distro = aptsources.distro.get_distro() | ||
2435 | 1524 | di = distro_info.UbuntuDistroInfo() | ||
2436 | 1525 | return self.has_online_accounts() and \ | ||
2437 | 1526 | di.is_lts(distro.codename) and \ | ||
2438 | 1527 | distro.codename in di.supported(datetime.now().date()) | ||
2439 | 1528 | |||
2440 | 1529 | def on_goa_auth_changed(self): | ||
2441 | 1530 | if self.goa_auth.logged: | ||
2442 | 1531 | self.button_ubuntuone.set_label(_('Sign Out')) | ||
2443 | 1532 | |||
2444 | 1533 | if self.goa_auth.token: | ||
2445 | 1534 | self.checkbutton_livepatch.set_sensitive(True) | ||
2446 | 1535 | self.label_livepatch_login.set_label(_('Signed in as %s' % self.goa_auth.username)) | ||
2447 | 1536 | else: | ||
2448 | 1537 | self.checkbutton_livepatch.set_sensitive(False) | ||
2449 | 1538 | text = _('%s isn\'t authorized to use Livepatch.' % self.goa_auth.username) | ||
2450 | 1539 | text = "<span color='red'>" + text + "</span>" | ||
2451 | 1540 | self.label_livepatch_login.set_markup(text) | ||
2452 | 1541 | else: | ||
2453 | 1542 | if self.is_livepatch_enabled() and not self.waiting_livepatch_response: | ||
2454 | 1543 | # Allow the user to disable livepatch even if | ||
2455 | 1544 | # the account expired (see LP: #1768797) | ||
2456 | 1545 | self.checkbutton_livepatch.set_sensitive(True) | ||
2457 | 1546 | self.label_livepatch_login.set_label(_('Livepatch is active.')) | ||
2458 | 1547 | else: | ||
2459 | 1548 | self.checkbutton_livepatch.set_sensitive(False) | ||
2460 | 1549 | self.label_livepatch_login.set_label(_('To use Livepatch you need to sign in.')) | ||
2461 | 1550 | |||
2462 | 1551 | self.button_ubuntuone.set_label(_('Sign In…')) | ||
2463 | 1552 | |||
2464 | 1553 | def on_livepatch_status_changed(self, file_monitor, file, other_file, event_type): | ||
2465 | 1554 | if not self.waiting_livepatch_response: | ||
2466 | 1555 | self.checkbutton_livepatch.set_active(self.is_livepatch_enabled()) | ||
2467 | 1556 | self.on_goa_auth_changed() | ||
2468 | 1557 | |||
2469 | 1558 | def on_button_ubuntuone_clicked(self, button): | ||
2470 | 1559 | if self.goa_auth.logged: | ||
2471 | 1560 | self.do_logout() | ||
2472 | 1561 | else: | ||
2473 | 1562 | self.do_login() | ||
2474 | 1563 | |||
2475 | 1564 | def do_login(self): | ||
2476 | 1565 | try: | ||
2477 | 1566 | # Show login dialog! | ||
2478 | 1567 | dialog = DialogAuth(self.window_main, self.datadir) | ||
2479 | 1568 | response = dialog.run() | ||
2480 | 1569 | except Exception as e: | ||
2481 | 1570 | logging.error(e) | ||
2482 | 1571 | error(self.window_main, | ||
2483 | 1572 | _("Error enabling Canonical Livepatch"), | ||
2484 | 1573 | _("Please check your Internet connection.")) | ||
2485 | 1574 | else: | ||
2486 | 1575 | if response == Gtk.ResponseType.OK: | ||
2487 | 1576 | self.goa_auth.login(dialog.account) | ||
2488 | 1577 | if self.goa_auth.logged: | ||
2489 | 1578 | self.checkbutton_livepatch.set_active(True) | ||
2490 | 1579 | |||
2491 | 1580 | def do_logout(self): | ||
2492 | 1581 | self.checkbutton_livepatch.set_active(False) | ||
2493 | 1582 | self.goa_auth.logout() | ||
2494 | 1583 | |||
2495 | 1584 | def on_checkbutton_livepatch_toggled(self, checkbutton): | ||
2496 | 1585 | if self.waiting_livepatch_response: | ||
2497 | 1586 | return | ||
2498 | 1587 | |||
2499 | 1588 | self.waiting_livepatch_response = True | ||
2500 | 1589 | |||
2501 | 1590 | token = '' | ||
2502 | 1591 | enabled = False | ||
2503 | 1592 | if self.checkbutton_livepatch.get_active(): | ||
2504 | 1593 | enabled = True | ||
2505 | 1594 | token = self.goa_auth.token if self.goa_auth.token else '' | ||
2506 | 1595 | self.backend.SetLivepatchEnabled(enabled, token, | ||
2507 | 1596 | reply_handler=self.livepatch_enabled_reply_handler, | ||
2508 | 1597 | error_handler=self.livepatch_enabled_error_handler, | ||
2509 | 1598 | timeout=LIVEPATCH_TIMEOUT) | ||
2510 | 1599 | |||
2511 | 1600 | def livepatch_enabled_reply_handler(self, is_error, prompt): | ||
2512 | 1601 | self.sync_checkbutton_livepatch(is_error, prompt) | ||
2513 | 1602 | |||
2514 | 1603 | def livepatch_enabled_error_handler(self, e): | ||
2515 | 1604 | if e._dbus_error_name == 'com.ubuntu.SoftwareProperties.PermissionDeniedByPolicy': | ||
2516 | 1605 | logging.error("Authentication canceled, changes have not been saved") | ||
2517 | 1606 | self.sync_checkbutton_livepatch(is_error=True, prompt=None) | ||
2518 | 1607 | else: | ||
2519 | 1608 | self.sync_checkbutton_livepatch(is_error=True, prompt=str(e)) | ||
2520 | 1609 | |||
2521 | 1610 | def sync_checkbutton_livepatch(self, is_error, prompt): | ||
2522 | 1611 | if is_error: | ||
2523 | 1612 | self.waiting_livepatch_response = False | ||
2524 | 1613 | self.checkbutton_livepatch.handler_block(self.handlers[self.checkbutton_livepatch]) | ||
2525 | 1614 | self.checkbutton_livepatch.set_active(self.is_livepatch_enabled()) | ||
2526 | 1615 | self.checkbutton_livepatch.handler_unblock(self.handlers[self.checkbutton_livepatch]) | ||
2527 | 1616 | |||
2528 | 1617 | if prompt: | ||
2529 | 1618 | dialog = DialogLivepatchError(self.window_main, self.datadir) | ||
2530 | 1619 | response = dialog.run(prompt, show_settings_button=self.quit_when_livepatch_responds) | ||
2531 | 1620 | if response == DialogLivepatchError.RESPONSE_SETTINGS: | ||
2532 | 1621 | self.window_main.show() | ||
2533 | 1622 | self.quit_when_livepatch_responds = False | ||
2534 | 1623 | else: | ||
2535 | 1624 | do_dbus_call = False | ||
2536 | 1625 | if self.is_livepatch_enabled() and not self.checkbutton_livepatch.get_active(): | ||
2537 | 1626 | do_dbus_call = True | ||
2538 | 1627 | enabled = False | ||
2539 | 1628 | token = '' | ||
2540 | 1629 | elif not self.is_livepatch_enabled() and self.checkbutton_livepatch.get_active(): | ||
2541 | 1630 | do_dbus_call = True | ||
2542 | 1631 | enabled = True | ||
2543 | 1632 | token = self.goa_auth.token if self.goa_auth.token else '' | ||
2544 | 1633 | else: | ||
2545 | 1634 | self.waiting_livepatch_response = False | ||
2546 | 1635 | |||
2547 | 1636 | if do_dbus_call: | ||
2548 | 1637 | self.backend.SetLivepatchEnabled(enabled, token, | ||
2549 | 1638 | reply_handler=self.livepatch_enabled_reply_handler, | ||
2550 | 1639 | error_handler=self.livepatch_enabled_error_handler, | ||
2551 | 1640 | timeout=LIVEPATCH_TIMEOUT) | ||
2552 | 1641 | |||
2553 | 1642 | self.on_goa_auth_changed() | ||
2554 | 1643 | |||
2555 | 1644 | if self.quit_when_livepatch_responds: | ||
2556 | 1645 | self.on_close_button(self.button_close) | ||
2558 | diff --git a/softwareproperties/gtk/utils.py b/softwareproperties/gtk/utils.py | |||
2559 | index 37f0c10..e0ddca9 100644 | |||
2560 | --- a/softwareproperties/gtk/utils.py | |||
2561 | +++ b/softwareproperties/gtk/utils.py | |||
2562 | @@ -18,13 +18,19 @@ | |||
2563 | 18 | 18 | ||
2564 | 19 | from __future__ import print_function | 19 | from __future__ import print_function |
2565 | 20 | 20 | ||
2566 | 21 | import aptsources.distro | ||
2567 | 22 | from datetime import datetime | ||
2568 | 23 | import distro_info | ||
2569 | 24 | from functools import wraps | ||
2570 | 21 | import gi | 25 | import gi |
2571 | 22 | gi.require_version("Gtk", "3.0") | 26 | gi.require_version("Gtk", "3.0") |
2573 | 23 | from gi.repository import Gtk | 27 | from gi.repository import Gio, Gtk |
2574 | 24 | 28 | ||
2575 | 25 | import logging | 29 | import logging |
2576 | 26 | LOG=logging.getLogger(__name__) | 30 | LOG=logging.getLogger(__name__) |
2577 | 27 | 31 | ||
2578 | 32 | import time | ||
2579 | 33 | |||
2580 | 28 | def setup_ui(self, path, domain): | 34 | def setup_ui(self, path, domain): |
2581 | 29 | # setup ui | 35 | # setup ui |
2582 | 30 | self.builder = Gtk.Builder() | 36 | self.builder = Gtk.Builder() |
2583 | @@ -37,3 +43,52 @@ def setup_ui(self, path, domain): | |||
2584 | 37 | setattr(self, name, o) | 43 | setattr(self, name, o) |
2585 | 38 | else: | 44 | else: |
2586 | 39 | logging.debug("can not get name for object '%s'" % o) | 45 | logging.debug("can not get name for object '%s'" % o) |
2587 | 46 | |||
2588 | 47 | def has_gnome_online_accounts(): | ||
2589 | 48 | try: | ||
2590 | 49 | d = Gio.DesktopAppInfo.new('gnome-online-accounts-panel.desktop') | ||
2591 | 50 | return d != None | ||
2592 | 51 | except Exception: | ||
2593 | 52 | return False | ||
2594 | 53 | |||
2595 | 54 | def is_current_distro_lts(): | ||
2596 | 55 | distro = aptsources.distro.get_distro() | ||
2597 | 56 | di = distro_info.UbuntuDistroInfo() | ||
2598 | 57 | return di.is_lts(distro.codename) | ||
2599 | 58 | |||
2600 | 59 | def is_current_distro_supported(): | ||
2601 | 60 | distro = aptsources.distro.get_distro() | ||
2602 | 61 | di = distro_info.UbuntuDistroInfo() | ||
2603 | 62 | return distro.codename in di.supported(datetime.now().date()) | ||
2604 | 63 | |||
2605 | 64 | def retry(exceptions, tries=10, delay=0.1, backoff=2): | ||
2606 | 65 | """ | ||
2607 | 66 | Retry calling the decorated function using an exponential backoff. | ||
2608 | 67 | |||
2609 | 68 | Args: | ||
2610 | 69 | exceptions: The exception to check. may be a tuple of | ||
2611 | 70 | exceptions to check. | ||
2612 | 71 | tries: Number of times to try (not retry) before giving up. | ||
2613 | 72 | delay: Initial delay between retries in seconds. | ||
2614 | 73 | backoff: Backoff multiplier (e.g. value of 2 will double the delay | ||
2615 | 74 | each retry). | ||
2616 | 75 | """ | ||
2617 | 76 | def deco_retry(f): | ||
2618 | 77 | |||
2619 | 78 | @wraps(f) | ||
2620 | 79 | def f_retry(*args, **kwargs): | ||
2621 | 80 | mtries, mdelay = tries, delay | ||
2622 | 81 | while mtries > 1: | ||
2623 | 82 | try: | ||
2624 | 83 | return f(*args, **kwargs) | ||
2625 | 84 | except exceptions as e: | ||
2626 | 85 | msg = '{}, Retrying in {} seconds...'.format(e, mdelay) | ||
2627 | 86 | logging.warning(msg) | ||
2628 | 87 | time.sleep(mdelay) | ||
2629 | 88 | mtries -= 1 | ||
2630 | 89 | mdelay *= backoff | ||
2631 | 90 | return f(*args, **kwargs) | ||
2632 | 91 | |||
2633 | 92 | return f_retry # true decorator | ||
2634 | 93 | |||
2635 | 94 | return deco_retry | ||
2636 | diff --git a/tests/aptroot/etc/apt/apt.conf.d/.keep b/tests/aptroot/etc/apt/apt.conf.d/.keep | |||
2637 | 40 | new file mode 100644 | 95 | new file mode 100644 |
2638 | index 0000000..e69de29 | |||
2639 | --- /dev/null | |||
2640 | +++ b/tests/aptroot/etc/apt/apt.conf.d/.keep |
Thanks, the code changes look fine it works as expected (tested with CVE applied, they were correctly listed)