39
39
public class Runner: GLib.Object
42
private Unity.ApplicationsPlace.Daemon daemon;
43
private const string BUS_NAME_PREFIX = "com.canonical.Unity.ApplicationsPlace.Runner";
42
private Unity.ApplicationsLens.Daemon daemon;
43
private const string BUS_NAME_PREFIX = "com.canonical.Unity.ApplicationsLens.Runner";
45
45
/* for now, load the same keys as gnome-panel */
46
46
private const string HISTORY_KEY = "history";
47
47
private const int MAX_HISTORY = 10;
49
public Unity.PlaceEntryInfo place_entry;
49
public Unity.Lens lens;
50
public Unity.Scope scope;
51
52
private bool all_models_synced;
53
54
/* We remember the previous search so we can figure out if we should do
54
55
* incremental filtering of the result models */
55
private PlaceSearch? previous_search;
56
private LensSearch? previous_search;
57
58
private Gee.HashMap<string,AboutEntry> about_entries;
58
59
private Gee.List<string> executables;
63
64
private EquationParser parser;
65
public Runner (Unity.ApplicationsPlace.Daemon daemon)
66
public Runner (Unity.ApplicationsLens.Daemon daemon)
67
var sections_model = new Dee.SharedModel(BUS_NAME_PREFIX + "SectionsModel");
68
sections_model.set_schema ("s", "s");
70
var groups_model = new Dee.SharedModel(BUS_NAME_PREFIX + "GroupsModel");
71
groups_model.set_schema ("s", "s", "s");
73
var results_model = new Dee.SharedModel(BUS_NAME_PREFIX + "ResultsModel");
74
results_model.set_schema ("s", "s", "u", "s", "s", "s");
76
place_entry = new PlaceEntryInfo ("/com/canonical/unity/applicationsplace/runner");
77
place_entry.sections_model = sections_model;
78
place_entry.entry_renderer_info.groups_model = groups_model;
79
place_entry.entry_renderer_info.results_model = results_model;
68
/* First create scope */
69
scope = new Unity.Scope ("/com/canonical/unity/scope/commands");
70
scope.search_in_global = false;
72
/* Listen for changes to the lens scope search */
73
scope.notify["active-search"].connect (
75
var search = scope.active_search;
77
if (!Utils.search_has_really_changed (previous_search, search))
80
update_search.begin(search);
81
previous_search = search;
85
/* Re-do the search if the filters changed */
86
scope.filters_changed.connect (
88
var search = scope.active_search;
89
if (search.search_string == null)
92
update_search.begin(search);
93
previous_search = search;
98
// Listen for when the lens is hidden/shown by the Unity Shell
99
scope.notify["active"].connect(
103
var search = scope.active_search;
104
update_search.begin(search);
105
previous_search = search;
110
scope.activate_uri.connect (daemon.activate);
112
/* Now create Lens */
113
lens = new Unity.Lens ("/com/canonical/unity/lens/commands", "commands");
114
lens.search_hint = _("Run a command");
115
lens.visible = false;
116
lens.search_in_global = false;
117
populate_categories ();
118
//populate_filters();
119
lens.add_local_scope (scope);
81
121
previous_search = null;
83
123
/* create the private about entries */
87
127
executables = new Gee.ArrayList<string> ();
88
128
find_system_executables.begin ();
90
130
this.gp_settings = new Settings ("com.canonical.Unity.Runner");
91
131
history = new Gee.ArrayList<string> ();
94
134
parser = new EquationParser ();
96
/* Listen for changes to the place entry search */
97
place_entry.notify["active-search"].connect (
99
if (!all_models_synced)
102
var search = place_entry.active_search;
104
if (!Utils.search_has_really_changed (previous_search, search))
107
update_search.begin(search);
108
previous_search = search;
112
// Listen for when the place is hidden/shown by the Unity Shell
113
place_entry.notify["active"].connect(
115
if (place_entry.active)
117
var search = place_entry.active_search;
118
update_search.begin(search);
119
previous_search = search;
124
/* We should not start manipulating any of our models before they are
125
* all synchronized. When they are we set all_models_synced = true */
126
sections_model.notify["synchronized"].connect (check_models_synced);
127
groups_model.notify["synchronized"].connect (check_models_synced);
128
results_model.notify["synchronized"].connect (check_models_synced);
129
all_models_synced = false;
131
136
this.daemon = daemon;
135
private async void update_search (PlaceSearch? search)
137
var model = place_entry.entry_renderer_info.results_model;
140
private void populate_categories ()
142
Unity.Category[] categories = {};
144
var cat = new Unity.Category (_("Results"),
145
ICON_PATH + "category-installed.svg");
148
cat = new Unity.Category (_("History"),
149
ICON_PATH + "category-available.svg");
153
lens.categories = categories;
156
private async void update_search (LensSearch? search)
158
var model = scope.results_model;
140
var search_string = search.get_search_string ();
161
var search_string = search.search_string;
141
162
bool has_search = !Utils.search_is_invalid (search);
143
164
/* Prevent concurrent searches and concurrent updates of our models,
144
165
* by preventing any notify signals from propagating to us.
145
* Important: Remember to thaw the notifys with release_entrylock()! */
146
place_entry.freeze_notify ();
166
* Important: Remember to thaw the notifys with release_scopelock()! */
167
scope.freeze_notify ();
150
display_history (ref model);
151
release_entrylock ();
171
display_history (ref model);
172
release_scopelock ();
152
173
search.finished ();
183
204
dirs_match.size, executables_match.size, timer.elapsed ()*1000, search_string);
186
release_entrylock ();
207
release_scopelock ();
187
208
search.finished ();
190
/* TODO : once computed, let's do something with the result (i.e copy it to the input line,
191
* or copy it to the system buffer) */
192
private bool process_computation (ref string search_string, ref Dee.Model model, out double result)
211
private bool process_computation (ref string search_string, ref Dee.SharedModel model, out double result)
194
213
if (search_string.has_prefix ("="))
270
private void process_command (ref string search_string, ref Dee.Model model, ref Gee.ArrayList<string> dirs_match, ref Gee.ArrayList<string> executables_match)
289
private void process_command (ref string search_string, ref Dee.SharedModel model, ref Gee.ArrayList<string> dirs_match, ref Gee.ArrayList<string> executables_match)
275
294
string display_name;
276
var group_id = RunnerGroup.HISTORY;
295
var category_id = RunnerCategory.HISTORY;
278
display_history (ref model);
297
display_history (ref model);
280
299
/* manual seek with directory and executables result */
281
300
if (search_string.has_prefix ("/") || search_string.has_prefix ("~"))
341
361
executables_match.sort ();
342
362
dirs_match.sort ();
344
group_id = RunnerGroup.RESULTS;
364
category_id = RunnerCategory.RESULTS;
346
366
// populate results
347
// 1. enable launching the exact search string
348
display_name = get_icon_uri_and_mimetype (search_string, out icon, out uri, out mimetype);
349
model.append (uri.strip (), icon.to_string (),
367
// 1. enable launching the exact search string if no other result
368
if ((executables_match.size == 0) && (dirs_match.size == 0))
370
display_name = get_icon_uri_and_mimetype (search_string, out icon, out uri, out mimetype);
371
model.append (uri.strip (), icon.to_string (),
372
category_id, mimetype,
354
377
// 2. add possible directories (we don't store them)
355
378
mimetype = "inode/directory";
371
394
display_name = get_icon_uri_and_mimetype (final_exec, out icon, out uri, out mimetype);
373
396
model.append (uri, icon.to_string (),
397
category_id, mimetype,
380
private bool process_easter (ref string search_string, ref Dee.Model model)
403
private bool process_easter (ref string search_string, ref Dee.SharedModel model)
405
/* no easter egg in unity */
382
406
if (search_string == "free the fish")
384
408
string uri = "no-easter-egg";
407
private void display_history (ref Dee.Model model)
431
private void display_history (ref Dee.SharedModel model)
412
436
string display_name;
413
var group_id = RunnerGroup.HISTORY;
437
var category_id = RunnerCategory.HISTORY;
415
439
foreach (var command in this.history)
417
441
display_name = get_icon_uri_and_mimetype (command, out icon, out uri, out mimetype);
418
442
model.append (uri, icon.to_string (),
443
category_id, mimetype,
425
private void release_entrylock ()
449
private void release_scopelock ()
427
451
/* Allow new searches once we enter an idle again.
428
452
* We don't do it directly from here as that could mean we start
429
453
* changing the model even before we had flushed out current changes
431
455
Idle.add (() => {
432
place_entry.thaw_notify ();
456
scope.thaw_notify ();
512
536
return exec_string;
517
/* The check_models_synced() method acts like a latch - once all models
518
* have reported themselves to be synchronized we set
519
* all_models_synced = true and tell the searches to re-check their state
520
* as they should refuse to run when all_models_synced == false */
521
private void check_models_synced (Object obj, ParamSpec pspec)
523
if ((place_entry.sections_model as Dee.SharedModel).synchronized &&
524
(place_entry.entry_renderer_info.groups_model as Dee.SharedModel).synchronized &&
525
(place_entry.entry_renderer_info.results_model as Dee.SharedModel).synchronized) {
526
if (all_models_synced == false)
528
all_models_synced = true;
530
populate_groups (place_entry.entry_renderer_info.groups_model);
532
/* Emitting notify here will make us recheck if the search results
533
* need update. In the negative case this is a noop */
534
place_entry.notify_property ("active-search");
539
private void populate_groups (Dee.Model groups)
541
if (groups.get_n_rows() != 0)
543
debug ("The groups model already populated. We probably cloned it off Unity. Rebuilding.");
548
groups.append ("UnityDefaultRenderer",
550
ICON_PATH + "group-installed.svg");
551
groups.append ("UnityDefaultRenderer",
553
ICON_PATH + "group-available.svg");
555
/* expand the history */
556
place_entry.entry_renderer_info.set_hint ("ExpandedGroups",
557
@"$((uint)RunnerGroup.HISTORY)");
560
541
public void add_history (string last_command)