Merge lp:~jeremy-munsch/synapse-project/virtualbox-plugin into lp:synapse-project

Proposed by Jeremy Munsch
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
Reviewer Review Type Date Requested Status
Rico Tzschichholz Pending
Review via email: mp+278244@code.launchpad.net

Description of the change

Search for virtual machines and provides actions for it
using libxmlbird since librest xml is buggy/harassing to work with.

To post a comment you must log in.
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

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