Add reset button for each preference in gesture config UI
[chromium-blink-merge.git] / base / nix / mime_util_xdg.cc
blob1a41394fc158c1c33970d3bf8ddfd436188e32d8
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/nix/mime_util_xdg.h"
7 #include <cstdlib>
8 #include <list>
9 #include <map>
10 #include <vector>
12 #include "base/environment.h"
13 #include "base/file_util.h"
14 #include "base/lazy_instance.h"
15 #include "base/logging.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/memory/singleton.h"
18 #include "base/nix/xdg_util.h"
19 #include "base/strings/string_split.h"
20 #include "base/strings/string_util.h"
21 #include "base/synchronization/lock.h"
22 #include "base/third_party/xdg_mime/xdgmime.h"
23 #include "base/threading/thread_restrictions.h"
24 #include "base/time/time.h"
26 namespace base {
27 namespace nix {
29 namespace {
31 class IconTheme;
33 // None of the XDG stuff is thread-safe, so serialize all access under
34 // this lock.
35 base::LazyInstance<base::Lock>::Leaky
36 g_mime_util_xdg_lock = LAZY_INSTANCE_INITIALIZER;
38 class MimeUtilConstants {
39 public:
40 typedef std::map<std::string, IconTheme*> IconThemeMap;
41 typedef std::map<FilePath, base::Time> IconDirMtimeMap;
42 typedef std::vector<std::string> IconFormats;
44 // Specified by XDG icon theme specs.
45 static const int kUpdateIntervalInSeconds = 5;
47 static const size_t kDefaultThemeNum = 4;
49 static MimeUtilConstants* GetInstance() {
50 return Singleton<MimeUtilConstants>::get();
53 // Store icon directories and their mtimes.
54 IconDirMtimeMap icon_dirs_;
56 // Store icon formats.
57 IconFormats icon_formats_;
59 // Store loaded icon_theme.
60 IconThemeMap icon_themes_;
62 // The default theme.
63 IconTheme* default_themes_[kDefaultThemeNum];
65 base::TimeTicks last_check_time_;
67 // The current icon theme, usually set through GTK theme integration.
68 std::string icon_theme_name_;
70 private:
71 MimeUtilConstants() {
72 icon_formats_.push_back(".png");
73 icon_formats_.push_back(".svg");
74 icon_formats_.push_back(".xpm");
76 for (size_t i = 0; i < kDefaultThemeNum; ++i)
77 default_themes_[i] = NULL;
79 ~MimeUtilConstants();
81 friend struct DefaultSingletonTraits<MimeUtilConstants>;
83 DISALLOW_COPY_AND_ASSIGN(MimeUtilConstants);
86 // IconTheme represents an icon theme as defined by the xdg icon theme spec.
87 // Example themes on GNOME include 'Human' and 'Mist'.
88 // Example themes on KDE include 'crystalsvg' and 'kdeclassic'.
89 class IconTheme {
90 public:
91 // A theme consists of multiple sub-directories, like '32x32' and 'scalable'.
92 class SubDirInfo {
93 public:
94 // See spec for details.
95 enum Type {
96 Fixed,
97 Scalable,
98 Threshold
100 SubDirInfo()
101 : size(0),
102 type(Threshold),
103 max_size(0),
104 min_size(0),
105 threshold(2) {
107 size_t size; // Nominal size of the icons in this directory.
108 Type type; // Type of the icon size.
109 size_t max_size; // Maximum size that the icons can be scaled to.
110 size_t min_size; // Minimum size that the icons can be scaled to.
111 size_t threshold; // Maximum difference from desired size. 2 by default.
114 explicit IconTheme(const std::string& name);
116 ~IconTheme() {}
118 // Returns the path to an icon with the name |icon_name| and a size of |size|
119 // pixels. If the icon does not exist, but |inherits| is true, then look for
120 // the icon in the parent theme.
121 FilePath GetIconPath(const std::string& icon_name, int size, bool inherits);
123 // Load a theme with the name |theme_name| into memory. Returns null if theme
124 // is invalid.
125 static IconTheme* LoadTheme(const std::string& theme_name);
127 private:
128 // Returns the path to an icon with the name |icon_name| in |subdir|.
129 FilePath GetIconPathUnderSubdir(const std::string& icon_name,
130 const std::string& subdir);
132 // Whether the theme loaded properly.
133 bool IsValid() {
134 return index_theme_loaded_;
137 // Read and parse |file| which is usually named 'index.theme' per theme spec.
138 bool LoadIndexTheme(const FilePath& file);
140 // Checks to see if the icons in |info| matches |size| (in pixels). Returns
141 // 0 if they match, or the size difference in pixels.
142 size_t MatchesSize(SubDirInfo* info, size_t size);
144 // Yet another function to read a line.
145 std::string ReadLine(FILE* fp);
147 // Set directories to search for icons to the comma-separated list |dirs|.
148 bool SetDirectories(const std::string& dirs);
150 bool index_theme_loaded_; // True if an instance is properly loaded.
151 // store the scattered directories of this theme.
152 std::list<FilePath> dirs_;
154 // store the subdirs of this theme and array index of |info_array_|.
155 std::map<std::string, int> subdirs_;
156 scoped_ptr<SubDirInfo[]> info_array_; // List of sub-directories.
157 std::string inherits_; // Name of the theme this one inherits from.
160 IconTheme::IconTheme(const std::string& name)
161 : index_theme_loaded_(false) {
162 base::ThreadRestrictions::AssertIOAllowed();
163 // Iterate on all icon directories to find directories of the specified
164 // theme and load the first encountered index.theme.
165 MimeUtilConstants::IconDirMtimeMap::iterator iter;
166 FilePath theme_path;
167 MimeUtilConstants::IconDirMtimeMap* icon_dirs =
168 &MimeUtilConstants::GetInstance()->icon_dirs_;
169 for (iter = icon_dirs->begin(); iter != icon_dirs->end(); ++iter) {
170 theme_path = iter->first.Append(name);
171 if (!DirectoryExists(theme_path))
172 continue;
173 FilePath theme_index = theme_path.Append("index.theme");
174 if (!index_theme_loaded_ && PathExists(theme_index)) {
175 if (!LoadIndexTheme(theme_index))
176 return;
177 index_theme_loaded_ = true;
179 dirs_.push_back(theme_path);
183 FilePath IconTheme::GetIconPath(const std::string& icon_name, int size,
184 bool inherits) {
185 std::map<std::string, int>::iterator subdir_iter;
186 FilePath icon_path;
188 for (subdir_iter = subdirs_.begin();
189 subdir_iter != subdirs_.end();
190 ++subdir_iter) {
191 SubDirInfo* info = &info_array_[subdir_iter->second];
192 if (MatchesSize(info, size) == 0) {
193 icon_path = GetIconPathUnderSubdir(icon_name, subdir_iter->first);
194 if (!icon_path.empty())
195 return icon_path;
198 // Now looking for the mostly matched.
199 size_t min_delta_seen = 9999;
201 for (subdir_iter = subdirs_.begin();
202 subdir_iter != subdirs_.end();
203 ++subdir_iter) {
204 SubDirInfo* info = &info_array_[subdir_iter->second];
205 size_t delta = MatchesSize(info, size);
206 if (delta < min_delta_seen) {
207 FilePath path = GetIconPathUnderSubdir(icon_name, subdir_iter->first);
208 if (!path.empty()) {
209 min_delta_seen = delta;
210 icon_path = path;
215 if (!icon_path.empty() || !inherits || inherits_ == "")
216 return icon_path;
218 IconTheme* theme = LoadTheme(inherits_);
219 // Inheriting from itself means the theme is buggy but we shouldn't crash.
220 if (theme && theme != this)
221 return theme->GetIconPath(icon_name, size, inherits);
222 else
223 return FilePath();
226 IconTheme* IconTheme::LoadTheme(const std::string& theme_name) {
227 scoped_ptr<IconTheme> theme;
228 MimeUtilConstants::IconThemeMap* icon_themes =
229 &MimeUtilConstants::GetInstance()->icon_themes_;
230 if (icon_themes->find(theme_name) != icon_themes->end()) {
231 theme.reset((*icon_themes)[theme_name]);
232 } else {
233 theme.reset(new IconTheme(theme_name));
234 if (!theme->IsValid())
235 theme.reset();
236 (*icon_themes)[theme_name] = theme.get();
238 return theme.release();
241 FilePath IconTheme::GetIconPathUnderSubdir(const std::string& icon_name,
242 const std::string& subdir) {
243 FilePath icon_path;
244 std::list<FilePath>::iterator dir_iter;
245 MimeUtilConstants::IconFormats* icon_formats =
246 &MimeUtilConstants::GetInstance()->icon_formats_;
247 for (dir_iter = dirs_.begin(); dir_iter != dirs_.end(); ++dir_iter) {
248 for (size_t i = 0; i < icon_formats->size(); ++i) {
249 icon_path = dir_iter->Append(subdir);
250 icon_path = icon_path.Append(icon_name + (*icon_formats)[i]);
251 if (PathExists(icon_path))
252 return icon_path;
255 return FilePath();
258 bool IconTheme::LoadIndexTheme(const FilePath& file) {
259 FILE* fp = file_util::OpenFile(file, "r");
260 SubDirInfo* current_info = NULL;
261 if (!fp)
262 return false;
264 // Read entries.
265 while (!feof(fp) && !ferror(fp)) {
266 std::string buf = ReadLine(fp);
267 if (buf == "")
268 break;
270 std::string entry;
271 TrimWhitespaceASCII(buf, TRIM_ALL, &entry);
272 if (entry.length() == 0 || entry[0] == '#') {
273 // Blank line or Comment.
274 continue;
275 } else if (entry[0] == '[' && info_array_.get()) {
276 current_info = NULL;
277 std::string subdir = entry.substr(1, entry.length() - 2);
278 if (subdirs_.find(subdir) != subdirs_.end())
279 current_info = &info_array_[subdirs_[subdir]];
282 std::string key, value;
283 std::vector<std::string> r;
284 base::SplitStringDontTrim(entry, '=', &r);
285 if (r.size() < 2)
286 continue;
288 TrimWhitespaceASCII(r[0], TRIM_ALL, &key);
289 for (size_t i = 1; i < r.size(); i++)
290 value.append(r[i]);
291 TrimWhitespaceASCII(value, TRIM_ALL, &value);
293 if (current_info) {
294 if (key == "Size") {
295 current_info->size = atoi(value.c_str());
296 } else if (key == "Type") {
297 if (value == "Fixed")
298 current_info->type = SubDirInfo::Fixed;
299 else if (value == "Scalable")
300 current_info->type = SubDirInfo::Scalable;
301 else if (value == "Threshold")
302 current_info->type = SubDirInfo::Threshold;
303 } else if (key == "MaxSize") {
304 current_info->max_size = atoi(value.c_str());
305 } else if (key == "MinSize") {
306 current_info->min_size = atoi(value.c_str());
307 } else if (key == "Threshold") {
308 current_info->threshold = atoi(value.c_str());
310 } else {
311 if (key.compare("Directories") == 0 && !info_array_.get()) {
312 if (!SetDirectories(value)) break;
313 } else if (key.compare("Inherits") == 0) {
314 if (value != "hicolor")
315 inherits_ = value;
320 file_util::CloseFile(fp);
321 return info_array_.get() != NULL;
324 size_t IconTheme::MatchesSize(SubDirInfo* info, size_t size) {
325 if (info->type == SubDirInfo::Fixed) {
326 if (size > info->size)
327 return size - info->size;
328 else
329 return info->size - size;
330 } else if (info->type == SubDirInfo::Scalable) {
331 if (size < info->min_size)
332 return info->min_size - size;
333 if (size > info->max_size)
334 return size - info->max_size;
335 return 0;
336 } else {
337 if (size + info->threshold < info->size)
338 return info->size - size - info->threshold;
339 if (size > info->size + info->threshold)
340 return size - info->size - info->threshold;
341 return 0;
345 std::string IconTheme::ReadLine(FILE* fp) {
346 if (!fp)
347 return std::string();
349 std::string result;
350 const size_t kBufferSize = 100;
351 char buffer[kBufferSize];
352 while ((fgets(buffer, kBufferSize - 1, fp)) != NULL) {
353 result += buffer;
354 size_t len = result.length();
355 if (len == 0)
356 break;
357 char end = result[len - 1];
358 if (end == '\n' || end == '\0')
359 break;
362 return result;
365 bool IconTheme::SetDirectories(const std::string& dirs) {
366 int num = 0;
367 std::string::size_type pos = 0, epos;
368 std::string dir;
369 while ((epos = dirs.find(',', pos)) != std::string::npos) {
370 TrimWhitespaceASCII(dirs.substr(pos, epos - pos), TRIM_ALL, &dir);
371 if (dir.length() == 0) {
372 DLOG(WARNING) << "Invalid index.theme: blank subdir";
373 return false;
375 subdirs_[dir] = num++;
376 pos = epos + 1;
378 TrimWhitespaceASCII(dirs.substr(pos), TRIM_ALL, &dir);
379 if (dir.length() == 0) {
380 DLOG(WARNING) << "Invalid index.theme: blank subdir";
381 return false;
383 subdirs_[dir] = num++;
384 info_array_.reset(new SubDirInfo[num]);
385 return true;
388 bool CheckDirExistsAndGetMtime(const FilePath& dir,
389 base::Time* last_modified) {
390 if (!DirectoryExists(dir))
391 return false;
392 base::PlatformFileInfo file_info;
393 if (!file_util::GetFileInfo(dir, &file_info))
394 return false;
395 *last_modified = file_info.last_modified;
396 return true;
399 // Make sure |dir| exists and add it to the list of icon directories.
400 void TryAddIconDir(const FilePath& dir) {
401 base::Time last_modified;
402 if (!CheckDirExistsAndGetMtime(dir, &last_modified))
403 return;
404 MimeUtilConstants::GetInstance()->icon_dirs_[dir] = last_modified;
407 // For a xdg directory |dir|, add the appropriate icon sub-directories.
408 void AddXDGDataDir(const FilePath& dir) {
409 if (!DirectoryExists(dir))
410 return;
411 TryAddIconDir(dir.Append("icons"));
412 TryAddIconDir(dir.Append("pixmaps"));
415 // Add all the xdg icon directories.
416 void InitIconDir() {
417 FilePath home = file_util::GetHomeDir();
418 if (!home.empty()) {
419 FilePath legacy_data_dir(home);
420 legacy_data_dir = legacy_data_dir.AppendASCII(".icons");
421 if (DirectoryExists(legacy_data_dir))
422 TryAddIconDir(legacy_data_dir);
424 const char* env = getenv("XDG_DATA_HOME");
425 if (env) {
426 AddXDGDataDir(FilePath(env));
427 } else if (!home.empty()) {
428 FilePath local_data_dir(home);
429 local_data_dir = local_data_dir.AppendASCII(".local");
430 local_data_dir = local_data_dir.AppendASCII("share");
431 AddXDGDataDir(local_data_dir);
434 env = getenv("XDG_DATA_DIRS");
435 if (!env) {
436 AddXDGDataDir(FilePath("/usr/local/share"));
437 AddXDGDataDir(FilePath("/usr/share"));
438 } else {
439 std::string xdg_data_dirs = env;
440 std::string::size_type pos = 0, epos;
441 while ((epos = xdg_data_dirs.find(':', pos)) != std::string::npos) {
442 AddXDGDataDir(FilePath(xdg_data_dirs.substr(pos, epos - pos)));
443 pos = epos + 1;
445 AddXDGDataDir(FilePath(xdg_data_dirs.substr(pos)));
449 void EnsureUpdated() {
450 MimeUtilConstants* constants = MimeUtilConstants::GetInstance();
451 if (constants->last_check_time_.is_null()) {
452 constants->last_check_time_ = base::TimeTicks::Now();
453 InitIconDir();
454 return;
457 // Per xdg theme spec, we should check the icon directories every so often
458 // for newly added icons.
459 base::TimeDelta time_since_last_check =
460 base::TimeTicks::Now() - constants->last_check_time_;
461 if (time_since_last_check.InSeconds() > constants->kUpdateIntervalInSeconds) {
462 constants->last_check_time_ += time_since_last_check;
464 bool rescan_icon_dirs = false;
465 MimeUtilConstants::IconDirMtimeMap* icon_dirs = &constants->icon_dirs_;
466 MimeUtilConstants::IconDirMtimeMap::iterator iter;
467 for (iter = icon_dirs->begin(); iter != icon_dirs->end(); ++iter) {
468 base::Time last_modified;
469 if (!CheckDirExistsAndGetMtime(iter->first, &last_modified) ||
470 last_modified != iter->second) {
471 rescan_icon_dirs = true;
472 break;
476 if (rescan_icon_dirs) {
477 constants->icon_dirs_.clear();
478 constants->icon_themes_.clear();
479 InitIconDir();
484 // Find a fallback icon if we cannot find it in the default theme.
485 FilePath LookupFallbackIcon(const std::string& icon_name) {
486 MimeUtilConstants* constants = MimeUtilConstants::GetInstance();
487 MimeUtilConstants::IconDirMtimeMap::iterator iter;
488 MimeUtilConstants::IconDirMtimeMap* icon_dirs = &constants->icon_dirs_;
489 MimeUtilConstants::IconFormats* icon_formats = &constants->icon_formats_;
490 for (iter = icon_dirs->begin(); iter != icon_dirs->end(); ++iter) {
491 for (size_t i = 0; i < icon_formats->size(); ++i) {
492 FilePath icon = iter->first.Append(icon_name + (*icon_formats)[i]);
493 if (PathExists(icon))
494 return icon;
497 return FilePath();
500 // Initialize the list of default themes.
501 void InitDefaultThemes() {
502 IconTheme** default_themes =
503 MimeUtilConstants::GetInstance()->default_themes_;
505 scoped_ptr<base::Environment> env(base::Environment::Create());
506 base::nix::DesktopEnvironment desktop_env =
507 base::nix::GetDesktopEnvironment(env.get());
508 if (desktop_env == base::nix::DESKTOP_ENVIRONMENT_KDE3 ||
509 desktop_env == base::nix::DESKTOP_ENVIRONMENT_KDE4) {
510 // KDE
511 std::string kde_default_theme;
512 std::string kde_fallback_theme;
514 // TODO(thestig): Figure out how to get the current icon theme on KDE.
515 // Setting stored in ~/.kde/share/config/kdeglobals under Icons -> Theme.
516 default_themes[0] = NULL;
518 // Try some reasonable defaults for KDE.
519 if (desktop_env == base::nix::DESKTOP_ENVIRONMENT_KDE3) {
520 // KDE 3
521 kde_default_theme = "default.kde";
522 kde_fallback_theme = "crystalsvg";
523 } else {
524 // KDE 4
525 kde_default_theme = "default.kde4";
526 kde_fallback_theme = "oxygen";
528 default_themes[1] = IconTheme::LoadTheme(kde_default_theme);
529 default_themes[2] = IconTheme::LoadTheme(kde_fallback_theme);
530 } else {
531 // Assume it's Gnome and use GTK to figure out the theme.
532 default_themes[1] = IconTheme::LoadTheme(
533 MimeUtilConstants::GetInstance()->icon_theme_name_);
534 default_themes[2] = IconTheme::LoadTheme("gnome");
536 // hicolor needs to be last per icon theme spec.
537 default_themes[3] = IconTheme::LoadTheme("hicolor");
539 for (size_t i = 0; i < MimeUtilConstants::kDefaultThemeNum; i++) {
540 if (default_themes[i] == NULL)
541 continue;
542 // NULL out duplicate pointers.
543 for (size_t j = i + 1; j < MimeUtilConstants::kDefaultThemeNum; j++) {
544 if (default_themes[j] == default_themes[i])
545 default_themes[j] = NULL;
550 // Try to find an icon with the name |icon_name| that's |size| pixels.
551 FilePath LookupIconInDefaultTheme(const std::string& icon_name, int size) {
552 EnsureUpdated();
553 MimeUtilConstants* constants = MimeUtilConstants::GetInstance();
554 MimeUtilConstants::IconThemeMap* icon_themes = &constants->icon_themes_;
555 if (icon_themes->empty())
556 InitDefaultThemes();
558 FilePath icon_path;
559 IconTheme** default_themes = constants->default_themes_;
560 for (size_t i = 0; i < MimeUtilConstants::kDefaultThemeNum; i++) {
561 if (default_themes[i]) {
562 icon_path = default_themes[i]->GetIconPath(icon_name, size, true);
563 if (!icon_path.empty())
564 return icon_path;
567 return LookupFallbackIcon(icon_name);
570 MimeUtilConstants::~MimeUtilConstants() {
571 for (size_t i = 0; i < kDefaultThemeNum; i++)
572 delete default_themes_[i];
575 } // namespace
577 std::string GetFileMimeType(const FilePath& filepath) {
578 if (filepath.empty())
579 return std::string();
580 base::ThreadRestrictions::AssertIOAllowed();
581 base::AutoLock scoped_lock(g_mime_util_xdg_lock.Get());
582 return xdg_mime_get_mime_type_from_file_name(filepath.value().c_str());
585 std::string GetDataMimeType(const std::string& data) {
586 base::ThreadRestrictions::AssertIOAllowed();
587 base::AutoLock scoped_lock(g_mime_util_xdg_lock.Get());
588 return xdg_mime_get_mime_type_for_data(data.data(), data.length(), NULL);
591 void SetIconThemeName(const std::string& name) {
592 // If the theme name is already loaded, do nothing. Chrome doesn't respond
593 // to changes in the system theme, so we never need to set this more than
594 // once.
595 if (!MimeUtilConstants::GetInstance()->icon_theme_name_.empty())
596 return;
598 MimeUtilConstants::GetInstance()->icon_theme_name_ = name;
601 FilePath GetMimeIcon(const std::string& mime_type, size_t size) {
602 base::ThreadRestrictions::AssertIOAllowed();
603 std::vector<std::string> icon_names;
604 std::string icon_name;
605 FilePath icon_file;
607 if (!mime_type.empty()) {
608 base::AutoLock scoped_lock(g_mime_util_xdg_lock.Get());
609 const char *icon = xdg_mime_get_icon(mime_type.c_str());
610 icon_name = std::string(icon ? icon : "");
613 if (icon_name.length())
614 icon_names.push_back(icon_name);
616 // For text/plain, try text-plain.
617 icon_name = mime_type;
618 for (size_t i = icon_name.find('/', 0); i != std::string::npos;
619 i = icon_name.find('/', i + 1)) {
620 icon_name[i] = '-';
622 icon_names.push_back(icon_name);
623 // Also try gnome-mime-text-plain.
624 icon_names.push_back("gnome-mime-" + icon_name);
626 // Try "deb" for "application/x-deb" in KDE 3.
627 size_t x_substr_pos = mime_type.find("/x-");
628 if (x_substr_pos != std::string::npos) {
629 icon_name = mime_type.substr(x_substr_pos + 3);
630 icon_names.push_back(icon_name);
633 // Try generic name like text-x-generic.
634 icon_name = mime_type.substr(0, mime_type.find('/')) + "-x-generic";
635 icon_names.push_back(icon_name);
637 // Last resort
638 icon_names.push_back("unknown");
640 for (size_t i = 0; i < icon_names.size(); i++) {
641 if (icon_names[i][0] == '/') {
642 icon_file = FilePath(icon_names[i]);
643 if (PathExists(icon_file))
644 return icon_file;
645 } else {
646 icon_file = LookupIconInDefaultTheme(icon_names[i], size);
647 if (!icon_file.empty())
648 return icon_file;
651 return FilePath();
654 } // namespace nix
655 } // namespace base