Merge lp:~jeremy-munsch/synapse-project/virtualbox-plugin into lp:synapse-project
- virtualbox-plugin
- Merge into trunk
Status: | Needs review | ||||||||
---|---|---|---|---|---|---|---|---|---|
Proposed branch: | lp:~jeremy-munsch/synapse-project/virtualbox-plugin | ||||||||
Merge into: | lp:synapse-project | ||||||||
Diff against target: |
499 lines (+443/-1) 5 files modified
configure.ac (+3/-1) debian/control (+1/-0) src/plugins/Makefile.am (+1/-0) src/plugins/virtualbox-plugin.vala (+437/-0) src/ui/synapse-main.vala (+1/-0) |
||||||||
To merge this branch: | bzr merge lp:~jeremy-munsch/synapse-project/virtualbox-plugin | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Rico Tzschichholz | Pending | ||
Review via email: mp+278244@code.launchpad.net |
Commit message
Description of the change
Search for virtual machines and provides actions for it
using libxmlbird since librest xml is buggy/harassing to work with.
- 634. By Rico Tzschichholz
-
ui/controller: Some optimization accessing model.query[] in search*()
- 635. By Rico Tzschichholz
-
core/query: Cache and reuse Regex.escape_string (query)
- 636. By Rico Tzschichholz
-
debian: Bump build-deps accordingly
- 637. By Rico Tzschichholz
-
plugins: Add suport for MATE Screensaver
- 638. By Rico Tzschichholz
-
po: Update translations
- 639. By Rico Tzschichholz
-
desktop-
file-plugin: correct ApplicationsMatch to DesktopFileMatch - 640. By Rico Tzschichholz
-
src: Drop "using Gee;"
- 641. By Rico Tzschichholz
-
src: Drop "using Json;"
- 642. By Rico Tzschichholz
-
src: Drop "using Zeitgeist;"
- 643. By Rico Tzschichholz
-
src: Drop "using Gtk;"
- 644. By Rico Tzschichholz
-
src: Drop "using Cairo;"
- 645. By Rico Tzschichholz
-
src: Drop last usages of "using *;"
- 646. By Rico Tzschichholz
-
ui: Show release-name in about-dialog only
- 647. By Rico Tzschichholz
-
ui: Bump to 2016
- 648. By Rico Tzschichholz
-
Release 0.2.99.2
- 649. By Rico Tzschichholz
-
ui: Set and update client-window of Gtk.IMMulticontext
- 650. By Rico Tzschichholz
-
main: Properly construct OptionEntry
Stop using an empty string where is getting treated as translateable.
- 651. By Michael Aquilina
-
Add pass plugin
- 652. By Rico Tzschichholz
-
Fix non-matching generic types
- 653. By Rico Tzschichholz
-
Fix non-matching generic types of interface implementations
- 654. By Jeremy Munsch
-
add virtualbox plugin
add libxmlbird dependency
Unmerged revisions
- 654. By Jeremy Munsch
-
add virtualbox plugin
add libxmlbird dependency
Preview Diff
1 | === modified file 'configure.ac' |
2 | --- configure.ac 2016-02-28 12:40:08 +0000 |
3 | +++ configure.ac 2016-12-16 21:07:04 +0000 |
4 | @@ -62,7 +62,8 @@ |
5 | gee-0.8 >= $MIN_GEE_VERSION \ |
6 | json-glib-1.0 >= $MIN_JSON_VERSION \ |
7 | keybinder-3.0 \ |
8 | - libnotify |
9 | + libnotify \ |
10 | + xmlbird |
11 | ) |
12 | SYNAPSE_MODULES_VALAFLAGS=" --pkg gdk-x11-3.0 \ |
13 | --pkg gtk+-3.0 \ |
14 | @@ -71,6 +72,7 @@ |
15 | --pkg gee-0.8 \ |
16 | --pkg json-glib-1.0 \ |
17 | --pkg keybinder-3.0 \ |
18 | + --pkg xmlbird \ |
19 | --pkg libnotify" |
20 | AC_SUBST(SYNAPSE_MODULES_VALAFLAGS) |
21 | |
22 | |
23 | === modified file 'debian/control' |
24 | --- debian/control 2015-11-30 14:47:10 +0000 |
25 | +++ debian/control 2016-12-16 21:07:04 +0000 |
26 | @@ -15,6 +15,7 @@ |
27 | libkeybinder-3.0-dev, |
28 | libnotify-dev, |
29 | librest-dev, |
30 | + libxmlbird-dev, |
31 | libappindicator3-dev (>= 0.0.7) |
32 | Vcs-Bzr: http://bazaar.launchpad.net/~synapse-core/synapse-project/trunk/ |
33 | Vcs-Browser: http://bazaar.launchpad.net/~synapse-core/synapse-project/trunk/files |
34 | |
35 | === modified file 'src/plugins/Makefile.am' |
36 | --- src/plugins/Makefile.am 2016-04-01 05:58:51 +0000 |
37 | +++ src/plugins/Makefile.am 2016-12-16 21:07:04 +0000 |
38 | @@ -53,6 +53,7 @@ |
39 | test-slow-plugin.vala \ |
40 | xnoise-media-player-plugin.vala \ |
41 | system-management.vala \ |
42 | + virtualbox-plugin.vala \ |
43 | zeal-plugin.vala \ |
44 | $(NULL) |
45 | |
46 | |
47 | === added file 'src/plugins/virtualbox-plugin.vala' |
48 | --- src/plugins/virtualbox-plugin.vala 1970-01-01 00:00:00 +0000 |
49 | +++ src/plugins/virtualbox-plugin.vala 2016-12-16 21:07:04 +0000 |
50 | @@ -0,0 +1,437 @@ |
51 | +/* |
52 | + * Copyright (C) 2015 Jérémy Munsch <jeremy.munsch@gmail.com> |
53 | + * |
54 | + * This program is free software; you can redistribute it and/or modify |
55 | + * it under the terms of the GNU General Public License as published by |
56 | + * the Free Software Foundation; either version 2 of the License, or |
57 | + * (at your option) any later version. |
58 | + * |
59 | + * This program is distributed in the hope that it will be useful, |
60 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
61 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
62 | + * GNU General Public License for more details. |
63 | + * |
64 | + * You should have received a copy of the GNU General Public License |
65 | + * along with this program; if not, write to the Free Software |
66 | + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
67 | + * |
68 | + * Note : |
69 | + * Using libxmlbird since librest is buggy/pain to use |
70 | + * |
71 | + * What is does : |
72 | + * Search hosts virtual machines and provides |
73 | + * some actions took from virtualbox --help |
74 | + */ |
75 | + |
76 | +using B; |
77 | + |
78 | +namespace Synapse |
79 | +{ |
80 | + private errordomain VMParseError { ERROR } |
81 | + |
82 | + public class VirtualBoxPlugin : Object, Activatable, ItemProvider, ActionProvider |
83 | + { |
84 | + public bool enabled { get; set; default = true; } |
85 | + |
86 | + public static string virtualbox_config_dir { get; private set; default = "VirtualBox"; } |
87 | + public static string virtualbox_vm_dir { get; private set; } |
88 | + |
89 | + private Gee.List<VirtualBoxMachineMatch> machines; |
90 | + private Gee.List<VirtualBoxAction> actions; |
91 | + |
92 | + public void activate () |
93 | + { |
94 | + parse_vm_config.begin (); |
95 | + } |
96 | + |
97 | + public void deactivate () |
98 | + { |
99 | + } |
100 | + |
101 | + static void register_plugin () |
102 | + { |
103 | + PluginRegistry.get_default ().register_plugin ( |
104 | + typeof (VirtualBoxPlugin), |
105 | + _("VirtualBox"), |
106 | + _("Control you virtual machines"), |
107 | + "virtualbox", |
108 | + register_plugin, |
109 | + Environment.find_program_in_path ("virtualbox") != null, |
110 | + _("Unable to find \"virtualbox\" program") |
111 | + ); |
112 | + } |
113 | + |
114 | + static construct |
115 | + { |
116 | + register_plugin (); |
117 | + virtualbox_config_dir = Path.build_filename (Environment.get_user_config_dir (), virtualbox_config_dir); |
118 | + } |
119 | + |
120 | + construct |
121 | + { |
122 | + machines = new Gee.ArrayList<VirtualBoxMachineMatch> (); |
123 | + actions = new Gee.ArrayList<VirtualBoxAction> (); |
124 | + actions.add (new StartVirtualMachine ()); |
125 | + actions.add (new StartFullscreenVirtualMachine ()); |
126 | + actions.add (new StartSeemlessVirtualMachine ()); |
127 | + actions.add (new StartScaleVirtualMachine ()); |
128 | + actions.add (new RestoreVirtualMachine ()); |
129 | + actions.add (new StartPausedVirtualMachine ()); |
130 | + actions.add (new StartSeparateVirtualMachine ()); |
131 | + } |
132 | + |
133 | + public bool handles_query (Query query) |
134 | + { |
135 | + return (QueryFlags.APPLICATIONS in query.query_type); |
136 | + } |
137 | + |
138 | + public async ResultSet? search (Query q) throws SearchError |
139 | + { |
140 | + var results = new ResultSet (); |
141 | + |
142 | + var matchers = Query.get_matchers_for_query (q.query_string, MatcherFlags.NO_FUZZY, RegexCompileFlags.OPTIMIZE | RegexCompileFlags.CASELESS); |
143 | + foreach (var matcher in matchers) |
144 | + { |
145 | + foreach (var machine in machines) |
146 | + { |
147 | + q.check_cancellable (); |
148 | + if (matcher.key.match (machine.title)) |
149 | + { |
150 | + results.add (machine, matcher.value); |
151 | + } |
152 | + } |
153 | + } |
154 | + return results; |
155 | + } |
156 | + |
157 | + public ResultSet? find_for_match (ref Query q, Match match) |
158 | + { |
159 | + if (!actions[0].valid_for_match (match)) return null; |
160 | + |
161 | + var matchers = Query.get_matchers_for_query (q.query_string, 0, RegexCompileFlags.OPTIMIZE | RegexCompileFlags.CASELESS); |
162 | + |
163 | + var results = new ResultSet (); |
164 | + foreach (var matcher in matchers) |
165 | + { |
166 | + foreach (var action in actions) |
167 | + { |
168 | + if (matcher.key.match (action.title)) |
169 | + { |
170 | + int relevancy = (match is StartVirtualMachine) ? |
171 | + action.default_relevancy : |
172 | + action.default_relevancy - MatchScore.INCREMENT_MINOR; |
173 | + results.add (action, relevancy); |
174 | + } |
175 | + } |
176 | + } |
177 | + return results; |
178 | + } |
179 | + |
180 | + |
181 | + /** |
182 | + * Actions |
183 | + */ |
184 | + |
185 | + private abstract class VirtualBoxAction : Action |
186 | + { |
187 | + public abstract string command { get; set; } |
188 | + public new virtual int default_relevancy { get; set; default = MatchScore.AVERAGE; } |
189 | + |
190 | + public override bool valid_for_match (Match match) |
191 | + { |
192 | + return (match is VirtualBoxMachineMatch); |
193 | + } |
194 | + |
195 | + public override void do_execute (Match match, Match? target = null) |
196 | + { |
197 | + string cmd = command.printf (((VirtualBoxMachineMatch)match).uuid); |
198 | + try |
199 | + { |
200 | + AppInfo ai = AppInfo.create_from_commandline (cmd, "virtualbox", 0); |
201 | + if (!ai.launch (null, null)) |
202 | + warning ("Could not launch VirtualBox %s".printf (cmd)); |
203 | + } |
204 | + catch (Error err) |
205 | + { |
206 | + warning ("Could not launch %s %s", cmd, err.message); |
207 | + } |
208 | + } |
209 | + } |
210 | + |
211 | + private class StartVirtualMachine : VirtualBoxAction |
212 | + { |
213 | + public override string command { get; set; default = "virtualbox --startvm %s"; } |
214 | + public StartVirtualMachine () |
215 | + { |
216 | + Object (title: _("Turn on"), |
217 | + description: _("Start the virtual machine"), |
218 | + icon_name: "virtualbox", |
219 | + has_thumbnail: false); |
220 | + default_relevancy = MatchScore.HIGHEST; |
221 | + } |
222 | + } |
223 | + |
224 | + private class StartFullscreenVirtualMachine : VirtualBoxAction |
225 | + { |
226 | + public override string command { get; set; default = "virtualbox --startvm %s --fullscreen"; } |
227 | + public StartFullscreenVirtualMachine () |
228 | + { |
229 | + Object (title: _("Fullscreen"), |
230 | + description: _("Start the virtual machine in fullscreen mode"), |
231 | + icon_name: "virtualbox", |
232 | + has_thumbnail: true); |
233 | + } |
234 | + } |
235 | + |
236 | + private class StartSeemlessVirtualMachine : VirtualBoxAction |
237 | + { |
238 | + public override string command { get; set; default = "virtualbox --startvm %s --seamless"; } |
239 | + public StartSeemlessVirtualMachine () |
240 | + { |
241 | + Object (title: _("Seamless"), |
242 | + description: _("Start the virtual machine in seamless mode"), |
243 | + icon_name: "virtualbox", |
244 | + has_thumbnail: false); |
245 | + } |
246 | + } |
247 | + |
248 | + private class StartScaleVirtualMachine : VirtualBoxAction |
249 | + { |
250 | + public override string command { get; set; default = "virtualbox --startvm %s --scale"; } |
251 | + public StartScaleVirtualMachine () |
252 | + { |
253 | + Object (title: _("Scale"), |
254 | + description: _("Start the virtual machine in scale mode"), |
255 | + icon_name: "virtualbox", |
256 | + has_thumbnail: false); |
257 | + } |
258 | + } |
259 | + |
260 | + private class RestoreVirtualMachine : VirtualBoxAction |
261 | + { |
262 | + public override string command { get; set; default = "virtualbox --startvm %s --restore"; } |
263 | + public RestoreVirtualMachine () |
264 | + { |
265 | + Object (title: _("Restore"), |
266 | + description: _("Restore the last vitual machine state"), |
267 | + icon_name: "virtualbox", |
268 | + has_thumbnail: false); |
269 | + } |
270 | + } |
271 | + |
272 | + private class StartPausedVirtualMachine : VirtualBoxAction |
273 | + { |
274 | + public override string command { get; set; default = "virtualbox --startvm %s --start-paused"; } |
275 | + public StartPausedVirtualMachine () |
276 | + { |
277 | + Object (title: _("Start Paused"), |
278 | + description: _("Start the virtual machine in paused state"), |
279 | + icon_name: "virtualbox", |
280 | + has_thumbnail: false); |
281 | + } |
282 | + } |
283 | + |
284 | + private class StartSeparateVirtualMachine : VirtualBoxAction |
285 | + { |
286 | + public override string command { get; set; default = "virtualbox --startvm %s --separate"; } |
287 | + public StartSeparateVirtualMachine () |
288 | + { |
289 | + Object (title: _("Start Separated"), |
290 | + description: _("Start the virtual machine in a separate process"), |
291 | + icon_name: "virtualbox", |
292 | + has_thumbnail: false); |
293 | + } |
294 | + } |
295 | + |
296 | + |
297 | + /** |
298 | + * Match |
299 | + */ |
300 | + |
301 | + private class VirtualBoxMachineMatch : Match |
302 | + { |
303 | + public string name { get; set; } |
304 | + public string uuid { get; set; } |
305 | + public string src { get; set; } |
306 | + public string os_type { get; set; } |
307 | + |
308 | + public VirtualBoxMachineMatch (string name, string uuid, string src, string os_type) |
309 | + { |
310 | + Object (title: "VM %s %s".printf (name, os_type), |
311 | + description : "", |
312 | + icon_name: "virtualbox", |
313 | + has_thumbnail: false); |
314 | + this.uuid = uuid; |
315 | + this.src = src; |
316 | + this.os_type = os_type; |
317 | + } |
318 | + } |
319 | + |
320 | + |
321 | + /** |
322 | + * private methods |
323 | + */ |
324 | + |
325 | + private async void parse_vm_config () |
326 | + { |
327 | + try |
328 | + { |
329 | + Gee.List<Tag> results = new Gee.ArrayList<Tag> (); |
330 | + string vm_config_file = Path.build_filename (virtualbox_config_dir, "VirtualBox.xml"); |
331 | + |
332 | + Tag root = parse_xml_file (vm_config_file); |
333 | + |
334 | + if (null == root) |
335 | + throw new VMParseError.ERROR ("Failed to parse VirtualBox config file %s".printf (vm_config_file)); |
336 | + |
337 | + find_all_tags ("MachineEntry", root, ref results); |
338 | + |
339 | + foreach (var machine_entry in results) |
340 | + { |
341 | + string uuid = get_attribute ("uuid", machine_entry) ?? null; |
342 | + string src = get_attribute ("src", machine_entry) ?? null; |
343 | + |
344 | + if (null == uuid || null == src) |
345 | + throw new VMParseError.ERROR ("Could not get uuid %s or src %s from MachineEntry %s".printf (uuid, src, vm_config_file)); |
346 | + |
347 | + Idle.add_full (Priority.DEFAULT_IDLE, parse_vm_config.callback); |
348 | + yield; |
349 | + |
350 | + Tag machine_tag = parse_xml_file (src); |
351 | + |
352 | + if (null == machine_tag) |
353 | + { |
354 | + message ("Failed to parse VirtualBox config file %s".printf (src)); |
355 | + continue; |
356 | + } |
357 | + |
358 | + machine_tag = find_first_tag ("Machine", machine_tag); |
359 | + |
360 | + if (null == machine_tag) |
361 | + { |
362 | + message ("Could not get Machine from %s".printf (src)); |
363 | + continue; |
364 | + } |
365 | + |
366 | + string name = get_attribute ("name", machine_tag) ?? null; |
367 | + string os_type = get_attribute ("OSType", machine_tag) ?? null; |
368 | + |
369 | + if (null == name || null == os_type) |
370 | + { |
371 | + message ("Could not get name %s or ostype %s from MachineEntry %s".printf (name, os_type, src)); |
372 | + continue; |
373 | + } |
374 | + |
375 | + machines.add (new VirtualBoxMachineMatch (name, uuid, src, parse_os (os_type))); |
376 | + } |
377 | + } |
378 | + catch (Error e) |
379 | + { |
380 | + warning ("%s".printf (e.message)); |
381 | + } |
382 | + } |
383 | + |
384 | + private string parse_os (string os_info) |
385 | + { |
386 | + int _pos = os_info.index_of_char ('_'); |
387 | + // 0: arch, 1: os, 2: version |
388 | + string os[3] = { -1 != _pos ? os_info.slice (_pos, os_info.length).replace ("_", "x") : "", |
389 | + -1 != _pos ? os_info.slice (0, _pos).replace ("_", "x") : os_info, |
390 | + "" }; |
391 | + try |
392 | + { |
393 | + os[2] = (new Regex ("[a-zA-Z]")).replace (os[1], os[1].length, 0, ""); |
394 | + os[1] = os[1].replace (os[2], ""); |
395 | + switch (os[1]) |
396 | + { |
397 | + case "Linux": |
398 | + if (os[2].length > 1) // eg: 22 -> 2.2 |
399 | + os[2] = os[2].substring (0, 1) + "." + os[2].substring (1); |
400 | + break; |
401 | + case "MacOS": |
402 | + if (os[2].length > 2) // eg: 106 -> 10.6 |
403 | + os[2] = os[2].substring (0, 2) + "." + os[2].substring (2); |
404 | + break; |
405 | + case "Windows": |
406 | + if ("81" == os[2]) |
407 | + os[2] = "8.1"; |
408 | + break; |
409 | + case "L": |
410 | + os[1] = "L4"; |
411 | + os[2] = ""; |
412 | + break; |
413 | + case "OSx": // IBM |
414 | + os[1] = "OS/x"; |
415 | + os[2] = "21"; |
416 | + break; |
417 | + case "OS2Warp45": // IBM |
418 | + os[1] = "OS/2 Warp"; |
419 | + os[2] = "45"; |
420 | + break; |
421 | + case "OS2Warp4": // IBM |
422 | + os[1] = "OS/2 Warp"; |
423 | + os[2] = "4"; |
424 | + break; |
425 | + case "OSeCS": // IBM |
426 | + os[1] = "OS/2 eCS"; |
427 | + os[2] = ""; |
428 | + break; |
429 | + } |
430 | + } |
431 | + catch (Error e) |
432 | + { |
433 | + warning ("%s".printf (e.message)); |
434 | + } |
435 | + return "%s %s %s%s".printf (os[1], os[2], string.nfill (3-os[0].length, ' '), os[0]); |
436 | + } |
437 | + |
438 | + /** |
439 | + * Xml methods |
440 | + */ |
441 | + |
442 | + private void find_all_tags (string tag_name, Tag root, ref Gee.List<Tag> results) |
443 | + { |
444 | + foreach (Tag tag in root) { |
445 | + if (tag_name == tag.get_name ()) |
446 | + results.add (tag); |
447 | + else |
448 | + find_all_tags (tag_name, tag, ref results); |
449 | + } |
450 | + } |
451 | + |
452 | + private Tag? find_first_tag (string tag_name, Tag root) |
453 | + { |
454 | + foreach (Tag tag in root) { |
455 | + if (tag_name == tag.get_name ()) |
456 | + return tag; |
457 | + else |
458 | + find_first_tag (tag_name, tag); |
459 | + } |
460 | + return null; |
461 | + } |
462 | + |
463 | + private string? get_attribute (string attribute_name, Tag tag) { |
464 | + foreach (Attribute attribute in tag.get_attributes ()) { |
465 | + if (attribute_name == attribute.get_name ()) |
466 | + return attribute.get_content (); |
467 | + } |
468 | + return null; |
469 | + } |
470 | + |
471 | + private Tag parse_xml_file (string file) throws FileError, VMParseError |
472 | + { |
473 | + size_t length; |
474 | + string read; |
475 | + FileUtils.get_contents (file, out read, out length); |
476 | + |
477 | + XmlParser parser = new XmlParser (read); |
478 | + |
479 | + if (!parser.validate ()) |
480 | + { |
481 | + throw new VMParseError.ERROR ("Invalid XML %s".printf (file)); |
482 | + } |
483 | + |
484 | + return parser.get_root_tag (); |
485 | + } |
486 | + } |
487 | +} |
488 | |
489 | === modified file 'src/ui/synapse-main.vala' |
490 | --- src/ui/synapse-main.vala 2016-04-01 05:58:51 +0000 |
491 | +++ src/ui/synapse-main.vala 2016-12-16 21:07:04 +0000 |
492 | @@ -183,6 +183,7 @@ |
493 | #endif |
494 | #if HAVE_LIBREST |
495 | typeof (ImgUrPlugin), |
496 | + typeof (VirtualBoxPlugin), |
497 | #endif |
498 | // action-only plugins |
499 | typeof (DevhelpPlugin), |