2019-02-15 06:27:17 +08:00

326 lines
7.9 KiB
C++

#include <nana/c++defines.hpp>
#if defined(NANA_POSIX) && defined(NANA_X11)
#include "theme.hpp"
#include <nana/filesystem/filesystem.hpp>
#include <algorithm>
#include <vector>
namespace nana
{
namespace detail
{
static int gschema_override_priority(const std::string& filename)
{
if(filename.size() < 3)
return -1;
auto str = filename.substr(0, 2);
if('0' <= str[0] && str[0] <= '9' && '0' <= str[1] && str[1] <= '9')
return std::stoi(str);
return 0;
}
//Removes the wrap of ' and " character.
std::string remove_decoration(const std::string& primitive_value)
{
auto pos = primitive_value.find_first_of("'\"");
if(pos == primitive_value.npos)
return primitive_value;
auto endpos = primitive_value.find(primitive_value[pos], pos + 1);
if(endpos == primitive_value.npos)
return primitive_value;
return primitive_value.substr(pos + 1, endpos - pos - 1);
}
std::string find_value(std::ifstream& ifs, const std::string& category, const std::string& key)
{
ifs.seekg(0, std::ios::beg);
std::string dec_categ = "[" + category + "]";
bool found_cat = false;
while(ifs.good())
{
std::string text;
std::getline(ifs, text);
if((text.size() > 2) && ('[' == text[0]))
{
if(found_cat)
break;
found_cat = (text == dec_categ);
}
else if(found_cat && (text.find(key + "=") == 0))
{
return remove_decoration(text.substr(key.size() + 1));
}
}
return {};
}
std::vector<std::string> split_value(const std::string& value_string)
{
std::vector<std::string> values;
std::size_t start_pos = 0;
while(start_pos != value_string.npos)
{
auto pos = value_string.find(',', start_pos);
if(value_string.npos == pos)
{
if(start_pos < value_string.size())
values.emplace_back(value_string.substr(start_pos));
break;
}
values.emplace_back(value_string.substr(start_pos, pos - start_pos));
start_pos = value_string.find_first_not_of(',', pos);
}
return values;
}
namespace fs = std::filesystem;
std::string find_gnome_theme_name()
{
try
{
//Searches all the gschema override files
std::vector<std::string> overrides;
for(fs::directory_iterator i{"/usr/share/glib-2.0/schemas"}, end; i != end; ++i)
{
auto filename = i->path().filename().string();
if(filename.size() > 17 && filename.substr(filename.size() - 17) == ".gschema.override")
{
auto priority = gschema_override_priority(filename);
if(priority < 0)
continue;
auto i = std::find_if(overrides.cbegin(), overrides.cend(), [priority](const std::string& ovrd){
return (priority > gschema_override_priority(ovrd));
});
overrides.emplace(i, std::move(filename));
//overrides.insert(i, filename);
}
}
//Searches the org.gnome.desktop.interface in override files.
for(auto & gschema_override : overrides)
{
std::ifstream ifs{"/usr/share/glib-2.0/schemas/" + gschema_override};
auto value = find_value(ifs, "org.gnome.desktop.interface", "icon-theme");
if(!value.empty())
return value;
}
//Return the value from org.gnome.desktop.interface.gschema.xml
fs::path xml = "/usr/share/glib-2.0/schemas/org.gnome.desktop.interface.gschema.xml";
auto bytes = fs::file_size(xml);
if(0 == bytes)
return {};
std::ifstream xml_ifs{"/usr/share/glib-2.0/schemas/org.gnome.desktop.interface.gschema.xml", std::ios::binary};
if(xml_ifs)
{
std::string data;
data.resize(bytes);
xml_ifs.read(&data.front(), bytes);
auto pos = data.find("\"icon-theme\"");
if(pos != data.npos)
{
pos = data.find("<default>", pos + 22);
if(pos != data.npos)
{
pos += 9;
auto endpos = data.find("</default>", pos);
if(endpos != data.npos)
{
return remove_decoration(data.substr(pos, endpos - pos));
}
}
}
}
}
catch(...){}
return {};
}
std::string find_kde_theme_name()
{
auto home = getenv("HOME");
if(home)
{
fs::path kdeglobals{home};
kdeglobals /= ".kde/share/config/kdeglobals";
std::error_code err;
if(fs::exists(kdeglobals, err))
{
std::ifstream ifs{kdeglobals};
return find_value(ifs, "Icons", "Theme");
}
}
return {};
}
std::string find_theme_name()
{
auto name = find_kde_theme_name();
if(name.empty())
return find_gnome_theme_name();
return name;
}
class icon_theme
{
public:
icon_theme(const std::string& name):
theme_name_(name),
ifs_("/usr/share/icons/" + name + "/index.theme")
{
//First of all, read the Inherits and Directories
inherits_ = split_value(find_value(ifs_, "Icon Theme", "Inherits"));
directories_ = split_value(find_value(ifs_, "Icon Theme", "Directories"));
}
std::string find(const std::string& name, std::size_t size_wanted) const
{
namespace fs = std::filesystem;
//candidates
std::vector<std::pair<std::string,int>> first, second, third;
fs::path theme_path = "/usr/share/icons/" + theme_name_;
std::string base_path = "/usr/share/icons/" + theme_name_ + "/";
std::string filename = "/" + name + ".png";
std::error_code err;
for(auto & dir : directories_)
{
if(!fs::exists(theme_path / dir / (name + ".png"), err))
continue;
auto size = find_value(ifs_, dir, "Size");
auto type = find_value(ifs_, dir, "Type");
auto scaled = find_value(ifs_, dir, "Scale");
if(size.empty() || ("Fixed" != type))
continue;
int int_size = std::stoi(size);
if(!scaled.empty())
int_size *= std::stoi(scaled);
auto distance = std::abs(static_cast<int>(size_wanted) - int_size);
if(0 == distance)
{
if(scaled.empty() || scaled == "1")
return base_path + dir + filename;
else
first.emplace_back(dir, 0);
}
else
{
if(scaled.empty() || scaled == "1")
second.emplace_back(dir, distance);
else
third.emplace_back(dir, distance);
}
}
using pair_type = std::pair<std::string,int>;
auto comp = [](const pair_type& a, const pair_type& b){
return a.second < b.second;
};
std::sort(first.begin(), first.end(), comp);
std::sort(second.begin(), second.end(), comp);
std::sort(third.begin(), third.end(), comp);
std::string closer_dir;
if(!first.empty())
closer_dir = first.front().first;
else if(!second.empty())
closer_dir = second.front().first;
else if(!third.empty())
closer_dir = third.front().first;
if(closer_dir.empty())
{
for(auto & inh : inherits_)
{
auto dir = icon_theme{inh}.find(name, size_wanted);
if(!dir.empty())
return dir;
}
//Avoid recursively traverse directory for hicolor if current theme name is hicolor
if("hicolor" == theme_name_)
return {};
return icon_theme{"hicolor"}.find(name, size_wanted);
}
return base_path + closer_dir + filename;
}
private:
const std::string theme_name_;
mutable std::ifstream ifs_;
std::vector<std::string> inherits_;
std::vector<std::string> directories_;
};
theme::theme():
path_("/usr/share/icons/"),
ifs_("/usr/share/icons/default/index.theme")
{
}
std::string theme::cursor(const std::string& name) const
{
auto theme = find_value(ifs_, "Icon Theme", "Inherits");
return path_ + theme + "/cursors/" + name;
}
std::string theme::icon(const std::string& name, std::size_t size_wanted) const
{
//Lookup in cache
auto i = iconcache_.find(name);
if(i != iconcache_.end())
{
for(auto & p : i->second)
{
if(p.first == size_wanted)
return p.second;
}
}
//Cache is missed.
auto file = icon_theme{find_theme_name()}.find(name, size_wanted);
if(!file.empty())
iconcache_[name].emplace_back(size_wanted, file);
return file;
}
}
}
#endif