Merge lp:~larryprice/ubuntu-app-launch/find-theme-icons into lp:ubuntu-app-launch/16.04

Proposed by Larry Price
Status: Merged
Approved by: Ted Gould
Approved revision: 310
Merged at revision: 223
Proposed branch: lp:~larryprice/ubuntu-app-launch/find-theme-icons
Merge into: lp:ubuntu-app-launch/16.04
Prerequisite: lp:~larryprice/ubuntu-app-launch/no-display-options
Diff against target: 1124 lines (+709/-36)
17 files modified
docs/index.rst (+10/-0)
libubuntu-app-launch/CMakeLists.txt (+3/-1)
libubuntu-app-launch/application-icon-finder.cpp (+353/-0)
libubuntu-app-launch/application-icon-finder.h (+89/-0)
libubuntu-app-launch/application-impl-legacy.cpp (+18/-9)
libubuntu-app-launch/application-impl-legacy.h (+3/-1)
libubuntu-app-launch/application-impl-libertine.cpp (+16/-2)
libubuntu-app-launch/application-impl-libertine.h (+2/-1)
libubuntu-app-launch/application-info-desktop.cpp (+26/-11)
libubuntu-app-launch/application-info-desktop.h (+4/-1)
libubuntu-app-launch/application.cpp (+1/-3)
libubuntu-app-launch/application.h (+1/-1)
libubuntu-app-launch/registry-impl.cpp (+11/-0)
libubuntu-app-launch/registry-impl.h (+9/-3)
tests/CMakeLists.txt (+22/-3)
tests/application-icon-finder.cpp (+93/-0)
tests/data/usr/share/icons/hicolor/index.theme (+48/-0)
To merge this branch: bzr merge lp:~larryprice/ubuntu-app-launch/find-theme-icons
Reviewer Review Type Date Requested Status
Ted Gould (community) Approve
Review via email: mp+293287@code.launchpad.net

Commit message

Update icon search for non-click applications to search the hicolor theme directory for appropriate icons.

Description of the change

Update icon search for non-click applications to search the hicolor theme directory for appropriate icons.

Icon update code is based on: https://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html but finds the optimal icon based on maximum size. Accessed through the registry, and caches paths that have already been considered. For only 12 Libertine apps, caching allowed the icon fetch process to take half as much time, so I'm assuming for 120 or 1200 apps it would have an even more drastic effect. Some of the various code paths are based only on what I've observed from various desktop files, such as searching the pixmaps directory when given a file with an extension but not a path.

To post a comment you must log in.
287. By Larry Price

Removing commented line

288. By Larry Price

removing unnecessary env setup in CMakeList

Revision history for this message
Ted Gould (ted) wrote :

Overall, really cool. Love getting all of these features included into the icon searching functionality, I think you're on the right path. A couple of global comments and some inline cleanup stuff!

