1 #include "util.hpp"
2 #include <fstream>
3 #include <giomm.h>
4 #include <glibmm/miscutils.h>
5 #include <glib/gstdio.h>
6 #include <unistd.h>
7 #ifdef G_OS_WIN32
8 #include <windows.h>
9 #include <iostream>
10 #endif
11 #include <iomanip>
12 #include "nlohmann/json.hpp"
13 #include "alphanum/alphanum.hpp"
14 #include <gdk/gdkkeysyms.h>
15 #include "imp/in_tool_action.hpp"
16 #include "str_util.hpp"
17 
18 namespace horizon {
19 
make_ifstream(const std::string & filename_utf8,std::ios_base::openmode mode)20 std::ifstream make_ifstream(const std::string &filename_utf8, std::ios_base::openmode mode)
21 {
22 #ifdef G_OS_WIN32
23     auto wfilename = reinterpret_cast<wchar_t *>(g_utf8_to_utf16(filename_utf8.c_str(), -1, NULL, NULL, NULL));
24     std::ifstream ifs(wfilename, mode);
25     g_free(wfilename);
26 #else
27     std::ifstream ifs(filename_utf8, mode);
28 #endif
29     return ifs;
30 }
31 
make_ofstream(const std::string & filename_utf8,std::ios_base::openmode mode)32 std::ofstream make_ofstream(const std::string &filename_utf8, std::ios_base::openmode mode)
33 {
34 #ifdef G_OS_WIN32
35     auto wfilename = reinterpret_cast<wchar_t *>(g_utf8_to_utf16(filename_utf8.c_str(), -1, NULL, NULL, NULL));
36     std::ofstream ofs(wfilename, mode);
37     g_free(wfilename);
38 #else
39     std::ofstream ofs(filename_utf8, mode);
40 #endif
41     return ofs;
42 }
43 
save_json_to_file(const std::string & filename,const json & j)44 void save_json_to_file(const std::string &filename, const json &j)
45 {
46     auto ofs = make_ofstream(filename);
47     if (!ofs.is_open()) {
48         throw std::runtime_error("can't save json " + filename);
49         return;
50     }
51     ofs << std::setw(4) << j;
52     ofs.close();
53 }
54 
load_json_from_file(const std::string & filename)55 json load_json_from_file(const std::string &filename)
56 {
57     json j;
58     auto ifs = make_ifstream(filename);
59     if (!ifs.is_open()) {
60         throw std::runtime_error("file " + filename + " not opened");
61     }
62     ifs >> j;
63     ifs.close();
64     return j;
65 }
66 
json_from_resource(const std::string & rsrc)67 json json_from_resource(const std::string &rsrc)
68 {
69     auto json_bytes = Gio::Resource::lookup_data_global(rsrc);
70     gsize size = json_bytes->get_size();
71     return json::parse((const char *)json_bytes->get_data(size));
72 }
73 
74 #ifdef G_OS_WIN32
get_exe_dir()75 std::string get_exe_dir()
76 {
77     TCHAR szPath[MAX_PATH];
78     if (!GetModuleFileName(NULL, szPath, MAX_PATH)) {
79         throw std::runtime_error("can't find executable");
80         return "";
81     }
82     else {
83         return Glib::path_get_dirname(szPath);
84     }
85 }
86 
87 #else
88 #if defined(__linux__)
get_exe_dir()89 std::string get_exe_dir()
90 {
91 
92     char buf[PATH_MAX];
93     ssize_t len;
94     if ((len = readlink("/proc/self/exe", buf, sizeof(buf) - 1)) != -1) {
95         buf[len] = '\0';
96         return Glib::path_get_dirname(buf);
97     }
98     else {
99         throw std::runtime_error("can't find executable");
100         return "";
101     }
102 }
103 #elif defined(__FreeBSD__) || defined(__DragonFly__)
104 #include <sys/sysctl.h>
get_exe_dir()105 std::string get_exe_dir()
106 {
107     char buf[PATH_MAX];
108     size_t len = sizeof(buf);
109     int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
110     int ret;
111     ret = sysctl(mib, 4, buf, &len, NULL, 0);
112     if (ret == 0) {
113         return Glib::path_get_dirname(buf);
114     }
115     else {
116         throw std::runtime_error("can't find executable");
117         return "";
118     }
119 }
120 #elif defined(__APPLE__)
121 #include <mach-o/dyld.h>
get_exe_dir()122 std::string get_exe_dir()
123 {
124     char buf[PATH_MAX];
125     uint32_t size = sizeof(buf);
126     if (_NSGetExecutablePath(buf, &size) == 0) {
127         return Glib::path_get_dirname(buf);
128     }
129     else {
130         throw std::runtime_error("can't find executable");
131         return "";
132     }
133 }
134 #else
135 #error Dont know how to find path to executable on your OS.
136 #endif
137 #endif
138 
139 
allow_set_foreground_window(int pid)140 void allow_set_foreground_window(int pid)
141 {
142 #ifdef G_OS_WIN32
143     AllowSetForegroundWindow(pid);
144 #endif
145 }
146 
endswith(const std::string & haystack,const std::string & needle)147 bool endswith(const std::string &haystack, const std::string &needle)
148 {
149     auto pos = haystack.rfind(needle);
150     if (pos == std::string::npos)
151         return false;
152     else
153         return (haystack.size() - haystack.rfind(needle)) == needle.size();
154 }
155 
strcmp_natural(const std::string & a,const std::string & b)156 int strcmp_natural(const std::string &a, const std::string &b)
157 {
158     return doj::alphanum_comp(a, b);
159 }
160 
strcmp_natural(const char * a,const char * b)161 int strcmp_natural(const char *a, const char *b)
162 {
163     return doj::alphanum_comp(a, b);
164 }
165 
get_config_dir()166 std::string get_config_dir()
167 {
168     return Glib::build_filename(Glib::get_user_config_dir(), "horizon");
169 }
170 
create_config_dir()171 void create_config_dir()
172 {
173     auto config_dir = get_config_dir();
174     if (!Glib::file_test(config_dir, Glib::FILE_TEST_EXISTS))
175         Gio::File::create_for_path(config_dir)->make_directory_with_parents();
176 }
177 
replace_backslash(std::string & path)178 void replace_backslash(std::string &path)
179 {
180 #ifdef G_OS_WIN32
181     std::replace(path.begin(), path.end(), '\\', '/');
182 #endif
183 }
184 
compare_files(const std::string & filename_a,const std::string & filename_b)185 bool compare_files(const std::string &filename_a, const std::string &filename_b)
186 {
187     auto mapped_a = g_mapped_file_new(filename_a.c_str(), false, NULL);
188     if (!mapped_a) {
189         return false;
190     }
191     auto mapped_b = g_mapped_file_new(filename_b.c_str(), false, NULL);
192     if (!mapped_b) {
193         g_mapped_file_unref(mapped_a);
194         return false;
195     }
196     if (g_mapped_file_get_length(mapped_a) != g_mapped_file_get_length(mapped_b)) {
197         g_mapped_file_unref(mapped_a);
198         g_mapped_file_unref(mapped_b);
199         return false;
200     }
201     auto size = g_mapped_file_get_length(mapped_a);
202     auto r = memcmp(g_mapped_file_get_contents(mapped_a), g_mapped_file_get_contents(mapped_b), size);
203     g_mapped_file_unref(mapped_a);
204     g_mapped_file_unref(mapped_b);
205     return r == 0;
206 }
207 
find_files_recursive(const std::string & base_path,std::function<void (const std::string &)> cb,const std::string & path)208 void find_files_recursive(const std::string &base_path, std::function<void(const std::string &)> cb,
209                           const std::string &path)
210 {
211     auto this_path = Glib::build_filename(base_path, path);
212     Glib::Dir dir(this_path);
213     for (const auto &it : dir) {
214         auto itempath = Glib::build_filename(this_path, it);
215         if (Glib::file_test(itempath, Glib::FILE_TEST_IS_DIR)) {
216             find_files_recursive(base_path, cb, Glib::build_filename(path, it));
217         }
218         else if (Glib::file_test(itempath, Glib::FILE_TEST_IS_REGULAR)) {
219             cb(Glib::build_filename(path, it));
220         }
221     }
222 }
223 
color_to_json(const Color & c)224 json color_to_json(const Color &c)
225 {
226     json j;
227     j["r"] = c.r;
228     j["g"] = c.g;
229     j["b"] = c.b;
230     return j;
231 }
232 
233 
color_from_json(const json & j)234 Color color_from_json(const json &j)
235 {
236     Color c;
237     c.r = j.at("r");
238     c.g = j.at("g");
239     c.b = j.at("b");
240     return c;
241 }
242 
colori_to_json(const ColorI & c)243 json colori_to_json(const ColorI &c)
244 {
245     json j;
246     j["r"] = c.r;
247     j["g"] = c.g;
248     j["b"] = c.b;
249     return j;
250 }
251 
252 
colori_from_json(const json & j)253 ColorI colori_from_json(const json &j)
254 {
255     ColorI c;
256     c.r = j.at("r");
257     c.g = j.at("g");
258     c.b = j.at("b");
259     return c;
260 }
261 
262 static std::locale the_locale = std::locale::classic();
263 
setup_locale()264 void setup_locale()
265 {
266     std::locale::global(std::locale::classic());
267     char decimal_sep = '.';
268 #ifdef G_OS_WIN32
269     TCHAR szSep[8];
270     GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, szSep, 8);
271     if (szSep[0] == ',') {
272         decimal_sep = ',';
273     }
274 #else
275     try {
276         // Try setting the "native locale", in order to retain
277         // things like decimal separator.  This might fail in
278         // case the user has no notion of a native locale.
279         auto user_locale = std::locale("");
280         decimal_sep = std::use_facet<std::numpunct<char>>(user_locale).decimal_point();
281     }
282     catch (...) {
283         // just proceed
284     }
285 #endif
286 
287     class comma : public std::numpunct<char> {
288     public:
289         comma(char c) : dp(c)
290         {
291         }
292         char do_decimal_point() const override
293         {
294             return dp;
295         } // comma
296 
297     private:
298         const char dp;
299     };
300 
301     the_locale = std::locale(std::locale::classic(), new comma(decimal_sep));
302 }
303 
get_locale()304 const std::locale &get_locale()
305 {
306     return the_locale;
307 }
308 
format_m_of_n(unsigned int m,unsigned int n)309 std::string format_m_of_n(unsigned int m, unsigned int n)
310 {
311     auto n_str = std::to_string(n);
312     auto digits_max = n_str.size();
313     auto m_str = std::to_string(m);
314     std::string prefix;
315     for (size_t i = 0; i < (digits_max - (int)m_str.size()); i++) {
316         prefix += " ";
317     }
318     return prefix + m_str + "/" + n_str;
319 }
320 
format_digits(unsigned int m,unsigned int digits_max)321 std::string format_digits(unsigned int m, unsigned int digits_max)
322 {
323     auto m_str = std::to_string(m);
324     std::string prefix;
325     if (m_str.size() < digits_max) {
326         for (size_t i = 0; i < (digits_max - (int)m_str.size()); i++) {
327             prefix += " ";
328         }
329     }
330     return prefix + m_str;
331 }
332 
parse_si(const std::string & inps)333 double parse_si(const std::string &inps)
334 {
335     static const auto regex = Glib::Regex::create(
336             "([+-]?)(?:(?:(\\d+)[\\.,]?(\\d*))|(?:[\\.,](\\d+)))(?:[eE]([-+]?)([0-9]+)|\\s*([a-zA-Zµ])|)");
337     Glib::ustring inp(inps);
338     Glib::MatchInfo ma;
339     if (regex->match(inp, ma)) {
340         auto sign = ma.fetch(1);
341         auto intv = ma.fetch(2);
342         auto fracv = ma.fetch(3);
343         auto fracv2 = ma.fetch(4);
344         auto exp_sign = ma.fetch(5);
345         auto exp = ma.fetch(6);
346         auto prefix = ma.fetch(7);
347 
348         double v = 0;
349         if (intv.size()) {
350             v = std::stoi(intv);
351             if (fracv.size()) {
352                 v += std::stoi(fracv) * std::pow(10, (int)fracv.size() * -1);
353             }
354         }
355         else {
356             v = std::stoi(fracv2) * std::pow(10, (int)fracv2.size() * -1);
357         }
358 
359         if (exp.size()) {
360             int iexp = std::stoi(exp);
361             if (exp_sign == "-") {
362                 iexp *= -1;
363             }
364             v *= std::pow(10, iexp);
365         }
366         else if (prefix.size()) {
367             int prefix_exp = 0;
368             if (prefix == "p")
369                 prefix_exp = -12;
370             else if (prefix == "n" || prefix == "N")
371                 prefix_exp = -9;
372             else if (prefix == "u" || prefix == "U" || prefix == "µ")
373                 prefix_exp = -6;
374             else if (prefix == "m")
375                 prefix_exp = -3;
376             else if (prefix == "k" || prefix == "K")
377                 prefix_exp = 3;
378             else if (prefix == "M")
379                 prefix_exp = 6;
380             else if (prefix == "G" || prefix == "g")
381                 prefix_exp = 9;
382             else if (prefix == "T" || prefix == "t")
383                 prefix_exp = 12;
384             v *= std::pow(10, prefix_exp);
385         }
386         if (sign == "-")
387             v *= -1;
388 
389         return v;
390     }
391 
392     return NAN;
393 }
394 
rmdir_recursive(const std::string & dir_name)395 void rmdir_recursive(const std::string &dir_name)
396 {
397     Glib::Dir dir(dir_name);
398     std::list<std::string> entries(dir.begin(), dir.end());
399     for (const auto &it : entries) {
400         auto filename = Glib::build_filename(dir_name, it);
401         if (Glib::file_test(filename, Glib::FILE_TEST_IS_DIR)) {
402             rmdir_recursive(filename);
403         }
404         else {
405             if (g_unlink(filename.c_str()) != 0)
406                 throw std::runtime_error("g_unlink");
407         }
408     }
409     if (g_rmdir(dir_name.c_str()) != 0)
410         throw std::runtime_error("g_rmdir");
411 }
412 
isvar(char c)413 static bool isvar(char c)
414 {
415     return std::isalnum(c) || c == '_' || c == ':';
416 }
417 
interpolate_text(const std::string & str,std::function<std::optional<std::string> (const std::string & s)> interpolator)418 std::string interpolate_text(const std::string &str,
419                              std::function<std::optional<std::string>(const std::string &s)> interpolator)
420 {
421 
422     enum class State { NORMAL, DOLLAR, VAR, VAR_BRACED };
423     State state = State::NORMAL;
424     std::string out;
425     std::string var_current;
426     size_t i = 0;
427     while (true) {
428         const char c = str.c_str()[i++];
429         switch (state) {
430         case State::NORMAL:
431             if (c == '$') {
432                 state = State::DOLLAR;
433             }
434             else if (c) {
435                 out.append(1, c);
436             }
437             break;
438 
439         case State::DOLLAR:
440             if (isvar(c)) {
441                 var_current.clear();
442                 var_current.append(1, c);
443                 state = State::VAR;
444             }
445             else if (c == '{') {
446                 var_current.clear();
447                 state = State::VAR_BRACED;
448             }
449             else {
450                 out.append("$");
451                 if (c)
452                     out.append(1, c);
453                 state = State::NORMAL;
454             }
455             break;
456 
457         case State::VAR:
458             if (isvar(c)) {
459                 var_current.append(1, c);
460             }
461             else {
462                 if (auto subst = interpolator(var_current)) {
463                     out.append(*subst);
464                 }
465                 else {
466                     out.append("$" + var_current);
467                 }
468 
469                 if (c == '$') {
470                     state = State::DOLLAR;
471                 }
472                 else if (c) {
473                     out.append(1, c);
474                     state = State::NORMAL;
475                 }
476             }
477             break;
478 
479         case State::VAR_BRACED:
480             if (c == '}') {
481                 if (auto subst = interpolator(var_current)) {
482                     out.append(*subst);
483                 }
484                 else {
485                     out.append("${" + var_current + "}");
486                 }
487                 state = State::NORMAL;
488             }
489             else {
490                 var_current.append(1, c);
491             }
492             break;
493         }
494 
495         if (!c)
496             break;
497     }
498 
499     return out;
500 }
501 
dir_from_action(InToolActionID a)502 std::pair<Coordi, bool> dir_from_action(InToolActionID a)
503 {
504     using I = InToolActionID;
505     switch (a) {
506     case I::MOVE_UP_FINE:
507         return {{0, 1}, true};
508     case I::MOVE_UP:
509         return {{0, 1}, false};
510 
511     case I::MOVE_DOWN_FINE:
512         return {{0, -1}, true};
513     case I::MOVE_DOWN:
514         return {{0, -1}, false};
515 
516     case I::MOVE_LEFT_FINE:
517         return {{-1, 0}, true};
518     case I::MOVE_LEFT:
519         return {{-1, 0}, false};
520 
521     case I::MOVE_RIGHT_FINE:
522         return {{1, 0}, true};
523     case I::MOVE_RIGHT:
524         return {{1, 0}, false};
525 
526     default:
527         return {{0, 0}, false};
528     }
529 }
530 
check_object_type(const json & j,ObjectType type)531 void check_object_type(const json &j, ObjectType type)
532 {
533     if (j.at("type") != object_type_lut.lookup_reverse(type)) {
534         throw std::runtime_error("wrong object type");
535     }
536 }
537 
ensure_parent_dir(const std::string & path)538 void ensure_parent_dir(const std::string &path)
539 {
540     auto parent = Glib::path_get_dirname(path);
541     if (!Glib::file_test(parent, Glib::FILE_TEST_IS_DIR)) {
542         Gio::File::create_for_path(parent)->make_directory_with_parents();
543     }
544 }
545 
546 
append_dot_json(const std::string & s)547 std::string append_dot_json(const std::string &s)
548 {
549     std::string r = s;
550     trim(r);
551     if (!endswith(r, ".json")) {
552         return r + ".json";
553     }
554     return r;
555 }
556 
557 } // namespace horizon
558