1 /*
2  * applications-search-provider: Search provider for searching installed
3  *                               applications
4  *
5  * Copyright 2012-2020 Stephan Haller <nomad@froevel.de>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20  * MA 02110-1301, USA.
21  *
22  *
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28 
29 #include <libxfdashboard/applications-search-provider.h>
30 
31 #include <glib/gi18n-lib.h>
32 #include <gtk/gtk.h>
33 #include <errno.h>
34 
35 #include <libxfdashboard/application-database.h>
36 #include <libxfdashboard/application-button.h>
37 #include <libxfdashboard/application.h>
38 #include <libxfdashboard/drag-action.h>
39 #include <libxfdashboard/click-action.h>
40 #include <libxfdashboard/popup-menu.h>
41 #include <libxfdashboard/popup-menu-item-button.h>
42 #include <libxfdashboard/popup-menu-item-separator.h>
43 #include <libxfdashboard/application-tracker.h>
44 #include <libxfdashboard/utils.h>
45 #include <libxfdashboard/enums.h>
46 #include <libxfdashboard/compat.h>
47 #include <libxfdashboard/debug.h>
48 
49 
50 /* Define this class in GObject system */
51 struct _XfdashboardApplicationsSearchProviderPrivate
52 {
53 	/* Properties related */
54 	XfdashboardApplicationsSearchProviderSortMode	nextSortMode;
55 
56 	/* Instance related */
57 	XfdashboardApplicationDatabase					*appDB;
58 	guint											applicationAddedID;
59 	guint											applicationRemovedID;
60 
61 	GList											*allApps;
62 
63 	XfconfChannel									*xfconfChannel;
64 	guint											xfconfSortModeBindingID;
65 	XfdashboardApplicationsSearchProviderSortMode	currentSortMode;
66 };
67 
68 G_DEFINE_TYPE_WITH_PRIVATE(XfdashboardApplicationsSearchProvider,
69 							xfdashboard_applications_search_provider,
70 							XFDASHBOARD_TYPE_SEARCH_PROVIDER)
71 
72 /* Properties */
73 enum
74 {
75 	PROP_0,
76 
77 	PROP_SORT_MODE,
78 
79 	PROP_LAST
80 };
81 
82 static GParamSpec* XfdashboardApplicationsSearchProviderProperties[PROP_LAST]={ 0, };
83 
84 /* IMPLEMENTATION: Private variables and methods */
85 #define SORT_MODE_XFCONF_PROP													"/components/applications-search-provider/sort-mode"
86 
87 #define DEFAULT_DELIMITERS														"\t\n\r "
88 
89 #define XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_FILE				"applications-search-provider-statistics.ini"
90 #define XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_ENTRIES_GROUP		"Entries"
91 #define XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_ENTRIES_COUNT		"Count"
92 #define XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_USED_COUNTER_GROUP	"Used Counters"
93 
94 typedef struct _XfdashboardApplicationsSearchProviderGlobal			XfdashboardApplicationsSearchProviderGlobal;
95 struct _XfdashboardApplicationsSearchProviderGlobal
96 {
97 	gchar								*filename;
98 
99 	GHashTable							*stats;
100 
101 	guint								shutdownSignalID;
102 	guint								applicationLaunchedSignalID;
103 
104 	guint								maxUsedCounter;
105 };
106 
107 G_LOCK_DEFINE_STATIC(_xfdashboard_applications_search_provider_statistics_lock);
108 XfdashboardApplicationsSearchProviderGlobal		_xfdashboard_applications_search_provider_statistics={0, };
109 
110 typedef struct _XfdashboardApplicationsSearchProviderStatistics		XfdashboardApplicationsSearchProviderStatistics;
111 struct _XfdashboardApplicationsSearchProviderStatistics
112 {
113 	gint								refCount;
114 
115 	guint								usedCounter;
116 };
117 
118 /* Create, destroy, ref and unref statistics data */
_xfdashboard_applications_search_provider_statistics_new(void)119 static XfdashboardApplicationsSearchProviderStatistics* _xfdashboard_applications_search_provider_statistics_new(void)
120 {
121 	XfdashboardApplicationsSearchProviderStatistics	*data;
122 
123 	/* Create statistics data */
124 	data=g_new0(XfdashboardApplicationsSearchProviderStatistics, 1);
125 	if(!data) return(NULL);
126 
127 	/* Set up statistics data */
128 	data->refCount=1;
129 
130 	return(data);
131 }
132 
_xfdashboard_applications_search_provider_statistics_free(XfdashboardApplicationsSearchProviderStatistics * inData)133 static void _xfdashboard_applications_search_provider_statistics_free(XfdashboardApplicationsSearchProviderStatistics *inData)
134 {
135 	g_return_if_fail(inData);
136 
137 	/* Release common allocated resources */
138 	g_free(inData);
139 }
140 
_xfdashboard_applications_search_provider_statistics_ref(XfdashboardApplicationsSearchProviderStatistics * inData)141 static XfdashboardApplicationsSearchProviderStatistics* _xfdashboard_applications_search_provider_statistics_ref(XfdashboardApplicationsSearchProviderStatistics *inData)
142 {
143 	g_return_val_if_fail(inData, NULL);
144 
145 	inData->refCount++;
146 	return(inData);
147 }
148 
_xfdashboard_applications_search_provider_statistics_unref(XfdashboardApplicationsSearchProviderStatistics * inData)149 static void _xfdashboard_applications_search_provider_statistics_unref(XfdashboardApplicationsSearchProviderStatistics *inData)
150 {
151 	g_return_if_fail(inData);
152 
153 	inData->refCount--;
154 	if(inData->refCount==0) _xfdashboard_applications_search_provider_statistics_free(inData);
155 }
156 
_xfdashboard_applications_search_provider_statistics_get(const gchar * inAppID)157 static XfdashboardApplicationsSearchProviderStatistics* _xfdashboard_applications_search_provider_statistics_get(const gchar *inAppID)
158 {
159 	XfdashboardApplicationsSearchProviderStatistics		*stats;
160 
161 	g_return_val_if_fail(_xfdashboard_applications_search_provider_statistics.stats, NULL);
162 	g_return_val_if_fail(inAppID && *inAppID, NULL);
163 
164 	/* Lookup statistics data by application ID. If this application could not be found,
165 	 * then return NULL pointer.
166 	 */
167 	if(!g_hash_table_lookup_extended(_xfdashboard_applications_search_provider_statistics.stats, inAppID, NULL, (gpointer*)&stats))
168 	{
169 		stats=NULL;
170 	}
171 
172 	/* Return statistics data or NULL */
173 	return(stats);
174 }
175 
176 /* An application was launched successfully */
_xfdashboard_applications_search_provider_on_application_launched(XfdashboardApplication * inApplication,GAppInfo * inAppInfo,gpointer inUserData)177 static void _xfdashboard_applications_search_provider_on_application_launched(XfdashboardApplication *inApplication,
178 																				GAppInfo *inAppInfo,
179 																				gpointer inUserData)
180 {
181 	const gchar											*appID;
182 	XfdashboardApplicationsSearchProviderStatistics		*stats;
183 
184 	g_return_if_fail(G_IS_APP_INFO(inAppInfo));
185 
186 	/* Lock for thread-safety */
187 	G_LOCK(_xfdashboard_applications_search_provider_statistics_lock);
188 
189 	/* Get application ID which is used to lookup and store statistics */
190 	appID=g_app_info_get_id(inAppInfo);
191 
192 	/* Create new statistics data if application is new, otherwise take an extra
193 	 * reference on statistics data to keep it alive as it will be removed and
194 	 * re-added when updating and the removal may decrease the reference counter
195 	 * to zero which destroys the statistics data.
196 	 */
197 	stats=_xfdashboard_applications_search_provider_statistics_get(appID);
198 	if(!stats) stats=_xfdashboard_applications_search_provider_statistics_new();
199 		else _xfdashboard_applications_search_provider_statistics_ref(stats);
200 
201 	/* Increase launch counter and remember it has highest launch counter if it
202 	 * is now higher than the one we remembered.
203 	 */
204 	stats->usedCounter++;
205 	if(stats->usedCounter>_xfdashboard_applications_search_provider_statistics.maxUsedCounter)
206 	{
207 		_xfdashboard_applications_search_provider_statistics.maxUsedCounter=stats->usedCounter;
208 	}
209 
210 	/* Store updated statistics */
211 	g_hash_table_insert(_xfdashboard_applications_search_provider_statistics.stats,
212 						g_strdup(appID),
213 						_xfdashboard_applications_search_provider_statistics_ref(stats));
214 
215 	/* Release extra reference we took to keep this statistics data alive */
216 	_xfdashboard_applications_search_provider_statistics_unref(stats);
217 
218 	/* Unlock for thread-safety */
219 	G_UNLOCK(_xfdashboard_applications_search_provider_statistics_lock);
220 }
221 
222 /* Save statistics to file */
_xfdashboard_applications_search_provider_save_statistics(GError ** outError)223 static gboolean _xfdashboard_applications_search_provider_save_statistics(GError **outError)
224 {
225 	GKeyFile												*keyFile;
226 	gchar													*keyFileData;
227 	gsize													keyFileLength;
228 	GList													*allAppIDs;
229 	GList													*iter;
230 	guint													entriesCount;
231 	gchar													*fileFolder;
232 	GError													*error;
233 
234 	g_return_val_if_fail(outError==NULL || *outError==NULL, FALSE);
235 
236 	error=NULL;
237 
238 	/* If we have no filename do not store statistics but do not return error */
239 	if(!_xfdashboard_applications_search_provider_statistics.filename) return(TRUE);
240 
241 	/* Create parent folders for key file if not available */
242 	fileFolder=g_path_get_dirname(_xfdashboard_applications_search_provider_statistics.filename);
243 	if(g_mkdir_with_parents(fileFolder, 0700)<0)
244 	{
245 		int											errno_save;
246 
247 		/* Get error code */
248 		errno_save=errno;
249 
250 		/* Set error */
251 		g_set_error(outError,
252 						G_IO_ERROR,
253 						g_io_error_from_errno(errno_save),
254 						"Could not create configuration folder for applications search provider at %s: %s",
255 						fileFolder,
256 						g_strerror(errno_save));
257 
258 		/* Release allocated resources */
259 		if(fileFolder) g_free(fileFolder);
260 
261 		return(FALSE);
262 	}
263 
264 	/* Create and set up key file to store statistics */
265 	keyFile=g_key_file_new();
266 
267 	/* Get list of all applications from statistics hash table, iterate through
268 	 * all applications and store them in key file.
269 	 */
270 	allAppIDs=g_hash_table_get_keys(_xfdashboard_applications_search_provider_statistics.stats);
271 
272 	g_key_file_set_integer(keyFile,
273 							XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_ENTRIES_GROUP,
274 							XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_ENTRIES_COUNT,
275 							g_list_length(allAppIDs));
276 
277 	entriesCount=0;
278 	for(iter=allAppIDs; iter; iter=g_list_next(iter))
279 	{
280 		gchar												*name;
281 		const gchar											*appID;
282 		XfdashboardApplicationsSearchProviderStatistics		*stats;
283 
284 		/* Get current iterated application desktop ID */
285 		appID=(const gchar*)iter->data;
286 
287 		/* Get statistics data (it must exist because we got a list of all keys) */
288 		stats=_xfdashboard_applications_search_provider_statistics_get(appID);
289 		g_assert(stats!=NULL);
290 
291 		/* Store application desktop ID in "entries" overview group and increase counter */
292 		entriesCount++;
293 
294 		name=g_strdup_printf("%d", entriesCount);
295 		g_key_file_set_string(keyFile,
296 								XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_ENTRIES_GROUP,
297 								name,
298 								appID);
299 		g_free(name);
300 
301 		/* Store statistics in key file in their groups but try to avoid to store
302 		 * default values to keep key file small.
303 		 */
304 		if(stats->usedCounter>0)
305 		{
306 			g_key_file_set_integer(keyFile,
307 									XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_USED_COUNTER_GROUP,
308 									appID,
309 									stats->usedCounter);
310 		}
311 	}
312 
313 	/* Store key file for statistics in file */
314 	keyFileData=g_key_file_to_data(keyFile, &keyFileLength, NULL);
315 	if(!g_file_set_contents(_xfdashboard_applications_search_provider_statistics.filename, keyFileData, keyFileLength, &error))
316 	{
317 		/* Propagate error */
318 		g_propagate_error(outError, error);
319 
320 		/* Release allocated resources */
321 		if(fileFolder) g_free(fileFolder);
322 		if(keyFileData) g_free(keyFileData);
323 		if(keyFile) g_key_file_free(keyFile);
324 
325 		return(FALSE);
326 	}
327 
328 	/* Release allocated resources */
329 	if(fileFolder) g_free(fileFolder);
330 	if(keyFileData) g_free(keyFileData);
331 	if(keyFile) g_key_file_free(keyFile);
332 
333 	/* If we get here saving statistics file was successful */
334 	return(TRUE);
335 }
336 
337 /* Load statistics from file */
_xfdashboard_applications_search_provider_load_statistics(XfdashboardApplicationsSearchProvider * self,GError ** outError)338 static gboolean _xfdashboard_applications_search_provider_load_statistics(XfdashboardApplicationsSearchProvider *self,
339 																			GError **outError)
340 {
341 	GKeyFile												*keyFile;
342 	GList													*allAppIDs;
343 	GList													*iter;
344 	guint													entriesCount;
345 	GError													*error;
346 
347 	g_return_val_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(self), FALSE);
348 	g_return_val_if_fail(outError==NULL || *outError==NULL, FALSE);
349 
350 	error=NULL;
351 
352 	/* If no statistics were set up, we cannot load from file */
353 	if(!_xfdashboard_applications_search_provider_statistics.stats)
354 	{
355 			/* Set error */
356 			g_set_error(outError,
357 							G_IO_ERROR,
358 							G_IO_ERROR_FAILED,
359 							"Statistics were not initialized");
360 
361 			return(FALSE);
362 	}
363 
364 	/* Get path to statistics file to load statistics from */
365 	if(!_xfdashboard_applications_search_provider_statistics.filename)
366 	{
367 		_xfdashboard_applications_search_provider_statistics.filename=
368 			g_build_filename(g_get_user_data_dir(),
369 								"xfdashboard",
370 								XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_FILE,
371 								NULL);
372 
373 		if(!_xfdashboard_applications_search_provider_statistics.filename)
374 		{
375 			/* Set error */
376 			g_set_error(outError,
377 							G_IO_ERROR,
378 							G_IO_ERROR_NOT_FOUND,
379 							"Could not build path to statistics file of applications search provider");
380 
381 			return(FALSE);
382 		}
383 	}
384 	XFDASHBOARD_DEBUG(self, APPLICATIONS,
385 						"Will load statistics of applications search provider from %s",
386 						_xfdashboard_applications_search_provider_statistics.filename);
387 
388 	/* If statistics file does not exist then return immediately but with success */
389 	if(!g_file_test(_xfdashboard_applications_search_provider_statistics.filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))
390 	{
391 		XFDASHBOARD_DEBUG(self, APPLICATIONS,
392 							"Statistics file %s does not exists. Will create empty statistics database for applications search provider",
393 							_xfdashboard_applications_search_provider_statistics.filename);
394 
395 		return(TRUE);
396 	}
397 
398 	/* Load statistics from key file */
399 	keyFile=g_key_file_new();
400 	if(!g_key_file_load_from_file(keyFile, _xfdashboard_applications_search_provider_statistics.filename, G_KEY_FILE_NONE, &error))
401 	{
402 		/* Propagate error */
403 		g_propagate_error(outError, error);
404 
405 		/* Release allocated resources */
406 		if(keyFile) g_key_file_free(keyFile);
407 
408 		return(FALSE);
409 	}
410 
411 	/* Get number of applications and their application desktop IDs from key file */
412 	entriesCount=g_key_file_get_integer(keyFile,
413 										XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_ENTRIES_GROUP,
414 										XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_ENTRIES_COUNT,
415 										&error);
416 	if(error)
417 	{
418 		/* Propagate error */
419 		g_propagate_error(outError, error);
420 
421 		/* Release allocated resources */
422 		if(keyFile) g_key_file_free(keyFile);
423 
424 		return(FALSE);
425 	}
426 	XFDASHBOARD_DEBUG(self, APPLICATIONS,
427 						"Will load statistics for %d applications",
428 						entriesCount);
429 
430 	allAppIDs=NULL;
431 	while(entriesCount>0)
432 	{
433 		gchar											*name;
434 		gchar											*appID;
435 
436 		/* Get application desktop ID */
437 		name=g_strdup_printf("%d", entriesCount);
438 		appID=g_key_file_get_string(keyFile,
439 									XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_ENTRIES_GROUP,
440 									name,
441 									&error);
442 		g_free(name);
443 
444 		if(error)
445 		{
446 			/* Propagate error */
447 			g_propagate_error(outError, error);
448 
449 			/* Release allocated resources */
450 			if(appID) g_free(appID);
451 			if(allAppIDs) g_list_free_full(allAppIDs, g_free);
452 			if(keyFile) g_key_file_free(keyFile);
453 
454 			return(FALSE);
455 		}
456 
457 		/* Store application desktop ID in list to iterate */
458 		allAppIDs=g_list_prepend(allAppIDs, appID);
459 		XFDASHBOARD_DEBUG(self, APPLICATIONS,
460 							"Will load statistics for application '%s'",
461 							appID);
462 
463 		/* Continue with next application in entries group */
464 		entriesCount--;
465 	}
466 
467 	/* Iterate through all application desktop IDs and create statistics data
468 	 * with default values for each one first. Then try to load stored values
469 	 * from key file.
470 	 */
471 	for(iter=allAppIDs; iter; iter=g_list_next(iter))
472 	{
473 		const gchar											*appID;
474 		XfdashboardApplicationsSearchProviderStatistics		*stats;
475 
476 		/* Get current iterated application desktop ID */
477 		appID=(const gchar*)iter->data;
478 
479 		/* Create statistics data for application with default values */
480 		stats=_xfdashboard_applications_search_provider_statistics_new();
481 		if(!stats)
482 		{
483 			g_critical("Could not create statistics data for application '%s' of applications search provider", appID);
484 			continue;
485 		}
486 
487 		/* Try to load stored values for application from key file */
488 		if(g_key_file_has_key(keyFile, XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_USED_COUNTER_GROUP, appID, NULL))
489 		{
490 			stats->usedCounter=g_key_file_get_integer(keyFile,
491 														XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_USED_COUNTER_GROUP,
492 														appID,
493 														&error);
494 			if(error)
495 			{
496 				g_critical("Could not get value from group [%s] for application %s from statistics file of applications search provider: %s",
497 							XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_STATISTICS_USED_COUNTER_GROUP,
498 							appID,
499 							error->message);
500 				g_clear_error(&error);
501 			}
502 
503 			if(stats->usedCounter>_xfdashboard_applications_search_provider_statistics.maxUsedCounter)
504 			{
505 				_xfdashboard_applications_search_provider_statistics.maxUsedCounter=stats->usedCounter;
506 			}
507 		}
508 
509 		/* Store statistics data for application in hash-table */
510 		g_hash_table_insert(_xfdashboard_applications_search_provider_statistics.stats, g_strdup(appID), _xfdashboard_applications_search_provider_statistics_ref(stats));
511 		XFDASHBOARD_DEBUG(self, APPLICATIONS,
512 							"Loaded and stored statistics for '%s' for applications search provider",
513 							appID);
514 
515 		/* Release allocated resources */
516 		_xfdashboard_applications_search_provider_statistics_unref(stats);
517 	}
518 
519 	/* Release allocated resources */
520 	if(allAppIDs) g_list_free_full(allAppIDs, g_free);
521 	if(keyFile) g_key_file_free(keyFile);
522 
523 	/* If we get here saving statistics file was successful */
524 	XFDASHBOARD_DEBUG(self, APPLICATIONS,
525 						"Loaded statistics of applications search provider from %s",
526 						_xfdashboard_applications_search_provider_statistics.filename);
527 
528 	return(TRUE);
529 }
530 
531 /* Destroy statistics for this search provider */
_xfdashboard_applications_search_provider_destroy_statistics(void)532 static void _xfdashboard_applications_search_provider_destroy_statistics(void)
533 {
534 	XfdashboardApplication			*application;
535 	GError							*error;
536 
537 	error=NULL;
538 
539 	/* Only existing statistics can be destroyed */
540 	if(!_xfdashboard_applications_search_provider_statistics.stats) return;
541 
542 	/* Lock for thread-safety */
543 	G_LOCK(_xfdashboard_applications_search_provider_statistics_lock);
544 
545 	/* Get application instance */
546 	application=xfdashboard_application_get_default();
547 
548 	/* Disconnect application "shutdown" signal handler */
549 	if(_xfdashboard_applications_search_provider_statistics.shutdownSignalID)
550 	{
551 		g_signal_handler_disconnect(application, _xfdashboard_applications_search_provider_statistics.shutdownSignalID);
552 		_xfdashboard_applications_search_provider_statistics.shutdownSignalID=0;
553 	}
554 
555 	/* Disconnect application "application-launched" signal handler */
556 	if(_xfdashboard_applications_search_provider_statistics.applicationLaunchedSignalID)
557 	{
558 		g_signal_handler_disconnect(application, _xfdashboard_applications_search_provider_statistics.applicationLaunchedSignalID);
559 		_xfdashboard_applications_search_provider_statistics.applicationLaunchedSignalID=0;
560 	}
561 
562 	/* Save statistics to file */
563 	if(!_xfdashboard_applications_search_provider_save_statistics(&error))
564 	{
565 		g_critical("Failed to save statistics of applications search provider to %s: %s",
566 					_xfdashboard_applications_search_provider_statistics.filename,
567 					error ? error->message : "Unknown error");
568 		if(error) g_clear_error(&error);
569 	}
570 
571 	/* Destroy statistics */
572 	XFDASHBOARD_DEBUG(NULL, APPLICATIONS, "Destroying statistics of applications search provider");
573 	g_hash_table_destroy(_xfdashboard_applications_search_provider_statistics.stats);
574 	_xfdashboard_applications_search_provider_statistics.stats=NULL;
575 
576 	/* Destroy filename for statistics */
577 	if(_xfdashboard_applications_search_provider_statistics.filename)
578 	{
579 		g_free(_xfdashboard_applications_search_provider_statistics.filename);
580 		_xfdashboard_applications_search_provider_statistics.filename=NULL;
581 	}
582 
583 	/* Reset other variables */
584 	_xfdashboard_applications_search_provider_statistics.maxUsedCounter=0;
585 
586 	/* Unlock for thread-safety */
587 	G_UNLOCK(_xfdashboard_applications_search_provider_statistics_lock);
588 }
589 
590 /* Create and load statistics for this search provider if not done already */
_xfdashboard_applications_search_provider_create_statistics(XfdashboardApplicationsSearchProvider * self)591 static void _xfdashboard_applications_search_provider_create_statistics(XfdashboardApplicationsSearchProvider *self)
592 {
593 	XfdashboardApplication			*application;
594 	GError							*error;
595 
596 	g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(self));
597 
598 	error=NULL;
599 
600 	/* Statistics were already set up */
601 	if(_xfdashboard_applications_search_provider_statistics.stats) return;
602 
603 	g_assert(!_xfdashboard_applications_search_provider_statistics.shutdownSignalID);
604 	g_assert(!_xfdashboard_applications_search_provider_statistics.applicationLaunchedSignalID);
605 
606 	/* Lock for thread-safety */
607 	G_LOCK(_xfdashboard_applications_search_provider_statistics_lock);
608 
609 	/* Initialize non-critical variables */
610 	_xfdashboard_applications_search_provider_statistics.maxUsedCounter=0;
611 
612 	/* Create hash-table for statistics */
613 	_xfdashboard_applications_search_provider_statistics.stats=
614 		g_hash_table_new_full(g_str_hash,
615 								g_str_equal,
616 								g_free,
617 								(GDestroyNotify)_xfdashboard_applications_search_provider_statistics_unref);
618 	XFDASHBOARD_DEBUG(self, APPLICATIONS, "Created statistics of applications search provider");
619 
620 	/* Load statistics from file */
621 	if(!_xfdashboard_applications_search_provider_load_statistics(self, &error))
622 	{
623 		g_critical("Failed to load statistics of applications search provider from %s: %s",
624 					_xfdashboard_applications_search_provider_statistics.filename,
625 					error ? error->message : "Unknown error");
626 		if(error) g_clear_error(&error);
627 
628 		/* Destroy hash-table to avoid the half-loaded hash-table being stored again
629 		 * and overriding existing statistics file (even if it may be broken).
630 		 * Also release
631 		 */
632 		if(_xfdashboard_applications_search_provider_statistics.stats)
633 		{
634 			g_hash_table_destroy(_xfdashboard_applications_search_provider_statistics.stats);
635 			_xfdashboard_applications_search_provider_statistics.stats=NULL;
636 		}
637 
638 		if(_xfdashboard_applications_search_provider_statistics.filename)
639 		{
640 			g_free(_xfdashboard_applications_search_provider_statistics.filename);
641 			_xfdashboard_applications_search_provider_statistics.filename=NULL;
642 		}
643 
644 		/* Unlock for thread-safety */
645 		G_UNLOCK(_xfdashboard_applications_search_provider_statistics_lock);
646 
647 		/* Return here to prevent further initializations of statistics
648 		 * which are not used and not deinitialized in 'destroy' function.
649 		 */
650 		return;
651 	}
652 
653 	/* Get application instance */
654 	application=xfdashboard_application_get_default();
655 
656 	/* Connect to "shutdown" signal of application to clean up statistics */
657 	_xfdashboard_applications_search_provider_statistics.shutdownSignalID=
658 		g_signal_connect(application,
659 							"shutdown-final",
660 							G_CALLBACK(_xfdashboard_applications_search_provider_destroy_statistics),
661 							NULL);
662 
663 	/* Connect to "application-launched" signal of application to track app launches */
664 	_xfdashboard_applications_search_provider_statistics.applicationLaunchedSignalID=
665 		g_signal_connect(application,
666 							"application-launched",
667 							G_CALLBACK(_xfdashboard_applications_search_provider_on_application_launched),
668 							NULL);
669 
670 	/* Unlock for thread-safety */
671 	G_UNLOCK(_xfdashboard_applications_search_provider_statistics_lock);
672 }
673 
674 /* An application was added to database */
_xfdashboard_applications_search_provider_on_application_added(XfdashboardApplicationsSearchProvider * self,GAppInfo * inAppInfo,gpointer inUserData)675 static void _xfdashboard_applications_search_provider_on_application_added(XfdashboardApplicationsSearchProvider *self,
676 																			GAppInfo *inAppInfo,
677 																			gpointer inUserData)
678 {
679 	XfdashboardApplicationsSearchProviderPrivate	*priv;
680 
681 	g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(self));
682 
683 	priv=self->priv;
684 
685 	/* Release current list of all installed applications */
686 	if(priv->allApps)
687 	{
688 		g_list_free_full(priv->allApps, g_object_unref);
689 		priv->allApps=NULL;
690 	}
691 
692 	/* Get new list of all installed applications */
693 	priv->allApps=xfdashboard_application_database_get_all_applications(priv->appDB);
694 }
695 
696 /* An application was removed to database */
_xfdashboard_applications_search_provider_on_application_removed(XfdashboardApplicationsSearchProvider * self,GAppInfo * inAppInfo,gpointer inUserData)697 static void _xfdashboard_applications_search_provider_on_application_removed(XfdashboardApplicationsSearchProvider *self,
698 																				GAppInfo *inAppInfo,
699 																				gpointer inUserData)
700 {
701 	XfdashboardApplicationsSearchProviderPrivate	*priv;
702 
703 	g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(self));
704 
705 	priv=self->priv;
706 
707 	/* Release current list of all installed applications */
708 	if(priv->allApps)
709 	{
710 		g_list_free_full(priv->allApps, g_object_unref);
711 		priv->allApps=NULL;
712 	}
713 
714 	/* Get new list of all installed applications */
715 	priv->allApps=xfdashboard_application_database_get_all_applications(priv->appDB);
716 }
717 
718 /* User selected to open a new window or to launch that application at pop-up menu */
_xfdashboard_applications_search_provider_on_popup_menu_item_launch(XfdashboardPopupMenuItem * inMenuItem,gpointer inUserData)719 static void _xfdashboard_applications_search_provider_on_popup_menu_item_launch(XfdashboardPopupMenuItem *inMenuItem,
720 																				gpointer inUserData)
721 {
722 	GAppInfo							*appInfo;
723 	XfdashboardApplicationTracker		*appTracker;
724 	GIcon								*gicon;
725 	const gchar							*iconName;
726 
727 	g_return_if_fail(XFDASHBOARD_IS_POPUP_MENU_ITEM(inMenuItem));
728 	g_return_if_fail(G_IS_APP_INFO(inUserData));
729 
730 	appInfo=G_APP_INFO(inUserData);
731 	iconName=NULL;
732 
733 	/* Get icon of application */
734 	gicon=g_app_info_get_icon(appInfo);
735 	if(gicon) iconName=g_icon_to_string(gicon);
736 
737 	/* Check if we should launch that application or to open a new window */
738 	appTracker=xfdashboard_application_tracker_get_default();
739 	if(!xfdashboard_application_tracker_is_running_by_app_info(appTracker, appInfo))
740 	{
741 		GAppLaunchContext			*context;
742 		GError						*error;
743 
744 		/* Create context to start application at */
745 		context=xfdashboard_create_app_context(NULL);
746 
747 		/* Try to launch application */
748 		error=NULL;
749 		if(!g_app_info_launch(appInfo, NULL, context, &error))
750 		{
751 			/* Show notification about failed application launch */
752 			xfdashboard_notify(CLUTTER_ACTOR(inMenuItem),
753 								iconName,
754 								_("Launching application '%s' failed: %s"),
755 								g_app_info_get_display_name(appInfo),
756 								(error && error->message) ? error->message : _("unknown error"));
757 			g_warning("Launching application '%s' failed: %s",
758 						g_app_info_get_display_name(appInfo),
759 						(error && error->message) ? error->message : "unknown error");
760 			if(error) g_error_free(error);
761 		}
762 			else
763 			{
764 				/* Show notification about successful application launch */
765 				xfdashboard_notify(CLUTTER_ACTOR(inMenuItem),
766 									iconName,
767 									_("Application '%s' launched"),
768 									g_app_info_get_display_name(appInfo));
769 
770 				/* Emit signal for successful application launch */
771 				g_signal_emit_by_name(xfdashboard_application_get_default(), "application-launched", appInfo);
772 
773 				/* Quit application */
774 				xfdashboard_application_suspend_or_quit(NULL);
775 			}
776 
777 		/* Release allocated resources */
778 		g_object_unref(context);
779 	}
780 
781 	/* Release allocated resources */
782 	g_object_unref(appTracker);
783 	g_object_unref(gicon);
784 }
785 
786 /* A right-click might have happened on an application icon */
_xfdashboard_applications_search_provider_on_popup_menu(XfdashboardApplicationsSearchProvider * self,ClutterActor * inActor,gpointer inUserData)787 static void _xfdashboard_applications_search_provider_on_popup_menu(XfdashboardApplicationsSearchProvider *self,
788 																	ClutterActor *inActor,
789 																	gpointer inUserData)
790 {
791 	XfdashboardApplicationButton				*button;
792 	XfdashboardClickAction						*action;
793 
794 	g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(self));
795 	g_return_if_fail(XFDASHBOARD_IS_APPLICATION_BUTTON(inActor));
796 	g_return_if_fail(XFDASHBOARD_IS_CLICK_ACTION(inUserData));
797 
798 	button=XFDASHBOARD_APPLICATION_BUTTON(inActor);
799 	action=XFDASHBOARD_CLICK_ACTION(inUserData);
800 
801 	/* Check if right button was used when the application button was clicked */
802 	if(xfdashboard_click_action_get_button(action)==XFDASHBOARD_CLICK_ACTION_RIGHT_BUTTON)
803 	{
804 		ClutterActor							*popup;
805 		ClutterActor							*menuItem;
806 		GAppInfo								*appInfo;
807 		XfdashboardApplicationTracker			*appTracker;
808 		gchar									*sourceStyleClass;
809 
810 		/* Get app info for application button as it is needed most the time */
811 		appInfo=xfdashboard_application_button_get_app_info(button);
812 		if(!appInfo)
813 		{
814 			g_critical("No application information available for clicked application button.");
815 			return;
816 		}
817 
818 		/* Create pop-up menu */
819 		popup=xfdashboard_popup_menu_new();
820 		xfdashboard_popup_menu_set_destroy_on_cancel(XFDASHBOARD_POPUP_MENU(popup), TRUE);
821 		xfdashboard_popup_menu_set_title(XFDASHBOARD_POPUP_MENU(popup), g_app_info_get_display_name(appInfo));
822 		xfdashboard_popup_menu_set_title_gicon(XFDASHBOARD_POPUP_MENU(popup), g_app_info_get_icon(appInfo));
823 
824 		/* Add each open window to pop-up of application */
825 		if(xfdashboard_application_button_add_popup_menu_items_for_windows(button, XFDASHBOARD_POPUP_MENU(popup))>0)
826 		{
827 			/* Add a separator to split windows from other actions in pop-up menu */
828 			menuItem=xfdashboard_popup_menu_item_separator_new();
829 			clutter_actor_set_x_expand(menuItem, TRUE);
830 			xfdashboard_popup_menu_add_item(XFDASHBOARD_POPUP_MENU(popup), XFDASHBOARD_POPUP_MENU_ITEM(menuItem));
831 		}
832 
833 		/* Add menu item to launch application if it is not running */
834 		appTracker=xfdashboard_application_tracker_get_default();
835 		if(!xfdashboard_application_tracker_is_running_by_app_info(appTracker, appInfo))
836 		{
837 			menuItem=xfdashboard_popup_menu_item_button_new();
838 			xfdashboard_label_set_text(XFDASHBOARD_LABEL(menuItem), _("Launch"));
839 			clutter_actor_set_x_expand(menuItem, TRUE);
840 			xfdashboard_popup_menu_add_item(XFDASHBOARD_POPUP_MENU(popup), XFDASHBOARD_POPUP_MENU_ITEM(menuItem));
841 
842 			g_signal_connect(menuItem,
843 								"activated",
844 								G_CALLBACK(_xfdashboard_applications_search_provider_on_popup_menu_item_launch),
845 								appInfo);
846 		}
847 		g_object_unref(appTracker);
848 
849 		/* Add application actions */
850 		xfdashboard_application_button_add_popup_menu_items_for_actions(button, XFDASHBOARD_POPUP_MENU(popup));
851 
852 		/* Set style class as pop-up menu has no source set to create style
853 		 * class automatically because this class is not derived from an actor
854 		 * class.
855 		 */
856 		sourceStyleClass=g_strdup_printf("popup-menu-source-%s", G_OBJECT_TYPE_NAME(self));
857 		xfdashboard_stylable_add_class(XFDASHBOARD_STYLABLE(popup), sourceStyleClass);
858 		g_free(sourceStyleClass);
859 
860 		/* Activate pop-up menu */
861 		xfdashboard_popup_menu_activate(XFDASHBOARD_POPUP_MENU(popup));
862 	}
863 }
864 
865 /* Drag of an menu item begins */
_xfdashboard_applications_search_provider_on_drag_begin(ClutterDragAction * inAction,ClutterActor * inActor,gfloat inStageX,gfloat inStageY,ClutterModifierType inModifiers,gpointer inUserData)866 static void _xfdashboard_applications_search_provider_on_drag_begin(ClutterDragAction *inAction,
867 																	ClutterActor *inActor,
868 																	gfloat inStageX,
869 																	gfloat inStageY,
870 																	ClutterModifierType inModifiers,
871 																	gpointer inUserData)
872 {
873 	GAppInfo							*appInfo;
874 	ClutterActor						*dragHandle;
875 	ClutterStage						*stage;
876 
877 	g_return_if_fail(CLUTTER_IS_DRAG_ACTION(inAction));
878 	g_return_if_fail(XFDASHBOARD_IS_APPLICATION_BUTTON(inActor));
879 	g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(inUserData));
880 
881 	/* Get stage */
882 	stage=CLUTTER_STAGE(clutter_actor_get_stage(inActor));
883 
884 	/* Create a application icon for drag handle */
885 	appInfo=xfdashboard_application_button_get_app_info(XFDASHBOARD_APPLICATION_BUTTON(inActor));
886 
887 	dragHandle=xfdashboard_application_button_new_from_app_info(appInfo);
888 	clutter_actor_set_position(dragHandle, inStageX, inStageY);
889 	clutter_actor_add_child(CLUTTER_ACTOR(stage), dragHandle);
890 
891 	clutter_drag_action_set_drag_handle(inAction, dragHandle);
892 }
893 
894 /* Drag of a result item ends */
_xfdashboard_applications_search_provider_on_drag_end(ClutterDragAction * inAction,ClutterActor * inActor,gfloat inStageX,gfloat inStageY,ClutterModifierType inModifiers,gpointer inUserData)895 static void _xfdashboard_applications_search_provider_on_drag_end(ClutterDragAction *inAction,
896 																	ClutterActor *inActor,
897 																	gfloat inStageX,
898 																	gfloat inStageY,
899 																	ClutterModifierType inModifiers,
900 																	gpointer inUserData)
901 {
902 	ClutterActor					*dragHandle;
903 
904 	g_return_if_fail(CLUTTER_IS_DRAG_ACTION(inAction));
905 	g_return_if_fail(XFDASHBOARD_IS_APPLICATION_BUTTON(inActor));
906 	g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(inUserData));
907 
908 	/* Destroy clone of application icon used as drag handle */
909 	dragHandle=clutter_drag_action_get_drag_handle(inAction);
910 	if(dragHandle)
911 	{
912 #if CLUTTER_CHECK_VERSION(1, 14, 0)
913 		/* Only unset drag handle if not running Clutter in version
914 		 * 1.12. This prevents a critical warning message in 1.12.
915 		 * Later versions of Clutter are fixed already.
916 		 */
917 		clutter_drag_action_set_drag_handle(inAction, NULL);
918 #endif
919 		xfdashboard_actor_destroy(dragHandle);
920 	}
921 }
922 
923 /* Check if given app info matches search terms and return score as fraction
924  * between 0.0and 1.0 - so called "relevance". A negative score means that
925  * the given app info does not match at all.
926  */
_xfdashboard_applications_search_provider_score(XfdashboardApplicationsSearchProvider * self,gchar ** inSearchTerms,GAppInfo * inAppInfo)927 static gfloat _xfdashboard_applications_search_provider_score(XfdashboardApplicationsSearchProvider *self,
928 																gchar **inSearchTerms,
929 																GAppInfo *inAppInfo)
930 {
931 	XfdashboardApplicationsSearchProviderPrivate		*priv;
932 	gchar												*title;
933 	gchar												*description;
934 	GList												*keywords;
935 	const gchar											*command;
936 	const gchar											*value;
937 	gint												matchesFound, matchesExpected;
938 	gfloat												pointsSearch;
939 	gfloat												score;
940 
941 	g_return_val_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(self), -1.0f);
942 	g_return_val_if_fail(G_IS_APP_INFO(inAppInfo), -1.0f);
943 
944 	priv=self->priv;
945 	score=-1.0f;
946 	keywords=NULL;
947 
948 	/* Empty search term matches no menu item */
949 	if(!inSearchTerms) return(0.0f);
950 
951 	matchesExpected=g_strv_length(inSearchTerms);
952 	if(matchesExpected==0) return(0.0f);
953 
954 	/* Calculate the highest score points possible which is the highest
955 	 * launch count among all applications, display name matches all search terms,
956 	 * description matches all search terms and also the command matches all
957 	 * search terms.
958 	 *
959 	 * But a matching display names weights more than a matching description and
960 	 * also a matching command or keyword weights more than a matching description.
961 	 * So the weights are 0.4 for matching display names, 0.25 for matching commands,
962 	 * 0.25 for matching keywords and 0.1 for matching descriptions.
963 	 *
964 	 * While iterating through all search terms we add the weights "points" for
965 	 * each matching item and when we iterated through all search terms we divide
966 	 * the total weight "points" by the number of search terms to get the average
967 	 * which is also the result score when *not* taking the launch count of
968 	 * application into account.
969 	 */
970 	value=g_app_info_get_display_name(inAppInfo);
971 	if(value) title=g_utf8_strdown(value, -1);
972 		else title=NULL;
973 
974 	value=g_app_info_get_description(inAppInfo);
975 	if(value) description=g_utf8_strdown(value, -1);
976 		else description=NULL;
977 
978 	command=g_app_info_get_executable(inAppInfo);
979 
980 	if(XFDASHBOARD_IS_DESKTOP_APP_INFO(inAppInfo))
981 	{
982 		GList											*appKeywords;
983 		GList											*iter;
984 
985 		appKeywords=xfdashboard_desktop_app_info_get_keywords(XFDASHBOARD_DESKTOP_APP_INFO(inAppInfo));
986 		for(iter=appKeywords; iter; iter=g_list_next(iter))
987 		{
988 			keywords=g_list_prepend(keywords, g_utf8_strdown(iter->data, -1));
989 		}
990 	}
991 
992 	matchesFound=0;
993 	pointsSearch=0.0f;
994 	while(*inSearchTerms)
995 	{
996 		gboolean										termMatch;
997 		gchar											*commandPos;
998 		gfloat											pointsTerm;
999 
1000 		/* Reset "found" indicator and score of current search term */
1001 		termMatch=FALSE;
1002 		pointsTerm=0.0f;
1003 
1004 		/* Check for current search term */
1005 		if(title &&
1006 			g_strstr_len(title, -1, *inSearchTerms))
1007 		{
1008 			pointsTerm+=0.4;
1009 			termMatch=TRUE;
1010 		}
1011 
1012 		if(keywords)
1013 		{
1014 			GList						*iter;
1015 
1016 			for(iter=keywords; iter; iter=g_list_next(iter))
1017 			{
1018 				if(iter->data &&
1019 					g_strstr_len(iter->data, -1, *inSearchTerms))
1020 				{
1021 					pointsTerm+=0.25;
1022 					termMatch=TRUE;
1023 					break;
1024 				}
1025 			}
1026 		}
1027 
1028 		if(command)
1029 		{
1030 			commandPos=g_strstr_len(command, -1, *inSearchTerms);
1031 			if(commandPos &&
1032 				(commandPos==command || *(commandPos-1)==G_DIR_SEPARATOR))
1033 			{
1034 				pointsTerm+=0.25;
1035 				termMatch=TRUE;
1036 			}
1037 		}
1038 
1039 		if(description &&
1040 			g_strstr_len(description, -1, *inSearchTerms))
1041 		{
1042 			pointsTerm+=0.1;
1043 			termMatch=TRUE;
1044 		}
1045 
1046 		/* Increase match counter if we found a match */
1047 		if(termMatch)
1048 		{
1049 			matchesFound++;
1050 			pointsSearch+=pointsTerm;
1051 		}
1052 
1053 		/* Continue with next search term */
1054 		inSearchTerms++;
1055 	}
1056 
1057 	/* If we got a match in either title, description or command for each search term
1058 	 * then calculate score and also check if we should take the number of
1059 	 * launches of this application into account.
1060 	 */
1061 	if(matchesFound>=matchesExpected)
1062 	{
1063 		gfloat											currentPoints;
1064 		gfloat											maxPoints;
1065 		XfdashboardApplicationsSearchProviderStatistics	*stats;
1066 
1067 		currentPoints=0.0f;
1068 		maxPoints=0.0f;
1069 
1070 		/* Set maximum points to the number of expected number of matches
1071 		 * if we should take title, description and command into calculation.
1072 		 */
1073 		if(priv->currentSortMode & XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_SORT_MODE_NAMES)
1074 		{
1075 			currentPoints+=pointsSearch;
1076 			maxPoints+=matchesExpected*1.0f;
1077 		}
1078 
1079 		/* If used counter should be taken into calculation add the highest number
1080 		 * of any application to the highest points possible and also add the number
1081 		 * of launches of this application to the total points we got so far.
1082 		 */
1083 		if(priv->currentSortMode & XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_SORT_MODE_MOST_USED)
1084 		{
1085 			maxPoints+=(_xfdashboard_applications_search_provider_statistics.maxUsedCounter*1.0f);
1086 
1087 			stats=_xfdashboard_applications_search_provider_statistics_get(g_app_info_get_id(inAppInfo));
1088 			if(stats) currentPoints+=(stats->usedCounter*1.0f);
1089 		}
1090 
1091 		/* Calculate score but if maximum points is still zero we should do a simple
1092 		 * match by setting score to 1.
1093 		 */
1094 		if(maxPoints>0.0f) score=currentPoints/maxPoints;
1095 			else score=1.0f;
1096 	}
1097 
1098 	/* Release allocated resources */
1099 	if(keywords) g_list_free_full(keywords, g_free);
1100 	if(description) g_free(description);
1101 	if(title) g_free(title);
1102 
1103 	/* Return score of this application for requested search terms */
1104 	return(score);
1105 }
1106 
1107 /* Callback to sort each item in result set */
_xfdashboard_applications_search_provider_sort_result_set(GVariant * inLeft,GVariant * inRight,gpointer inUserData)1108 static gint _xfdashboard_applications_search_provider_sort_result_set(GVariant *inLeft,
1109 																		GVariant *inRight,
1110 																		gpointer inUserData)
1111 {
1112 	XfdashboardApplicationsSearchProvider				*self;
1113 	XfdashboardApplicationsSearchProviderPrivate		*priv;
1114 	const gchar											*leftID;
1115 	const gchar											*rightID;
1116 	GAppInfo											*leftAppInfo;
1117 	GAppInfo											*rightAppInfo;
1118 	const gchar											*leftName;
1119 	const gchar											*rightName;
1120 	gchar												*lowerLeftName;
1121 	gchar												*lowerRightName;
1122 	gint												result;
1123 
1124 	g_return_val_if_fail(inLeft, 0);
1125 	g_return_val_if_fail(inRight, 0);
1126 	g_return_val_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(inUserData), 0);
1127 
1128 	self=XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER(inUserData);
1129 	priv=self->priv;
1130 
1131 	/* Get desktop IDs of both items */
1132 	leftID=g_variant_get_string(inLeft, NULL);
1133 	rightID=g_variant_get_string(inRight, NULL);
1134 
1135 	/* Get desktop application information of both items */
1136 	leftAppInfo=xfdashboard_application_database_lookup_desktop_id(priv->appDB, leftID);
1137 	if(leftAppInfo) leftName=g_app_info_get_display_name(G_APP_INFO(leftAppInfo));
1138 		else leftName=NULL;
1139 
1140 	rightAppInfo=xfdashboard_application_database_lookup_desktop_id(priv->appDB, rightID);
1141 	if(rightAppInfo) rightName=g_app_info_get_display_name(G_APP_INFO(rightAppInfo));
1142 		else rightName=NULL;
1143 
1144 	/* Get result of comparing both desktop application information objects */
1145 	if(leftName) lowerLeftName=g_utf8_strdown(leftName, -1);
1146 		else lowerLeftName=NULL;
1147 
1148 	if(rightName) lowerRightName=g_utf8_strdown(rightName, -1);
1149 		else lowerRightName=NULL;
1150 
1151 	result=g_strcmp0(lowerLeftName, lowerRightName);
1152 
1153 	/* Release allocated resources */
1154 	if(rightAppInfo) g_object_unref(rightAppInfo);
1155 	if(leftAppInfo) g_object_unref(leftAppInfo);
1156 	if(lowerLeftName) g_free(lowerLeftName);
1157 	if(lowerRightName) g_free(lowerRightName);
1158 
1159 	/* Return result */
1160 	return(result);
1161 }
1162 
1163 /* IMPLEMENTATION: XfdashboardSearchProvider */
_xfdashboard_applications_search_provider_initialize(XfdashboardSearchProvider * inProvider)1164 static void _xfdashboard_applications_search_provider_initialize(XfdashboardSearchProvider *inProvider)
1165 {
1166 	g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(inProvider));
1167 
1168 	/* Create and load statistics hash-table (will only be done once) */
1169 	_xfdashboard_applications_search_provider_create_statistics(XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER(inProvider));
1170 }
1171 
1172 /* Get display name for this search provider */
_xfdashboard_applications_search_provider_get_name(XfdashboardSearchProvider * inProvider)1173 static const gchar* _xfdashboard_applications_search_provider_get_name(XfdashboardSearchProvider *inProvider)
1174 {
1175 	return(_("Applications"));
1176 }
1177 
1178 /* Get icon-name for this search provider */
_xfdashboard_applications_search_provider_get_icon(XfdashboardSearchProvider * inProvider)1179 static const gchar* _xfdashboard_applications_search_provider_get_icon(XfdashboardSearchProvider *inProvider)
1180 {
1181 	return("go-home");
1182 }
1183 
1184 /* Get result set for requested search terms */
_xfdashboard_applications_search_provider_get_result_set(XfdashboardSearchProvider * inProvider,const gchar ** inSearchTerms,XfdashboardSearchResultSet * inPreviousResultSet)1185 static XfdashboardSearchResultSet* _xfdashboard_applications_search_provider_get_result_set(XfdashboardSearchProvider *inProvider,
1186 																							const gchar **inSearchTerms,
1187 																							XfdashboardSearchResultSet *inPreviousResultSet)
1188 {
1189 	XfdashboardApplicationsSearchProvider				*self;
1190 	XfdashboardApplicationsSearchProviderPrivate		*priv;
1191 	XfdashboardSearchResultSet							*resultSet;
1192 	GList												*iter;
1193 	guint												numberTerms;
1194 	gchar												**terms, **termsIter;
1195 	XfdashboardDesktopAppInfo							*appInfo;
1196 	gfloat												score;
1197 
1198 	g_return_val_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(inProvider), NULL);
1199 
1200 	self=XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER(inProvider);
1201 	priv=self->priv;
1202 
1203 	/* Set new match mode */
1204 	priv->currentSortMode=priv->nextSortMode;
1205 
1206 	/* To perform case-insensitive searches through model convert all search terms
1207 	 * to lower-case before starting search.
1208 	 * Remember that string list must be NULL terminated.
1209 	 */
1210 	numberTerms=g_strv_length((gchar**)inSearchTerms);
1211 	if(numberTerms==0)
1212 	{
1213 		/* If we get here no search term is given, return no result set */
1214 		return(NULL);
1215 	}
1216 
1217 	terms=g_new(gchar*, numberTerms+1);
1218 	if(!terms)
1219 	{
1220 		g_critical("Could not allocate memory to copy search criteria for case-insensitive search");
1221 		return(NULL);
1222 	}
1223 
1224 	termsIter=terms;
1225 	while(*inSearchTerms)
1226 	{
1227 		*termsIter=g_utf8_strdown(*inSearchTerms, -1);
1228 
1229 		/* Move to next entry where to store lower-case and
1230 		 * initialize with NULL for NULL termination of list.
1231 		 */
1232 		termsIter++;
1233 		*termsIter=NULL;
1234 
1235 		/* Move to next search term to convert to lower-case */
1236 		inSearchTerms++;
1237 	}
1238 
1239 	/* Create empty result set to store matching result items */
1240 	resultSet=xfdashboard_search_result_set_new();
1241 
1242 	/* Perform search */
1243 	for(iter=priv->allApps; iter; iter=g_list_next(iter))
1244 	{
1245 		/* Get app info to check for match */
1246 		appInfo=XFDASHBOARD_DESKTOP_APP_INFO(iter->data);
1247 
1248 		/* If desktop app info should be hidden then continue with next one */
1249 		if(!g_app_info_should_show(G_APP_INFO(appInfo)))
1250 		{
1251 			continue;
1252 		}
1253 
1254 		/* Check for a match against search terms */
1255 		score=_xfdashboard_applications_search_provider_score(self, terms, G_APP_INFO(appInfo));
1256 		if(score>=0.0f)
1257 		{
1258 			GVariant									*resultItem;
1259 
1260 			/* Create result item */
1261 			resultItem=g_variant_new_string(g_app_info_get_id(G_APP_INFO(appInfo)));
1262 
1263 			/* Add result item to result set */
1264 			xfdashboard_search_result_set_add_item(resultSet, resultItem);
1265 			xfdashboard_search_result_set_set_item_score(resultSet, resultItem, score);
1266 		}
1267 	}
1268 
1269 	/* Sort result set */
1270 	xfdashboard_search_result_set_set_sort_func_full(resultSet,
1271 														_xfdashboard_applications_search_provider_sort_result_set,
1272 														g_object_ref(self),
1273 														g_object_unref);
1274 
1275 	/* Release allocated resources */
1276 	if(terms)
1277 	{
1278 		termsIter=terms;
1279 		while(*termsIter)
1280 		{
1281 			g_free(*termsIter);
1282 			termsIter++;
1283 		}
1284 		g_free(terms);
1285 	}
1286 
1287 	/* Return result set */
1288 	return(resultSet);
1289 }
1290 
1291 /* Create actor for a result item of the result set returned from a search request */
_xfdashboard_applications_search_provider_create_result_actor(XfdashboardSearchProvider * inProvider,GVariant * inResultItem)1292 static ClutterActor* _xfdashboard_applications_search_provider_create_result_actor(XfdashboardSearchProvider *inProvider,
1293 																					GVariant *inResultItem)
1294 {
1295 	XfdashboardApplicationsSearchProvider			*self;
1296 	XfdashboardApplicationsSearchProviderPrivate	*priv;
1297 	ClutterActor									*actor;
1298 	ClutterAction									*clickAction;
1299 	ClutterAction									*dragAction;
1300 	GAppInfo										*appInfo;
1301 
1302 	g_return_val_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(inProvider), NULL);
1303 	g_return_val_if_fail(inResultItem, NULL);
1304 
1305 	self=XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER(inProvider);
1306 	priv=self->priv;
1307 
1308 	/* Get app info for result item */
1309 	appInfo=xfdashboard_application_database_lookup_desktop_id(priv->appDB, g_variant_get_string(inResultItem, NULL));
1310 	if(!appInfo) appInfo=xfdashboard_desktop_app_info_new_from_desktop_id(g_variant_get_string(inResultItem, NULL));
1311 	if(!appInfo)
1312 	{
1313 		g_warning("Cannot create actor for desktop ID '%s' in result set of %s",
1314 					g_variant_get_string(inResultItem, NULL),
1315 					G_OBJECT_TYPE_NAME(inProvider));
1316 		return(NULL);
1317 	}
1318 
1319 	/* Create actor for result item */
1320 	actor=xfdashboard_application_button_new_from_app_info(appInfo);
1321 	clutter_actor_show(actor);
1322 
1323 	clickAction=xfdashboard_click_action_new();
1324 	g_signal_connect_swapped(clickAction, "clicked", G_CALLBACK(_xfdashboard_applications_search_provider_on_popup_menu), self);
1325 	clutter_actor_add_action(actor, clickAction);
1326 
1327 	dragAction=xfdashboard_drag_action_new();
1328 	clutter_drag_action_set_drag_threshold(CLUTTER_DRAG_ACTION(dragAction), -1, -1);
1329 	clutter_actor_add_action(actor, dragAction);
1330 	g_signal_connect(dragAction, "drag-begin", G_CALLBACK(_xfdashboard_applications_search_provider_on_drag_begin), self);
1331 	g_signal_connect(dragAction, "drag-end", G_CALLBACK(_xfdashboard_applications_search_provider_on_drag_end), self);
1332 
1333 	/* Release allocated resources */
1334 	g_object_unref(appInfo);
1335 
1336 	/* Return created actor */
1337 	return(actor);
1338 }
1339 
1340 /* Activate result item */
_xfdashboard_applications_search_provider_activate_result(XfdashboardSearchProvider * inProvider,GVariant * inResultItem,ClutterActor * inActor,const gchar ** inSearchTerms)1341 static gboolean _xfdashboard_applications_search_provider_activate_result(XfdashboardSearchProvider* inProvider,
1342 																			GVariant *inResultItem,
1343 																			ClutterActor *inActor,
1344 																			const gchar **inSearchTerms)
1345 {
1346 	XfdashboardApplicationButton		*button;
1347 
1348 	g_return_val_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(inProvider), FALSE);
1349 	g_return_val_if_fail(XFDASHBOARD_IS_APPLICATION_BUTTON(inActor), FALSE);
1350 
1351 	button=XFDASHBOARD_APPLICATION_BUTTON(inActor);
1352 
1353 	/* Launch application */
1354 	return(xfdashboard_application_button_execute(button, NULL));
1355 }
1356 
1357 /* IMPLEMENTATION: GObject */
1358 
1359 /* Dispose this object */
_xfdashboard_applications_search_provider_dispose(GObject * inObject)1360 static void _xfdashboard_applications_search_provider_dispose(GObject *inObject)
1361 {
1362 	XfdashboardApplicationsSearchProvider			*self=XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER(inObject);
1363 	XfdashboardApplicationsSearchProviderPrivate	*priv=self->priv;
1364 
1365 	/* Release allocated resouces */
1366 	if(priv->appDB)
1367 	{
1368 		if(priv->applicationAddedID)
1369 		{
1370 			g_signal_handler_disconnect(priv->appDB, priv->applicationAddedID);
1371 			priv->applicationAddedID=0;
1372 		}
1373 
1374 		if(priv->applicationRemovedID)
1375 		{
1376 			g_signal_handler_disconnect(priv->appDB, priv->applicationRemovedID);
1377 			priv->applicationRemovedID=0;
1378 		}
1379 
1380 		g_object_unref(priv->appDB);
1381 		priv->appDB=NULL;
1382 	}
1383 
1384 	if(priv->allApps)
1385 	{
1386 		g_list_free_full(priv->allApps, g_object_unref);
1387 		priv->allApps=NULL;
1388 	}
1389 
1390 	if(priv->xfconfSortModeBindingID)
1391 	{
1392 		xfconf_g_property_unbind(priv->xfconfSortModeBindingID);
1393 		priv->xfconfSortModeBindingID=0;
1394 	}
1395 
1396 	if(priv->xfconfChannel)
1397 	{
1398 		priv->xfconfChannel=NULL;
1399 	}
1400 
1401 	/* Call parent's class dispose method */
1402 	G_OBJECT_CLASS(xfdashboard_applications_search_provider_parent_class)->dispose(inObject);
1403 }
1404 
1405 /* Set/get properties */
_xfdashboard_applications_search_provider_set_property(GObject * inObject,guint inPropID,const GValue * inValue,GParamSpec * inSpec)1406 static void _xfdashboard_applications_search_provider_set_property(GObject *inObject,
1407 																	guint inPropID,
1408 																	const GValue *inValue,
1409 																	GParamSpec *inSpec)
1410 {
1411 	XfdashboardApplicationsSearchProvider			*self=XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER(inObject);
1412 
1413 	switch(inPropID)
1414 	{
1415 		case PROP_SORT_MODE:
1416 			xfdashboard_applications_search_provider_set_sort_mode(self, g_value_get_flags(inValue));
1417 			break;
1418 
1419 		default:
1420 			G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
1421 			break;
1422 	}
1423 }
1424 
_xfdashboard_applications_search_provider_get_property(GObject * inObject,guint inPropID,GValue * outValue,GParamSpec * inSpec)1425 static void _xfdashboard_applications_search_provider_get_property(GObject *inObject,
1426 																	guint inPropID,
1427 																	GValue *outValue,
1428 																	GParamSpec *inSpec)
1429 {
1430 	XfdashboardApplicationsSearchProvider			*self=XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER(inObject);
1431 	XfdashboardApplicationsSearchProviderPrivate	*priv=self->priv;
1432 
1433 	switch(inPropID)
1434 	{
1435 		case PROP_SORT_MODE:
1436 			g_value_set_flags(outValue, priv->nextSortMode);
1437 			break;
1438 
1439 		default:
1440 			G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
1441 			break;
1442 	}
1443 }
1444 
1445 /* Class initialization
1446  * Override functions in parent classes and define properties
1447  * and signals
1448  */
xfdashboard_applications_search_provider_class_init(XfdashboardApplicationsSearchProviderClass * klass)1449 static void xfdashboard_applications_search_provider_class_init(XfdashboardApplicationsSearchProviderClass *klass)
1450 {
1451 	XfdashboardSearchProviderClass		*providerClass=XFDASHBOARD_SEARCH_PROVIDER_CLASS(klass);
1452 	GObjectClass						*gobjectClass=G_OBJECT_CLASS(klass);
1453 
1454 	/* Override functions */
1455 	gobjectClass->dispose=_xfdashboard_applications_search_provider_dispose;
1456 	gobjectClass->set_property=_xfdashboard_applications_search_provider_set_property;
1457 	gobjectClass->get_property=_xfdashboard_applications_search_provider_get_property;
1458 
1459 	providerClass->initialize=_xfdashboard_applications_search_provider_initialize;
1460 	providerClass->get_name=_xfdashboard_applications_search_provider_get_name;
1461 	providerClass->get_icon=_xfdashboard_applications_search_provider_get_icon;
1462 	providerClass->get_result_set=_xfdashboard_applications_search_provider_get_result_set;
1463 	providerClass->create_result_actor=_xfdashboard_applications_search_provider_create_result_actor;
1464 	providerClass->activate_result=_xfdashboard_applications_search_provider_activate_result;
1465 
1466 	/* Define properties */
1467 	XfdashboardApplicationsSearchProviderProperties[PROP_SORT_MODE]=
1468 		g_param_spec_flags("sort-mode",
1469 							"Sort mode",
1470 							"Defines how to sort matching applications",
1471 							XFDASHBOARD_TYPE_APPLICATIONS_SEARCH_PROVIDER_SORT_MODE,
1472 							XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_SORT_MODE_NONE,
1473 							G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1474 
1475 	g_object_class_install_properties(gobjectClass, PROP_LAST, XfdashboardApplicationsSearchProviderProperties);
1476 }
1477 
1478 /* Object initialization
1479  * Create private structure and set up default values
1480  */
xfdashboard_applications_search_provider_init(XfdashboardApplicationsSearchProvider * self)1481 static void xfdashboard_applications_search_provider_init(XfdashboardApplicationsSearchProvider *self)
1482 {
1483 	XfdashboardApplicationsSearchProviderPrivate	*priv;
1484 
1485 	self->priv=priv=xfdashboard_applications_search_provider_get_instance_private(self);
1486 
1487 	/* Set up default values */
1488 	priv->xfconfChannel=xfdashboard_application_get_xfconf_channel(NULL);
1489 	priv->currentSortMode=XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_SORT_MODE_NONE;
1490 	priv->nextSortMode=XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_SORT_MODE_NONE;
1491 
1492 	/* Get application database */
1493 	priv->appDB=xfdashboard_application_database_get_default();
1494 	priv->applicationAddedID=g_signal_connect_swapped(priv->appDB,
1495 														"application-added",
1496 														G_CALLBACK(_xfdashboard_applications_search_provider_on_application_added),
1497 														self);
1498 	priv->applicationRemovedID=g_signal_connect_swapped(priv->appDB,
1499 														"application-removed",
1500 														G_CALLBACK(_xfdashboard_applications_search_provider_on_application_removed),
1501 														self);
1502 
1503 	/* Get list of all installed applications */
1504 	priv->allApps=xfdashboard_application_database_get_all_applications(priv->appDB);
1505 
1506 	/* Bind to xfconf to react on changes */
1507 	priv->xfconfSortModeBindingID=
1508 		xfconf_g_property_bind(priv->xfconfChannel,
1509 								SORT_MODE_XFCONF_PROP,
1510 								G_TYPE_UINT,
1511 								self,
1512 								"sort-mode");
1513 }
1514 
1515 /* IMPLEMENTATION: Public API */
1516 
1517 /* Get/set sorting mode */
xfdashboard_applications_search_provider_get_sort_mode(XfdashboardApplicationsSearchProvider * self)1518 XfdashboardApplicationsSearchProviderSortMode xfdashboard_applications_search_provider_get_sort_mode(XfdashboardApplicationsSearchProvider *self)
1519 {
1520 	g_return_val_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(self), XFDASHBOARD_APPLICATIONS_SEARCH_PROVIDER_SORT_MODE_NONE);
1521 
1522 	return(self->priv->nextSortMode);
1523 }
1524 
xfdashboard_applications_search_provider_set_sort_mode(XfdashboardApplicationsSearchProvider * self,const XfdashboardApplicationsSearchProviderSortMode inMode)1525 void xfdashboard_applications_search_provider_set_sort_mode(XfdashboardApplicationsSearchProvider *self, const XfdashboardApplicationsSearchProviderSortMode inMode)
1526 {
1527 	XfdashboardApplicationsSearchProviderPrivate	*priv;
1528 
1529 	g_return_if_fail(XFDASHBOARD_IS_APPLICATIONS_SEARCH_PROVIDER(self));
1530 
1531 	priv=self->priv;
1532 
1533 	/* Set value if changed */
1534 	if(priv->nextSortMode!=inMode)
1535 	{
1536 		/* Set value */
1537 		priv->nextSortMode=inMode;
1538 
1539 		/* Notify about property change */
1540 		g_object_notify_by_pspec(G_OBJECT(self), XfdashboardApplicationsSearchProviderProperties[PROP_SORT_MODE]);
1541 	}
1542 }
1543