* Fix for ~/.local installed icons for Steam we discussed on IRC
* Please add the new Class to /docs/index.rst so it gets in the implementation documentation (doc strings are a plus as well, not everywhere yet, but trying to get more in)
* make sure to run "make format" in the lib dir, not sure if you have or not (I don't care, but lots of folks seem to, and it avoids big diffs in the future)

review: Needs Fixing
289. By Larry Price

pixmaps should be fallback for all icons - addresses firefox

290. By Larry Price

use g_build_filepath more often, update interface, better flow [recycle]

291. By Larry Price

sorting directories by size from highest to lowest

292. By Larry Price

using defined cmake source path for tests

293. By Larry Price

adding more test data and a few more tests

294. By Larry Price

running clang format

295. By Larry Price

Adding support for searching local icon directories

296. By Larry Price

adding forgotten steam icons

297. By Larry Price

updating documentation

298. By Larry Price

more documentation for public members of iconfinder

299. By Larry Price

Merge ~ted/ubuntu-app-launch/find-theme-icons-ted

300. By Larry Price

deleting unnecessary null check

301. By Larry Price

Allowing spaces in app names and undoing incorrect local dir stuff

302. By Larry Price

Merge with ted

303. By Larry Price

Fixing some small merge issues, correcting regex for matching dirs

304. By Larry Price

Fixing tests

305. By Larry Price

Merge with trunk

306. By Larry Price

no need for second capture group in regex

307. By Larry Price

Merge with ted

308. By Larry Price

Adding tests for root icons path

309. By Larry Price

running clang format on the test

310. By Larry Price

Adding missing app icons

Revision history for this message
Ted Gould (ted) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'docs/index.rst'
2--- docs/index.rst 2016-04-20 21:29:37 +0000
3+++ docs/index.rst 2016-05-03 14:23:08 +0000
4@@ -106,6 +106,16 @@
5 :private-members:
6 :undoc-members:
7
8+Application Icon Finder
9+------------------------
10+
11+.. doxygenclass:: ubuntu::app_launch::app_info::IconFinder
12+ :project: libubuntu-app-launch
13+ :members:
14+ :protected-members:
15+ :private-members:
16+ :undoc-members:
17+
18 Helper Implementation Click
19 ---------------------------
20
21
22=== modified file 'libubuntu-app-launch/CMakeLists.txt'
23--- libubuntu-app-launch/CMakeLists.txt 2016-02-09 22:51:12 +0000
24+++ libubuntu-app-launch/CMakeLists.txt 2016-05-03 14:23:08 +0000
25@@ -1,4 +1,3 @@
26-
27 include_directories(${CMAKE_CURRENT_SOURCE_DIR})
28 include_directories(${CMAKE_CURRENT_BINARY_DIR})
29
30@@ -40,9 +39,12 @@
31 registry-impl.cpp
32 application-impl-base.cpp
33 application-impl-click.cpp
34+application-impl-legacy.h
35 application-impl-legacy.cpp
36+application-impl-libertine.h
37 application-impl-libertine.cpp
38 application-info-desktop.cpp
39+application-icon-finder.cpp
40 helper-impl-click.cpp
41 glib-thread.cpp
42 )
43
44=== added file 'libubuntu-app-launch/application-icon-finder.cpp'
45--- libubuntu-app-launch/application-icon-finder.cpp 1970-01-01 00:00:00 +0000
46+++ libubuntu-app-launch/application-icon-finder.cpp 2016-05-03 14:23:08 +0000
47@@ -0,0 +1,353 @@
48+/*
49+ * Copyright © 2016 Canonical Ltd.
50+ *
51+ * This program is free software: you can redistribute it and/or modify it
52+ * under the terms of the GNU General Public License version 3, as published
53+ * by the Free Software Foundation.
54+ *
55+ * This program is distributed in the hope that it will be useful, but
56+ * WITHOUT ANY WARRANTY; without even the implied warranties of
57+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
58+ * PURPOSE. See the GNU General Public License for more details.
59+ *
60+ * You should have received a copy of the GNU General Public License along
61+ * with this program. If not, see <http://www.gnu.org/licenses/>.
62+ *
63+ * Authors:
64+ * Larry Price <larry.price@canonical.com>
65+ */
66+
67+#include "application-icon-finder.h"
68+#include <regex>
69+
70+namespace ubuntu
71+{
72+namespace app_launch
73+{
74+namespace
75+{
76+constexpr auto ICONS_DIR = "/icons";
77+constexpr auto HICOLOR_THEME_DIR = "/icons/hicolor";
78+constexpr auto HICOLOR_THEME_FILE = "/icons/hicolor/index.theme";
79+constexpr auto APPLICATIONS_TYPE = "Applications";
80+constexpr auto SIZE_PROPERTY = "Size";
81+constexpr auto MAXSIZE_PROPERTY = "MaxSize";
82+constexpr auto THRESHOLD_PROPERTY = "Threshold";
83+constexpr auto FIXED_CONTEXT = "Fixed";
84+constexpr auto SCALABLE_CONTEXT = "Scalable";
85+constexpr auto THRESHOLD_CONTEXT = "Threshold";
86+constexpr auto CONTEXT_PROPERTY = "Context";
87+constexpr auto TYPE_PROPERTY = "Type";
88+constexpr auto DIRECTORIES_PROPERTY = "Directories";
89+constexpr auto ICON_THEME_KEY = "Icon Theme";
90+constexpr auto PIXMAPS_PATH = "/pixmaps/";
91+constexpr auto ICON_TYPES = {".png", ".svg", ".xpm"};
92+
93+static const std::regex iconSizeDirname = std::regex("^(\\d+)x\\1$");
94+} // anonymous namespace
95+
96+IconFinder::IconFinder(std::string basePath)
97+ : _searchPaths(getSearchPaths(basePath))
98+ , _basePath(basePath)
99+{
100+}
101+
102+/** Finds an icon in the search paths that we have for this path */
103+Application::Info::IconPath IconFinder::find(const std::string& iconName)
104+{
105+ if (iconName[0] == '/') // explicit icon path received
106+ {
107+ return Application::Info::IconPath::from_raw(iconName);
108+ }
109+
110+ /* Look in each directory slowly decreasing the size until we find
111+ an icon */
112+ auto size = 0;
113+ std::string iconPath;
114+ for (const auto& path : _searchPaths)
115+ {
116+ if (path.size > size)
117+ {
118+ auto foundIcon = findExistingIcon(path.path, iconName);
119+ if (!foundIcon.empty())
120+ {
121+ size = path.size;
122+ iconPath = foundIcon;
123+ }
124+ }
125+ }
126+
127+ return Application::Info::IconPath::from_raw(iconPath);
128+}
129+
130+/** Check to see if this is an icon name or an icon filename */
131+bool IconFinder::hasImageExtension(const char* filename)
132+{
133+ for (const auto& extension : ICON_TYPES)
134+ {
135+ if (g_str_has_suffix(filename, extension))
136+ {
137+ return true;
138+ }
139+ }
140+ return false;
141+}
142+
143+/** Check in a given path if there is an existing file in it that satifies our name */
144+std::string IconFinder::findExistingIcon(const std::string& path, const std::string& iconName)
145+{
146+ std::string iconPath;
147+
148+ /* If it already has an extension, only check that one */
149+ if (hasImageExtension(iconName.c_str()))
150+ {
151+ auto fullpath = g_build_filename(path.c_str(), iconName.c_str(), nullptr);
152+ if (g_file_test(fullpath, G_FILE_TEST_EXISTS))
153+ {
154+ iconPath = fullpath;
155+ }
156+ g_free(fullpath);
157+ return iconPath;
158+ }
159+
160+ /* Otherwise check all the valid extensions to see if they exist */
161+ for (const auto& extension : ICON_TYPES)
162+ {
163+ auto fullpath = g_build_filename(path.c_str(), (iconName + extension).c_str(), nullptr);
164+ if (g_file_test(fullpath, G_FILE_TEST_EXISTS))
165+ {
166+ iconPath = fullpath;
167+ g_free(fullpath);
168+ break;
169+ }
170+ g_free(fullpath);
171+ }
172+ return iconPath;
173+}
174+
175+/** Create a directory item if the directory exists */
176+std::list<IconFinder::ThemeSubdirectory> IconFinder::validDirectories(const std::string& basePath,
177+ gchar* directory,
178+ int size)
179+{
180+ std::list<IconFinder::ThemeSubdirectory> dirs;
181+ auto globalHicolorTheme = g_build_filename(basePath.c_str(), HICOLOR_THEME_DIR, directory, nullptr);
182+ if (g_file_test(globalHicolorTheme, G_FILE_TEST_EXISTS))
183+ {
184+ dirs.emplace_back(ThemeSubdirectory{std::string(globalHicolorTheme), size});
185+ }
186+ g_free(globalHicolorTheme);
187+
188+ return dirs;
189+}
190+
191+/** Take the data in a directory stanza and turn it into an actual directory */
192+std::list<IconFinder::ThemeSubdirectory> IconFinder::addSubdirectoryByType(std::shared_ptr<GKeyFile> themefile,
193+ gchar* directory,
194+ const std::string& basePath)
195+{
196+ GError* error = nullptr;
197+ auto gType = g_key_file_get_string(themefile.get(), directory, TYPE_PROPERTY, &error);
198+ if (error != nullptr)
199+ {
200+ g_error_free(error);
201+ return std::list<ThemeSubdirectory>{};
202+ }
203+ std::string type(gType);
204+ g_free(gType);
205+
206+ if (type == FIXED_CONTEXT)
207+ {
208+ auto size = g_key_file_get_integer(themefile.get(), directory, SIZE_PROPERTY, &error);
209+ if (error != nullptr)
210+ {
211+ g_error_free(error);
212+ }
213+ else
214+ {
215+ return validDirectories(basePath, directory, size);
216+ }
217+ }
218+ else if (type == SCALABLE_CONTEXT)
219+ {
220+ auto size = g_key_file_get_integer(themefile.get(), directory, MAXSIZE_PROPERTY, &error);
221+ if (error != nullptr)
222+ {
223+ g_error_free(error);
224+ }
225+ else
226+ {
227+ return validDirectories(basePath, directory, size);
228+ }
229+ }
230+ else if (type == THRESHOLD_CONTEXT)
231+ {
232+ auto size = g_key_file_get_integer(themefile.get(), directory, SIZE_PROPERTY, &error);
233+ if (error != nullptr)
234+ {
235+ g_error_free(error);
236+ }
237+ else
238+ {
239+ auto threshold = g_key_file_get_integer(themefile.get(), directory, THRESHOLD_PROPERTY, &error);
240+ if (error != nullptr)
241+ {
242+ threshold = 2; // threshold defaults to 2
243+ g_error_free(error);
244+ }
245+ return validDirectories(basePath, directory, size + threshold);
246+ }
247+ }
248+ return std::list<ThemeSubdirectory>{};
249+}
250+
251+/** Parse a theme file's various stanzas for each directory */
252+std::list<IconFinder::ThemeSubdirectory> IconFinder::searchIconPaths(std::shared_ptr<GKeyFile> themefile,
253+ gchar** directories,
254+ const std::string& basePath)
255+{
256+ std::list<ThemeSubdirectory> subdirs;
257+ for (auto i = 0; directories[i] != nullptr; ++i)
258+ {
259+ GError* error = nullptr;
260+ auto context = g_key_file_get_string(themefile.get(), directories[i], CONTEXT_PROPERTY, &error);
261+ if (error != nullptr)
262+ {
263+ g_error_free(error);
264+ continue;
265+ }
266+ if (g_strcmp0(context, APPLICATIONS_TYPE) == 0)
267+ {
268+ auto newDirs = addSubdirectoryByType(themefile, directories[i], basePath);
269+ subdirs.splice(subdirs.end(), newDirs);
270+ }
271+ g_free(context);
272+ }
273+ return subdirs;
274+}
275+
276+/** Try to get theme subdirectories using .theme file in the hicolor theme file
277+ if it exists */
278+std::list<IconFinder::ThemeSubdirectory> IconFinder::themeFileSearchPaths(const std::string& basePath)
279+{
280+ std::string themeFilePath = basePath + HICOLOR_THEME_FILE;
281+ GError* error = nullptr;
282+ auto themefile = std::shared_ptr<GKeyFile>(g_key_file_new(), g_key_file_free);
283+ g_key_file_load_from_file(themefile.get(), themeFilePath.c_str(), G_KEY_FILE_NONE, &error);
284+ if (error != nullptr)
285+ {
286+ g_debug("Unable to find Hicolor theme file: %s", themeFilePath.c_str());
287+ g_error_free(error);
288+ return std::list<ThemeSubdirectory>();
289+ }
290+
291+ g_key_file_set_list_separator(themefile.get(), ',');
292+ auto directories =
293+ g_key_file_get_string_list(themefile.get(), ICON_THEME_KEY, DIRECTORIES_PROPERTY, nullptr, &error);
294+ if (error != nullptr)
295+ {
296+ g_debug("Hicolor theme file '%s' didn't have any directories", themeFilePath.c_str());
297+ g_error_free(error);
298+ return std::list<ThemeSubdirectory>();
299+ }
300+
301+ auto iconPaths = searchIconPaths(themefile, directories, basePath);
302+ g_strfreev(directories);
303+ return iconPaths;
304+}
305+
306+/** Look into a theme directory and see if we can use the subdirectories
307+ as icon folders. Sadly inefficient. */
308+std::list<IconFinder::ThemeSubdirectory> IconFinder::themeDirSearchPaths(const std::string& themeDir)
309+{
310+ std::list<IconFinder::ThemeSubdirectory> searchPaths;
311+ GError* error = nullptr;
312+ auto gdir = g_dir_open(themeDir.c_str(), 0, &error);
313+
314+ if (error != nullptr)
315+ {
316+ g_debug("Unable to open directory '%s' becuase: %s", themeDir.c_str(), error->message);
317+ g_error_free(error);
318+ return searchPaths;
319+ }
320+
321+ const gchar* dirname = nullptr;
322+ while ((dirname = g_dir_read_name(gdir)) != nullptr)
323+ {
324+ auto fullPath = g_build_filename(themeDir.c_str(), dirname, "apps", nullptr);
325+ /* Directories only */
326+ if (g_file_test(fullPath, G_FILE_TEST_IS_DIR))
327+ {
328+ if (g_strcmp0(dirname, "scalable") == 0)
329+ {
330+ /* We don't really know what to do with scalable here, let's
331+ call them 256 images */
332+ searchPaths.emplace_back(IconFinder::ThemeSubdirectory{fullPath, 256});
333+ continue;
334+ }
335+
336+ std::smatch match;
337+ std::string dirstr(dirname);
338+ /* We want it to match and have the same values for the first and second size */
339+ if (std::regex_match(dirstr, match, iconSizeDirname))
340+ {
341+ searchPaths.emplace_back(IconFinder::ThemeSubdirectory{fullPath, std::atoi(match[1].str().c_str())});
342+ }
343+ }
344+ g_free(fullPath);
345+ }
346+
347+ g_dir_close(gdir);
348+ return searchPaths;
349+}
350+
351+/** Gets all the search paths, from either a theme file or just
352+ looking at the directory. And possibly pixmaps as well */
353+std::list<IconFinder::ThemeSubdirectory> IconFinder::getSearchPaths(const std::string& basePath)
354+{
355+ std::list<IconFinder::ThemeSubdirectory> iconPaths;
356+
357+ auto hicolorDir = g_build_filename(basePath.c_str(), HICOLOR_THEME_DIR, nullptr);
358+ if (g_file_test(hicolorDir, G_FILE_TEST_IS_DIR))
359+ {
360+ /* If the directory exists, it could have icons of unknown size */
361+ iconPaths.emplace_back(IconFinder::ThemeSubdirectory{hicolorDir, 1});
362+
363+ /* Now see if we can get directories from a theme file */
364+ auto themeDirs = themeFileSearchPaths(basePath);
365+ if (themeDirs.size() > 0)
366+ {
367+ iconPaths.splice(iconPaths.end(), themeDirs);
368+ }
369+ else
370+ {
371+ /* If we didn't get from a theme file, we need to manually scan the directories */
372+ auto dirPaths = themeDirSearchPaths(hicolorDir);
373+ iconPaths.splice(iconPaths.end(), dirPaths);
374+ }
375+ }
376+ g_free(hicolorDir);
377+
378+ /* Add root icons directory as potential path */
379+ auto iconsPath = g_build_filename(basePath.c_str(), ICONS_DIR, nullptr);
380+ if (g_file_test(iconsPath, G_FILE_TEST_IS_DIR))
381+ {
382+ iconPaths.emplace_back(IconFinder::ThemeSubdirectory{iconsPath, 1});
383+ }
384+ g_free(iconsPath);
385+
386+ /* Add the pixmaps path as a fallback if it exists */
387+ auto pixmapsPath = g_build_filename(basePath.c_str(), PIXMAPS_PATH, nullptr);
388+ if (g_file_test(pixmapsPath, G_FILE_TEST_IS_DIR))
389+ {
390+ iconPaths.emplace_back(IconFinder::ThemeSubdirectory{pixmapsPath, 1});
391+ }
392+ g_free(pixmapsPath);
393+
394+ // find icons sorted by size, highest to lowest
395+ iconPaths.sort([](const ThemeSubdirectory& lhs, const ThemeSubdirectory& rhs) { return lhs.size > rhs.size; });
396+ return iconPaths;
397+}
398+
399+} // namesapce app_launch
400+} // namespace ubuntu
401
402=== added file 'libubuntu-app-launch/application-icon-finder.h'
403--- libubuntu-app-launch/application-icon-finder.h 1970-01-01 00:00:00 +0000
404+++ libubuntu-app-launch/application-icon-finder.h 2016-05-03 14:23:08 +0000
405@@ -0,0 +1,89 @@
406+/*
407+ * Copyright © 2016 Canonical Ltd.
408+ *
409+ * This program is free software: you can redistribute it and/or modify it
410+ * under the terms of the GNU General Public License version 3, as published
411+ * by the Free Software Foundation.
412+ *
413+ * This program is distributed in the hope that it will be useful, but
414+ * WITHOUT ANY WARRANTY; without even the implied warranties of
415+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
416+ * PURPOSE. See the GNU General Public License for more details.
417+ *
418+ * You should have received a copy of the GNU General Public License along
419+ * with this program. If not, see <http://www.gnu.org/licenses/>.
420+ *
421+ * Authors:
422+ * Larry Price <larry.price@canonical.com>
423+ */
424+
425+#pragma once
426+
427+#include "application-info-desktop.h"
428+#include <glib.h>
429+#include <list>
430+#include <map>
431+#include <memory>
432+
433+namespace ubuntu
434+{
435+namespace app_launch
436+{
437+/** \brief Class to search for available application icons and select the best option.
438+
439+ This object attempts to find the highest resolution icon based on the freedesktop icon
440+ theme specification found at:
441+ https://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
442+ It parses the theme file for the hicolor theme and identifies all possible directories
443+ in the global scope and the local scope.
444+*/
445+class IconFinder
446+{
447+public:
448+ /** Create an IconFinder
449+
450+ \param basePath the root directory to begin searching for themes
451+ */
452+ explicit IconFinder(std::string basePath);
453+ virtual ~IconFinder() = default;
454+
455+ /** Find the optimal icon for the given icon name.
456+
457+ \param iconName name of or path to application icon
458+ */
459+ virtual Application::Info::IconPath find(const std::string& iconName);
460+
461+private:
462+ /** \private */
463+ struct ThemeSubdirectory
464+ {
465+ std::string path;
466+ int size;
467+ };
468+
469+ /** \private */
470+ std::list<ThemeSubdirectory> _searchPaths;
471+ /** \private */
472+ std::string _basePath;
473+
474+ /** \private */
475+ static bool hasImageExtension(const char* filename);
476+ /** \private */
477+ static std::string findExistingIcon(const std::string& path, const std::string& iconName);
478+ /** \private */
479+ static std::list<ThemeSubdirectory> validDirectories(const std::string& basePath, gchar* directory, int size);
480+ /** \private */
481+ static std::list<ThemeSubdirectory> addSubdirectoryByType(std::shared_ptr<GKeyFile> themefile,
482+ gchar* directory,
483+ const std::string& basePath);
484+ /** \private */
485+ static std::list<ThemeSubdirectory> searchIconPaths(std::shared_ptr<GKeyFile> themefile,
486+ gchar** directories,
487+ const std::string& basePath);
488+ static std::list<ThemeSubdirectory> themeFileSearchPaths(const std::string& basePath);
489+ static std::list<ThemeSubdirectory> themeDirSearchPaths(const std::string& basePath);
490+ static std::list<ThemeSubdirectory> getSearchPaths(const std::string& basePath);
491+};
492+
493+} // namespace app_launch
494+} // namesapce ubuntu
495
496=== modified file 'libubuntu-app-launch/application-impl-legacy.cpp'
497--- libubuntu-app-launch/application-impl-legacy.cpp 2016-03-03 20:48:24 +0000
498+++ libubuntu-app-launch/application-impl-legacy.cpp 2016-05-03 14:23:08 +0000
499@@ -27,7 +27,7 @@
500 namespace app_impls
501 {
502
503-std::shared_ptr<GKeyFile> keyfileForApp(const AppID::AppName& name);
504+std::pair<std::string, std::shared_ptr<GKeyFile>> keyfileForApp(const AppID::AppName& name);
505
506 void clear_keyfile(GKeyFile* keyfile)
507 {
508@@ -38,10 +38,12 @@
509 }
510
511 Legacy::Legacy(const AppID::AppName& appname,
512+ const std::string& basedir,
513 const std::shared_ptr<GKeyFile>& keyfile,
514 const std::shared_ptr<Registry>& registry)
515 : Base(registry)
516 , _appname(appname)
517+ , _basedir(basedir)
518 , _keyfile(keyfile)
519 {
520 if (!_keyfile)
521@@ -49,15 +51,20 @@
522 }
523
524 Legacy::Legacy(const AppID::AppName& appname, const std::shared_ptr<Registry>& registry)
525- : Legacy(appname, keyfileForApp(appname), registry)
526+ : Base(registry)
527+ , _appname(appname)
528 {
529+ std::tie(_basedir, _keyfile) = keyfileForApp(appname);
530+
531+ if (!_keyfile)
532+ throw std::runtime_error{"Unable to find keyfile for legacy application: " + appname.value()};
533 }
534
535-std::shared_ptr<GKeyFile> keyfileForApp(const AppID::AppName& name)
536+std::pair<std::string, std::shared_ptr<GKeyFile>> keyfileForApp(const AppID::AppName& name)
537 {
538 std::string desktopName = name.value() + ".desktop";
539- auto keyfilecheck = [desktopName](const gchar* dir) -> std::shared_ptr<GKeyFile> {
540- auto fullname = g_build_filename(dir, "applications", desktopName.c_str(), nullptr);
541+ auto keyfilecheck = [desktopName](const std::string& dir) -> std::shared_ptr<GKeyFile> {
542+ auto fullname = g_build_filename(dir.c_str(), "applications", desktopName.c_str(), nullptr);
543 if (!g_file_test(fullname, G_FILE_TEST_EXISTS))
544 {
545 g_free(fullname);
546@@ -80,20 +87,22 @@
547 return keyfile;
548 };
549
550- auto retval = keyfilecheck(g_get_user_data_dir());
551+ std::string basedir = g_get_user_data_dir();
552+ auto retval = keyfilecheck(basedir);
553
554 auto systemDirs = g_get_system_data_dirs();
555 for (auto i = 0; !retval && systemDirs[i] != nullptr; i++)
556 {
557- retval = keyfilecheck(systemDirs[i]);
558+ basedir = systemDirs[i];
559+ retval = keyfilecheck(basedir);
560 }
561
562- return retval;
563+ return std::make_pair(basedir, retval);
564 }
565
566 std::shared_ptr<Application::Info> Legacy::info()
567 {
568- return std::make_shared<app_info::Desktop>(_keyfile, "/usr/share/icons/");
569+ return std::make_shared<app_info::Desktop>(_keyfile, _basedir, _registry, true);
570 }
571
572 std::list<std::shared_ptr<Application>> Legacy::list(const std::shared_ptr<Registry>& registry)
573
574=== modified file 'libubuntu-app-launch/application-impl-legacy.h'
575--- libubuntu-app-launch/application-impl-legacy.h 2016-02-10 15:27:10 +0000
576+++ libubuntu-app-launch/application-impl-legacy.h 2016-05-03 14:23:08 +0000
577@@ -35,6 +35,7 @@
578 public:
579 Legacy(const AppID::AppName& appname, const std::shared_ptr<Registry>& registry);
580 Legacy(const AppID::AppName& appname,
581+ const std::string& basedir,
582 const std::shared_ptr<GKeyFile>& keyfile,
583 const std::shared_ptr<Registry>& registry);
584
585@@ -45,10 +46,11 @@
586
587 std::shared_ptr<Info> info() override;
588
589- static std::list<std::shared_ptr<Application>> list(const std::shared_ptr<Registry> &registry);
590+ static std::list<std::shared_ptr<Application>> list(const std::shared_ptr<Registry>& registry);
591
592 private:
593 AppID::AppName _appname;
594+ std::string _basedir;
595 std::shared_ptr<GKeyFile> _keyfile;
596 };
597
598
599=== modified file 'libubuntu-app-launch/application-impl-libertine.cpp'
600--- libubuntu-app-launch/application-impl-libertine.cpp 2016-03-03 20:48:24 +0000
601+++ libubuntu-app-launch/application-impl-libertine.cpp 2016-05-03 14:23:08 +0000
602@@ -41,10 +41,17 @@
603 {
604 auto container_path = libertine_container_path(container.value().c_str());
605 auto container_app_path = g_build_filename(container_path, "usr", "share", "applications",
606- (appname.value() + ".desktop").c_str(), NULL);
607+ (appname.value() + ".desktop").c_str(), nullptr);
608
609 _keyfile = keyfileFromPath(container_app_path);
610
611+ if (_keyfile)
612+ {
613+ auto gbasedir = g_build_filename(container_path, "usr", "share", nullptr);
614+ _basedir = gbasedir;
615+ g_free(gbasedir);
616+ }
617+
618 g_free(container_app_path);
619 g_free(container_path);
620 }
621@@ -57,6 +64,13 @@
622
623 _keyfile = keyfileFromPath(home_app_path);
624
625+ if (_keyfile)
626+ {
627+ auto gbasedir = g_build_filename(home_path, ".local", "share", nullptr);
628+ _basedir = gbasedir;
629+ g_free(gbasedir);
630+ }
631+
632 g_free(home_app_path);
633 g_free(home_path);
634 }
635@@ -119,7 +133,7 @@
636
637 std::shared_ptr<Application::Info> Libertine::info()
638 {
639- return std::make_shared<app_info::Desktop>(_keyfile, libertine_container_path(_container.value().c_str()));
640+ return std::make_shared<app_info::Desktop>(_keyfile, _basedir, _registry);
641 }
642
643 }; // namespace app_impls
644
645=== modified file 'libubuntu-app-launch/application-impl-libertine.h'
646--- libubuntu-app-launch/application-impl-libertine.h 2016-02-10 15:38:21 +0000
647+++ libubuntu-app-launch/application-impl-libertine.h 2016-05-03 14:23:08 +0000
648@@ -36,7 +36,7 @@
649 const AppID::AppName& appname,
650 const std::shared_ptr<Registry>& registry);
651
652- static std::list<std::shared_ptr<Application>> list(const std::shared_ptr<Registry> &registry);
653+ static std::list<std::shared_ptr<Application>> list(const std::shared_ptr<Registry>& registry);
654
655 AppID appId() override
656 {
657@@ -49,6 +49,7 @@
658 AppID::Package _container;
659 AppID::AppName _appname;
660 std::shared_ptr<GKeyFile> _keyfile;
661+ std::string _basedir;
662 };
663
664 }; // namespace app_impls
665
666=== modified file 'libubuntu-app-launch/application-info-desktop.cpp'
667--- libubuntu-app-launch/application-info-desktop.cpp 2016-05-03 14:23:08 +0000
668+++ libubuntu-app-launch/application-info-desktop.cpp 2016-05-03 14:23:08 +0000
669@@ -18,6 +18,8 @@
670 */
671
672 #include "application-info-desktop.h"
673+#include "application-icon-finder.h"
674+#include "registry-impl.h"
675 #include <cstdlib>
676
677 namespace ubuntu
678@@ -38,7 +40,7 @@
679
680 struct NoDisplayTag;
681 typedef TypeTagger<NoDisplayTag, bool> NoDisplay;
682-} // anonymous namespace
683+} // anonymous namespace
684
685 template <typename T>
686 auto stringFromKeyfile(std::shared_ptr<GKeyFile> keyfile, const std::string& key, const std::string& exceptionText = {})
687@@ -134,8 +136,8 @@
688 auto results = g_key_file_get_string_list(keyfile.get(), DESKTOP_GROUP, key, nullptr, &error);
689 if (error != nullptr)
690 {
691- g_error_free(error);
692- return defaultValue;
693+ g_error_free(error);
694+ return defaultValue;
695 }
696
697 bool result = false;
698@@ -152,8 +154,11 @@
699 return result;
700 }
701
702-Desktop::Desktop(std::shared_ptr<GKeyFile> keyfile, const std::string& basePath)
703- : _keyfile([keyfile]() {
704+Desktop::Desktop(std::shared_ptr<GKeyFile> keyfile,
705+ const std::string& basePath,
706+ std::shared_ptr<Registry> registry,
707+ bool allowNoDisplay)
708+ : _keyfile([keyfile, allowNoDisplay]() {
709 if (!keyfile)
710 {
711 throw std::runtime_error("Can not build a desktop application info object with a null keyfile");
712@@ -162,7 +167,7 @@
713 {
714 throw std::runtime_error("Keyfile does not represent application type");
715 }
716- if (boolFromKeyfile<NoDisplay>(keyfile, "NoDisplay", false).value())
717+ if (boolFromKeyfile<NoDisplay>(keyfile, "NoDisplay", false).value() && !allowNoDisplay)
718 {
719 throw std::runtime_error("Application is not meant to be displayed");
720 }
721@@ -171,10 +176,13 @@
722 throw std::runtime_error("Application keyfile is hidden");
723 }
724 auto xdg_current_desktop = getenv("XDG_CURRENT_DESKTOP");
725- if (stringlistFromKeyfileContains(keyfile, "NotShowIn", xdg_current_desktop, false)
726- || !stringlistFromKeyfileContains(keyfile, "OnlyShowIn", xdg_current_desktop, true))
727+ if (xdg_current_desktop != nullptr)
728 {
729- throw std::runtime_error("Application is not shown in Unity");
730+ if (stringlistFromKeyfileContains(keyfile, "NotShowIn", xdg_current_desktop, false) ||
731+ !stringlistFromKeyfileContains(keyfile, "OnlyShowIn", xdg_current_desktop, true))
732+ {
733+ throw std::runtime_error("Application is not shown in Unity");
734+ }
735 }
736
737 return keyfile;
738@@ -182,8 +190,15 @@
739 , _basePath(basePath)
740 , _name(stringFromKeyfile<Application::Info::Name>(keyfile, "Name", "Unable to get name from keyfile"))
741 , _description(stringFromKeyfile<Application::Info::Description>(keyfile, "Comment"))
742- , _iconPath(
743- fileFromKeyfile<Application::Info::IconPath>(keyfile, basePath, "Icon", "Missing icon for desktop file"))
744+ , _iconPath([keyfile, basePath, registry]() {
745+ if (registry != nullptr)
746+ {
747+ auto iconName =
748+ stringFromKeyfile<Application::Info::IconPath>(keyfile, "Icon", "Missing icon for desktop file");
749+ return registry->impl->getIconFinder(basePath)->find(iconName);
750+ }
751+ return fileFromKeyfile<Application::Info::IconPath>(keyfile, basePath, "Icon", "Missing icon for desktop file");
752+ }())
753 , _splashInfo({
754 title : stringFromKeyfile<Application::Info::Splash::Title>(keyfile, "X-Ubuntu-Splash-Title"),
755 image : fileFromKeyfile<Application::Info::Splash::Image>(keyfile, basePath, "X-Ubuntu-Splash-Image"),
756
757=== modified file 'libubuntu-app-launch/application-info-desktop.h'
758--- libubuntu-app-launch/application-info-desktop.h 2016-02-10 22:04:00 +0000
759+++ libubuntu-app-launch/application-info-desktop.h 2016-05-03 14:23:08 +0000
760@@ -33,7 +33,10 @@
761 class Desktop : public Application::Info
762 {
763 public:
764- Desktop(std::shared_ptr<GKeyFile> keyfile, const std::string& basePath);
765+ Desktop(std::shared_ptr<GKeyFile> keyfile,
766+ const std::string& basePath,
767+ std::shared_ptr<Registry> registry = nullptr,
768+ bool allowNoDisplay = false);
769
770 const Application::Info::Name& name() override
771 {
772
773=== modified file 'libubuntu-app-launch/application.cpp'
774--- libubuntu-app-launch/application.cpp 2016-04-27 14:22:39 +0000
775+++ libubuntu-app-launch/application.cpp 2016-05-03 14:23:08 +0000
776@@ -75,10 +75,8 @@
777 {
778 }
779
780-/* These are the Regex's taken from the Click Reviewer Tools
781- on Jan 16, 2016 revision 566 */
782 #define REGEX_PKGNAME "([a-z0-9][a-z0-9+.-]+)"
783-#define REGEX_APPNAME "([A-Za-z0-9+-.:~-]+)"
784+#define REGEX_APPNAME "([A-Za-z0-9+-.:~-][\\sA-Za-z0-9+-.:~-]+)"
785 #define REGEX_VERSION "([\\d+:]?[A-Za-z0-9.+:~-]+?(?:-[A-Za-z0-9+.~]+)?)"
786
787 const std::regex full_appid_regex("^" REGEX_PKGNAME "_" REGEX_APPNAME "_" REGEX_VERSION "$");
788
789=== modified file 'libubuntu-app-launch/application.h'
790--- libubuntu-app-launch/application.h 2016-04-19 02:14:03 +0000
791+++ libubuntu-app-launch/application.h 2016-05-03 14:23:08 +0000
792@@ -38,7 +38,7 @@
793 /** \brief Class to represent an application, whether running or not, and
794 query more information about it.
795
796- Generally the Application object represnts an Application in the system. It
797+ Generally the Application object represents an Application in the system. It
798 hooks up all of it's signals, finds out information about it and controls
799 whether it is running or not. This class is what most users of Ubuntu App
800 Launch will do the majority of their work.
801
802=== modified file 'libubuntu-app-launch/registry-impl.cpp'
803--- libubuntu-app-launch/registry-impl.cpp 2016-03-03 20:48:24 +0000
804+++ libubuntu-app-launch/registry-impl.cpp 2016-05-03 14:23:08 +0000
805@@ -18,6 +18,7 @@
806 */
807
808 #include "registry-impl.h"
809+#include "application-icon-finder.h"
810
811 namespace ubuntu
812 {
813@@ -34,6 +35,7 @@
814 _dbus.reset();
815 })
816 , _registry(registry)
817+ , _iconFinders()
818 // _manager(nullptr)
819 {
820 auto cancel = thread.getCancellable();
821@@ -162,6 +164,15 @@
822 });
823 }
824
825+std::shared_ptr<IconFinder> Registry::Impl::getIconFinder(std::string basePath)
826+{
827+ if (_iconFinders.find(basePath) == _iconFinders.end())
828+ {
829+ _iconFinders[basePath] = std::make_shared<IconFinder>(basePath);
830+ }
831+ return _iconFinders[basePath];
832+}
833+
834 #if 0
835 void
836 Registry::Impl::setManager (Registry::Manager* manager)
837
838=== modified file 'libubuntu-app-launch/registry-impl.h'
839--- libubuntu-app-launch/registry-impl.h 2016-04-18 18:29:32 +0000
840+++ libubuntu-app-launch/registry-impl.h 2016-05-03 14:23:08 +0000
841@@ -17,12 +17,12 @@
842 * Ted Gould <ted.gould@canonical.com>
843 */
844
845+#include "glib-thread.h"
846 #include "registry.h"
847-#include "glib-thread.h"
848-
849-#include <json-glib/json-glib.h>
850 #include <click.h>
851 #include <gio/gio.h>
852+#include <json-glib/json-glib.h>
853+#include <unordered_map>
854
855 #pragma once
856
857@@ -31,6 +31,8 @@
858 namespace app_launch
859 {
860
861+class IconFinder;
862+
863 /** \private
864 \brief Private implementation of the Registry object
865
866@@ -55,6 +57,8 @@
867
868 GLib::ContextThread thread;
869
870+ std::shared_ptr<IconFinder> getIconFinder(std::string basePath);
871+
872 private:
873 Registry* _registry;
874 #if 0
875@@ -67,6 +71,8 @@
876 std::shared_ptr<GDBusConnection> _dbus;
877
878 void initClick();
879+
880+ std::unordered_map<std::string, std::shared_ptr<IconFinder>> _iconFinders;
881 };
882
883 }; // namespace app_launch
884
885=== modified file 'tests/CMakeLists.txt'
886--- tests/CMakeLists.txt 2016-02-17 21:56:34 +0000
887+++ tests/CMakeLists.txt 2016-05-03 14:23:08 +0000
888@@ -1,4 +1,3 @@
889-
890 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
891
892 configure_file ("click-db-dir/test.conf.in" "${CMAKE_CURRENT_BINARY_DIR}/click-db-dir/test.conf" @ONLY)
893@@ -60,12 +59,32 @@
894 # Application Info Desktop
895
896 add_executable (application-info-desktop-test
897- application-info-desktop.cpp
898- ${CMAKE_SOURCE_DIR}/libubuntu-app-launch/application-info-desktop.cpp)
899+ # test
900+ application-info-desktop.cpp
901+
902+ # sources
903+ ${CMAKE_SOURCE_DIR}/libubuntu-app-launch/application-info-desktop.cpp
904+ ${CMAKE_SOURCE_DIR}/libubuntu-app-launch/application-icon-finder.cpp
905+ ${CMAKE_SOURCE_DIR}/libubuntu-app-launch/glib-thread.cpp
906+ ${CMAKE_SOURCE_DIR}/libubuntu-app-launch/registry-impl.cpp)
907 target_link_libraries (application-info-desktop-test gtest ${GTEST_LIBS} ubuntu-launcher)
908
909 add_test (NAME application-info-desktop-test COMMAND application-info-desktop-test)
910
911+# Application Icon Finder
912+
913+add_executable (application-icon-finder-test
914+ # test
915+ application-icon-finder.cpp
916+
917+ #sources
918+ ${CMAKE_SOURCE_DIR}/libubuntu-app-launch/application-icon-finder.cpp)
919+target_link_libraries (application-icon-finder-test gtest ${GTEST_LIBS} ubuntu-launcher)
920+
921+add_test (NAME application-icon-finder-test COMMAND application-icon-finder-test)
922+
923+file(COPY data DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
924+
925 # Failure Test
926
927 add_definitions ( -DAPP_FAILED_TOOL="${CMAKE_BINARY_DIR}/application-failed" )
928
929=== added file 'tests/application-icon-finder.cpp'
930--- tests/application-icon-finder.cpp 1970-01-01 00:00:00 +0000
931+++ tests/application-icon-finder.cpp 2016-05-03 14:23:08 +0000
932@@ -0,0 +1,93 @@
933+/*
934+ * Copyright © 2016 Canonical Ltd.
935+ *
936+ * This program is free software: you can redistribute it and/or modify it
937+ * under the terms of the GNU General Public License version 3, as published
938+ * by the Free Software Foundation.
939+ *
940+ * This program is distributed in the hope that it will be useful, but
941+ * WITHOUT ANY WARRANTY; without even the implied warranties of
942+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
943+ * PURPOSE. See the GNU General Public License for more details.
944+ *
945+ * You should have received a copy of the GNU General Public License along
946+ * with this program. If not, see <http://www.gnu.org/licenses/>.
947+ *
948+ * Authors:
949+ * Larry Price <larry.price@canonical.com>
950+ */
951+
952+#include "application-icon-finder.h"
953+#include <gtest/gtest.h>
954+
955+using namespace ubuntu::app_launch;
956+
957+TEST(ApplicationIconFinder, ReturnsEmptyWhenNoThemeFileAvailable)
958+{
959+ IconFinder finder("/tmp/please/dont/put/stuff/here");
960+ EXPECT_TRUE(finder.find("app").value().empty());
961+}
962+
963+TEST(ApplicationIconFinder, ReturnsEmptyWhenNoAppIconFound)
964+{
965+ auto basePath = std::string(CMAKE_SOURCE_DIR) + "/data";
966+ IconFinder finder(basePath);
967+ EXPECT_TRUE(finder.find("app_unknown").value().empty());
968+}
969+
970+TEST(ApplicationIconFinder, ReturnsLargestAvailableIcon)
971+{
972+ auto basePath = std::string(CMAKE_SOURCE_DIR) + "/data/usr/share";
973+ IconFinder finder(basePath);
974+ EXPECT_EQ(basePath + "/icons/hicolor/24x24/apps/app.xpm", finder.find("app").value());
975+}
976+
977+TEST(ApplicationIconFinder, ReturnsLargestAvailableIconIncludingLocalIcons)
978+{
979+ auto basePath = std::string(CMAKE_SOURCE_DIR) + "/data/home/test/.local/share";
980+ IconFinder finder(basePath);
981+ EXPECT_EQ(basePath + "/icons/hicolor/32x32/apps/steam_123456.png", finder.find("steam_123456").value());
982+}
983+
984+TEST(ApplicationIconFinder, ReturnsIconAsDirectlyGiven)
985+{
986+ auto basePath = std::string(CMAKE_SOURCE_DIR) + "/data/usr/share";
987+ IconFinder finder(basePath);
988+ EXPECT_EQ(basePath + "/icons/hicolor/scalable/apps/app.svg",
989+ finder.find(basePath + "/icons/hicolor/scalable/apps/app.svg").value());
990+}
991+
992+TEST(ApplicationIconFinder, ReturnsIconFromPixmapAsFallback)
993+{
994+ auto basePath = std::string(CMAKE_SOURCE_DIR) + "/data/usr/share";
995+ IconFinder finder(basePath);
996+ EXPECT_EQ(basePath + "/pixmaps/app2.png", finder.find("app2.png").value());
997+}
998+
999+TEST(ApplicationIconFinder, ReturnsIconFromRootThemeDirectory)
1000+{
1001+ auto basePath = std::string(CMAKE_SOURCE_DIR) + "/data/usr/share";
1002+ IconFinder finder(basePath);
1003+ EXPECT_EQ(basePath + "/icons/hicolor/app4.png", finder.find("app4.png").value());
1004+}
1005+
1006+TEST(ApplicationIconFinder, ReturnsIconFromRootIconsDirectory)
1007+{
1008+ auto basePath = std::string(CMAKE_SOURCE_DIR) + "/data/usr/share";
1009+ IconFinder finder(basePath);
1010+ EXPECT_EQ(basePath + "/icons/app5.png", finder.find("app5.png").value());
1011+}
1012+
1013+TEST(ApplicationIconFinder, ReturnsThresholdIconBasedOnGap)
1014+{
1015+ auto basePath = std::string(CMAKE_SOURCE_DIR) + "/data/usr/share";
1016+ IconFinder finder(basePath);
1017+ EXPECT_EQ(basePath + "/icons/hicolor/22x22/apps/app1.png", finder.find("app1.png").value());
1018+}
1019+
1020+TEST(ApplicationIconFinder, IgnoresDirectoriesWithJunkSize)
1021+{
1022+ auto basePath = std::string(CMAKE_SOURCE_DIR) + "/data/usr/share";
1023+ IconFinder finder(basePath);
1024+ EXPECT_EQ(basePath + "/icons/hicolor/16x16/apps/app3.png", finder.find("app3.png").value());
1025+}
1026
1027=== added directory 'tests/data'
1028=== added directory 'tests/data/home'
1029=== added directory 'tests/data/home/test'
1030=== added directory 'tests/data/home/test/.local'
1031=== added directory 'tests/data/home/test/.local/share'
1032=== added directory 'tests/data/home/test/.local/share/icons'
1033=== added directory 'tests/data/home/test/.local/share/icons/hicolor'
1034=== added directory 'tests/data/home/test/.local/share/icons/hicolor/32x32'
1035=== added directory 'tests/data/home/test/.local/share/icons/hicolor/32x32/apps'
1036=== added file 'tests/data/home/test/.local/share/icons/hicolor/32x32/apps/steam_123456.png'
1037=== added directory 'tests/data/usr'
1038=== added directory 'tests/data/usr/share'
1039=== added directory 'tests/data/usr/share/icons'
1040=== added file 'tests/data/usr/share/icons/app5.png'
1041=== added directory 'tests/data/usr/share/icons/hicolor'
1042=== added directory 'tests/data/usr/share/icons/hicolor/16x16'
1043=== added directory 'tests/data/usr/share/icons/hicolor/16x16/actions'
1044=== added file 'tests/data/usr/share/icons/hicolor/16x16/actions/app.png'
1045=== added directory 'tests/data/usr/share/icons/hicolor/16x16/apps'
1046=== added file 'tests/data/usr/share/icons/hicolor/16x16/apps/app.png'
1047=== added file 'tests/data/usr/share/icons/hicolor/16x16/apps/app3.png'
1048=== added file 'tests/data/usr/share/icons/hicolor/16x16/apps/steam_123456.png'
1049=== added directory 'tests/data/usr/share/icons/hicolor/20x20'
1050=== added directory 'tests/data/usr/share/icons/hicolor/20x20/apps'
1051=== added file 'tests/data/usr/share/icons/hicolor/20x20/apps/app3.png'
1052=== added directory 'tests/data/usr/share/icons/hicolor/22x22'
1053=== added directory 'tests/data/usr/share/icons/hicolor/22x22/apps'
1054=== added file 'tests/data/usr/share/icons/hicolor/22x22/apps/app1.png'
1055=== added directory 'tests/data/usr/share/icons/hicolor/23x23'
1056=== added directory 'tests/data/usr/share/icons/hicolor/23x23/apps'
1057=== added directory 'tests/data/usr/share/icons/hicolor/24x24'
1058=== added directory 'tests/data/usr/share/icons/hicolor/24x24/apps'
1059=== added file 'tests/data/usr/share/icons/hicolor/24x24/apps/app.xpm'
1060=== added directory 'tests/data/usr/share/icons/hicolor/25x25'
1061=== added directory 'tests/data/usr/share/icons/hicolor/25x25/apps'
1062=== added file 'tests/data/usr/share/icons/hicolor/25x25/apps/app.png'
1063=== added directory 'tests/data/usr/share/icons/hicolor/32x32'
1064=== added directory 'tests/data/usr/share/icons/hicolor/32x32/apps'
1065=== added file 'tests/data/usr/share/icons/hicolor/32x32/apps/app1.png'
1066=== added file 'tests/data/usr/share/icons/hicolor/app4.png'
1067=== added file 'tests/data/usr/share/icons/hicolor/index.theme'
1068--- tests/data/usr/share/icons/hicolor/index.theme 1970-01-01 00:00:00 +0000
1069+++ tests/data/usr/share/icons/hicolor/index.theme 2016-05-03 14:23:08 +0000
1070@@ -0,0 +1,48 @@
1071+[Icon Theme]
1072+Directories=16x16/actions,16x16/apps,16x16/stock,20x20/apps,22x22/apps,23x23/apps,scalable/apps,24x24/apps,25x25/apps,32x32/apps
1073+
1074+[16x16/actions]
1075+Size=16
1076+Context=Actions
1077+Type=Threshold
1078+
1079+[16x16/apps]
1080+Size=16
1081+Context=Applications
1082+Type=Threshold
1083+
1084+[20x20/apps]
1085+Size=junk
1086+Context=Applications
1087+Type=Fixed
1088+
1089+[22x22/apps]
1090+Size=22
1091+Threshold=10
1092+Type=Threshold
1093+Context=Applications
1094+
1095+[23x23/apps]
1096+MaxSize=junk
1097+Context=Applications
1098+Type=scalable
1099+
1100+[scalable/apps]
1101+MaxSize=24
1102+Context=Applications
1103+Type=Scalable
1104+
1105+[24x24/apps]
1106+Size=24
1107+Context=Applications
1108+Type=Threshold
1109+
1110+[25x25/apps]
1111+Size=25
1112+Context=Applications
1113+Type=Fixed
1114+
1115+[32x32/apps]
1116+Size=32
1117+Context=Applications
1118+Type=Fixed
1119
1120=== added directory 'tests/data/usr/share/icons/hicolor/scalable'
1121=== added directory 'tests/data/usr/share/icons/hicolor/scalable/apps'
1122=== added file 'tests/data/usr/share/icons/hicolor/scalable/apps/app.svg'
1123=== added directory 'tests/data/usr/share/pixmaps'
1124=== added file 'tests/data/usr/share/pixmaps/app2.png'

Subscribers

People subscribed via source and target branches