add: base project
This commit is contained in:
commit
f0cb465c85
14 changed files with 1571 additions and 0 deletions
18
src/config.h
Normal file
18
src/config.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
#include "ini.h"
|
||||
#include "xdg.h"
|
||||
|
||||
class Config {
|
||||
private:
|
||||
struct config {
|
||||
std::optional<std::filesystem::path> configDir;
|
||||
|
||||
config() {}
|
||||
~config() {}
|
||||
};
|
||||
public:
|
||||
Config();
|
||||
~Config();
|
||||
};
|
47
src/dock.cpp
Normal file
47
src/dock.cpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
#include "dock.h"
|
||||
|
||||
/// @brief Create dock windows on all screens
|
||||
/// @param app Qt Application callback
|
||||
void Dock::createDockWindows(QApplication* app) {
|
||||
for (int i = 0; i < this->m_screens.size(); ++i) {
|
||||
QScreen* screen = this->m_screens[i];
|
||||
|
||||
qDebug() << "Creating dock window for screen:" << screen->name();
|
||||
|
||||
QWidget* container = new QWidget(nullptr, Qt::FramelessWindowHint | Qt::Tool);
|
||||
container->setAttribute(Qt::WA_TranslucentBackground);
|
||||
container->setAttribute(Qt::WA_NoSystemBackground);
|
||||
container->setWindowFlag(Qt::WindowStaysOnTopHint);
|
||||
container->resize(screen->geometry().size().width() / 2, 60);
|
||||
|
||||
QLabel* label = new QLabel(QString("✨ Dock %1 ✨").arg(i));
|
||||
label->setAlignment(Qt::AlignCenter);
|
||||
label->setStyleSheet(R"(
|
||||
QLabel {
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
background-color: rgba(0, 0, 0, 180);
|
||||
border-radius: 10px;
|
||||
}
|
||||
)");
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout(container);
|
||||
layout->addWidget(label);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
QRect screenRect = screen->geometry();
|
||||
int x = screenRect.center().x() - container->width() / 2;
|
||||
int y = screenRect.bottom() - container->height();
|
||||
container->move(x, y);
|
||||
|
||||
container->show();
|
||||
}
|
||||
|
||||
app->exec();
|
||||
}
|
||||
|
||||
Dock::Dock(QApplication* app) {
|
||||
this->createDockWindows(app);
|
||||
}
|
||||
|
||||
Dock::~Dock() {};
|
125
src/dock.h
Normal file
125
src/dock.h
Normal file
|
@ -0,0 +1,125 @@
|
|||
#include <QApplication>
|
||||
#include <QScreen>
|
||||
#include <QRect>
|
||||
#include <QList>
|
||||
#include <QWindow>
|
||||
#include <QWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QTimer>
|
||||
#include <QLabel>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <format>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
class Dock {
|
||||
private:
|
||||
/// @brief Stores all dock windows
|
||||
QList<QWindow*> m_dockWindows;
|
||||
|
||||
/// @brief QWidget container for dock windows
|
||||
QList<QWidget*> m_dockWindowContainters;
|
||||
|
||||
/// @brief Stores all configuration windows
|
||||
QList<QWindow*> m_configWindows;
|
||||
|
||||
/// @brief Stores all available screens
|
||||
QList<QScreen*> m_screens = QGuiApplication::screens();
|
||||
|
||||
/// @brief An application in the dock
|
||||
struct dock_application {
|
||||
/// @brief Whether the application is pinned to the dock
|
||||
bool pinned;
|
||||
|
||||
/// @brief Index of where the application is pinned (0 when disabled)
|
||||
int pinIndex;
|
||||
|
||||
/// @brief Whether the application is running
|
||||
bool running;
|
||||
|
||||
/// @brief Path to the application's desktop file entry
|
||||
std::filesystem::path desktopFilePath;
|
||||
|
||||
/// @brief Reference the to the icon file of the application
|
||||
std::ifstream desktopIcon;
|
||||
|
||||
/// @brief Name of the application as specified by the desktop file
|
||||
std::string desktopName;
|
||||
|
||||
/// @brief Name of the running process, used if an application desktop file does not exist
|
||||
std::string processName;
|
||||
|
||||
/// @brief Create a new `dock_application` with an optional desktop file
|
||||
/// @param filename Optional desktop file for metadata
|
||||
dock_application() {
|
||||
getDesktopName();
|
||||
getDesktopIcon();
|
||||
}
|
||||
|
||||
/// @brief Clean up `dock_application` resources
|
||||
~dock_application() {
|
||||
if (desktopIcon.is_open()) {
|
||||
desktopIcon.close();
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Get application desktop name from desktop file
|
||||
/// @return UTF-16 desktop name
|
||||
std::optional<std::string> getDesktopName() {
|
||||
inih::INIReader r;
|
||||
std::string name;
|
||||
|
||||
try {
|
||||
r = std::move(inih::INIReader(desktopFilePath.string()));
|
||||
} catch (std::runtime_error&) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
try {
|
||||
name = r.Get<std::string>("Desktop Entry", "name");
|
||||
} catch (std::runtime_error&) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/// @brief Get application desktop icon from desktop file
|
||||
/// @return Desktop icon file stream
|
||||
std::optional<std::ifstream> getDesktopIcon() {
|
||||
inih::INIReader r;
|
||||
std::filesystem::path icon_path;
|
||||
std::ifstream icon{icon_path};
|
||||
|
||||
try {
|
||||
r = std::move(inih::INIReader(desktopFilePath.string()));
|
||||
} catch (std::runtime_error&) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
try {
|
||||
icon_path = r.Get<std::string>("Desktop Entry", "icon");
|
||||
} catch (std::runtime_error&) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief List of all applications in the dock
|
||||
std::vector<dock_application> m_dockApplications;
|
||||
|
||||
/// @brief Create dock windows on all screens
|
||||
/// @param app Qt Application callback
|
||||
void createDockWindows(QApplication* app);
|
||||
public:
|
||||
Dock(QApplication* app);
|
||||
~Dock();
|
||||
};
|
624
src/ini.h
Normal file
624
src/ini.h
Normal file
|
@ -0,0 +1,624 @@
|
|||
/**
|
||||
* Yet another .ini parser for modern c++ (made for cpp17), inspired and extend
|
||||
* from @benhoyt's inih. See project page: https://github.com/SSARCandy/ini-cpp
|
||||
*/
|
||||
|
||||
#ifndef __INI_H__
|
||||
#define __INI_H__
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace inih {
|
||||
|
||||
/* Typedef for prototype of handler function. */
|
||||
typedef int (*ini_handler)(void* user, const char* section, const char* name,
|
||||
const char* value);
|
||||
|
||||
/* Typedef for prototype of fgets-style reader function. */
|
||||
typedef char* (*ini_reader)(char* str, int num, void* stream);
|
||||
|
||||
#define INI_STOP_ON_FIRST_ERROR 1
|
||||
#define INI_MAX_LINE 2000
|
||||
#define INI_INITIAL_ALLOC 200
|
||||
#define MAX_SECTION 50
|
||||
#define MAX_NAME 50
|
||||
#define INI_START_COMMENT_PREFIXES ";#"
|
||||
#define INI_INLINE_COMMENT_PREFIXES ";"
|
||||
|
||||
/* Strip whitespace chars off end of given string, in place. Return s. */
|
||||
inline static char* rstrip(char* s) {
|
||||
char* p = s + strlen(s);
|
||||
while (p > s && isspace((unsigned char)(*--p))) *p = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Return pointer to first non-whitespace char in given string. */
|
||||
inline static char* lskip(const char* s) {
|
||||
while (*s && isspace((unsigned char)(*s))) s++;
|
||||
return (char*)s;
|
||||
}
|
||||
|
||||
/* Return pointer to first char (of chars) or inline comment in given string,
|
||||
or pointer to null at end of string if neither found. Inline comment must
|
||||
be prefixed by a whitespace character to register as a comment. */
|
||||
inline static char* find_chars_or_comment(const char* s, const char* chars) {
|
||||
int was_space = 0;
|
||||
while (*s && (!chars || !strchr(chars, *s)) &&
|
||||
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
|
||||
was_space = isspace((unsigned char)(*s));
|
||||
s++;
|
||||
}
|
||||
return (char*)s;
|
||||
}
|
||||
|
||||
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
|
||||
inline static char* strncpy0(char* dest, const char* src, size_t size) {
|
||||
strncpy(dest, src, size - 1);
|
||||
dest[size - 1] = '\0';
|
||||
return dest;
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
inline int ini_parse_stream(ini_reader reader, void* stream,
|
||||
ini_handler handler, void* user) {
|
||||
/* Uses a fair bit of stack (use heap instead if you need to) */
|
||||
char* line;
|
||||
size_t max_line = INI_INITIAL_ALLOC;
|
||||
char* new_line;
|
||||
size_t offset;
|
||||
char section[MAX_SECTION] = "";
|
||||
char prev_name[MAX_NAME] = "";
|
||||
|
||||
char* start;
|
||||
char* end;
|
||||
char* name;
|
||||
char* value;
|
||||
int lineno = 0;
|
||||
int error = 0;
|
||||
|
||||
line = (char*)malloc(INI_INITIAL_ALLOC);
|
||||
if (!line) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
#if INI_HANDLER_LINENO
|
||||
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
|
||||
#else
|
||||
#define HANDLER(u, s, n, v) handler(u, s, n, v)
|
||||
#endif
|
||||
|
||||
/* Scan through stream line by line */
|
||||
while (reader(line, (int)max_line, stream) != NULL) {
|
||||
offset = strlen(line);
|
||||
while (offset == max_line - 1 && line[offset - 1] != '\n') {
|
||||
max_line *= 2;
|
||||
if (max_line > INI_MAX_LINE) max_line = INI_MAX_LINE;
|
||||
new_line = (char*)realloc(line, max_line);
|
||||
if (!new_line) {
|
||||
free(line);
|
||||
return -2;
|
||||
}
|
||||
line = new_line;
|
||||
if (reader(line + offset, (int)(max_line - offset), stream) == NULL)
|
||||
break;
|
||||
if (max_line >= INI_MAX_LINE) break;
|
||||
offset += strlen(line + offset);
|
||||
}
|
||||
|
||||
lineno++;
|
||||
|
||||
start = line;
|
||||
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
|
||||
(unsigned char)start[1] == 0xBB &&
|
||||
(unsigned char)start[2] == 0xBF) {
|
||||
start += 3;
|
||||
}
|
||||
start = lskip(rstrip(start));
|
||||
|
||||
if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
|
||||
/* Start-of-line comment */
|
||||
} else if (*start == '[') {
|
||||
/* A "[section]" line */
|
||||
end = find_chars_or_comment(start + 1, "]");
|
||||
if (*end == ']') {
|
||||
*end = '\0';
|
||||
strncpy0(section, start + 1, sizeof(section));
|
||||
*prev_name = '\0';
|
||||
} else if (!error) {
|
||||
/* No ']' found on section line */
|
||||
error = lineno;
|
||||
}
|
||||
} else if (*start) {
|
||||
/* Not a comment, must be a name[=:]value pair */
|
||||
end = find_chars_or_comment(start, "=:");
|
||||
if (*end == '=' || *end == ':') {
|
||||
*end = '\0';
|
||||
name = rstrip(start);
|
||||
value = end + 1;
|
||||
end = find_chars_or_comment(value, NULL);
|
||||
if (*end) *end = '\0';
|
||||
value = lskip(value);
|
||||
rstrip(value);
|
||||
|
||||
/* Valid name[=:]value pair found, call handler */
|
||||
strncpy0(prev_name, name, sizeof(prev_name));
|
||||
if (!HANDLER(user, section, name, value) && !error)
|
||||
error = lineno;
|
||||
} else if (!error) {
|
||||
/* No '=' or ':' found on name[=:]value line */
|
||||
error = lineno;
|
||||
}
|
||||
}
|
||||
|
||||
if (error) break;
|
||||
}
|
||||
|
||||
free(line);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
inline int ini_parse_file(FILE* file, ini_handler handler, void* user) {
|
||||
return ini_parse_stream((ini_reader)fgets, file, handler, user);
|
||||
}
|
||||
|
||||
inline int ini_parse(const char* filename, ini_handler handler, void* user) {
|
||||
FILE* file;
|
||||
int error;
|
||||
|
||||
file = fopen(filename, "r");
|
||||
if (!file) return -1;
|
||||
error = ini_parse_file(file, handler, user);
|
||||
fclose(file);
|
||||
return error;
|
||||
}
|
||||
|
||||
#endif /* __INI_H__ */
|
||||
|
||||
#ifndef __INIREADER_H__
|
||||
#define __INIREADER_H__
|
||||
|
||||
// Read an INI file into easy-to-access name/value pairs. (Note that I've gone
|
||||
// for simplicity here rather than speed, but it should be pretty decent.)
|
||||
class INIReader {
|
||||
public:
|
||||
// Empty Constructor
|
||||
INIReader(){};
|
||||
|
||||
// Construct INIReader and parse given filename. See ini.h for more info
|
||||
// about the parsing.
|
||||
INIReader(std::string filename);
|
||||
|
||||
// Construct INIReader and parse given file. See ini.h for more info
|
||||
// about the parsing.
|
||||
INIReader(FILE* file);
|
||||
|
||||
// Return the result of ini_parse(), i.e., 0 on success, line number of
|
||||
// first error on parse error, or -1 on file open error.
|
||||
int ParseError() const;
|
||||
|
||||
// Return the list of sections found in ini file
|
||||
const std::set<std::string> Sections() const;
|
||||
|
||||
// Return the list of keys in the given section
|
||||
const std::set<std::string> Keys(std::string section) const;
|
||||
|
||||
const std::unordered_map<std::string, std::string> Get(
|
||||
std::string section) const;
|
||||
|
||||
template <typename T = std::string>
|
||||
T Get(const std::string& section, const std::string& name) const;
|
||||
|
||||
template <typename T>
|
||||
T Get(const std::string& section, const std::string& name,
|
||||
T&& default_v) const;
|
||||
|
||||
template <typename T = std::string>
|
||||
std::vector<T> GetVector(const std::string& section,
|
||||
const std::string& name) const;
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> GetVector(const std::string& section,
|
||||
const std::string& name,
|
||||
const std::vector<T>& default_v) const;
|
||||
|
||||
template <typename T = std::string>
|
||||
void InsertEntry(const std::string& section, const std::string& name,
|
||||
const T& v);
|
||||
|
||||
template <typename T = std::string>
|
||||
void InsertEntry(const std::string& section, const std::string& name,
|
||||
const std::vector<T>& vs);
|
||||
|
||||
template <typename T = std::string>
|
||||
void UpdateEntry(const std::string& section, const std::string& name,
|
||||
const T& v);
|
||||
|
||||
template <typename T = std::string>
|
||||
void UpdateEntry(const std::string& section, const std::string& name,
|
||||
const std::vector<T>& vs);
|
||||
|
||||
protected:
|
||||
int _error;
|
||||
std::unordered_map<std::string,
|
||||
std::unordered_map<std::string, std::string>>
|
||||
_values;
|
||||
static int ValueHandler(void* user, const char* section, const char* name,
|
||||
const char* value);
|
||||
|
||||
template <typename T>
|
||||
T Converter(const std::string& s) const;
|
||||
|
||||
const bool BoolConverter(std::string s) const;
|
||||
|
||||
template <typename T>
|
||||
std::string V2String(const T& v) const;
|
||||
|
||||
template <typename T>
|
||||
std::string Vec2String(const std::vector<T>& v) const;
|
||||
};
|
||||
|
||||
#endif // __INIREADER_H__
|
||||
|
||||
#ifndef __INIREADER__
|
||||
#define __INIREADER__
|
||||
|
||||
/**
|
||||
* @brief Construct an INIReader object from a file name
|
||||
* @param filename The name of the INI file to parse
|
||||
* @throws std::runtime_error if there is an error parsing the INI file
|
||||
*/
|
||||
inline INIReader::INIReader(std::string filename) {
|
||||
_error = ini_parse(filename.c_str(), ValueHandler, this);
|
||||
ParseError();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct an INIReader object from a file pointer
|
||||
* @param file A pointer to the INI file to parse
|
||||
* @throws std::runtime_error if there is an error parsing the INI file
|
||||
*/
|
||||
inline INIReader::INIReader(FILE* file) {
|
||||
_error = ini_parse_file(file, ValueHandler, this);
|
||||
ParseError();
|
||||
}
|
||||
|
||||
inline int INIReader::ParseError() const {
|
||||
switch (_error) {
|
||||
case 0:
|
||||
break;
|
||||
case -1:
|
||||
throw std::runtime_error("ini file not found.");
|
||||
case -2:
|
||||
throw std::runtime_error("memory alloc error");
|
||||
default:
|
||||
throw std::runtime_error("parse error on line no: " +
|
||||
std::to_string(_error));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the list of sections found in ini file
|
||||
* @return The list of sections found in ini file
|
||||
*/
|
||||
inline const std::set<std::string> INIReader::Sections() const {
|
||||
std::set<std::string> retval;
|
||||
for (auto const& element : _values) {
|
||||
retval.insert(element.first);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the list of keys in the given section
|
||||
* @param section The section name
|
||||
* @return The list of keys in the given section
|
||||
*/
|
||||
inline const std::set<std::string> INIReader::Keys(std::string section) const {
|
||||
auto const _section = Get(section);
|
||||
std::set<std::string> retval;
|
||||
for (auto const& element : _section) {
|
||||
retval.insert(element.first);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the map representing the values in a section of the INI file
|
||||
* @param section The name of the section to retrieve
|
||||
* @return The map representing the values in the given section
|
||||
* @throws std::runtime_error if the section is not found
|
||||
*/
|
||||
inline const std::unordered_map<std::string, std::string> INIReader::Get(
|
||||
std::string section) const {
|
||||
auto const _section = _values.find(section);
|
||||
if (_section == _values.end()) {
|
||||
throw std::runtime_error("section '" + section + "' not found.");
|
||||
}
|
||||
return _section->second;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the value of the given key in the given section
|
||||
* @param section The section name
|
||||
* @param name The key name
|
||||
* @return The value of the given key in the given section
|
||||
*/
|
||||
template <typename T>
|
||||
inline T INIReader::Get(const std::string& section,
|
||||
const std::string& name) const {
|
||||
auto const _section = Get(section);
|
||||
auto const _value = _section.find(name);
|
||||
|
||||
if (_value == _section.end()) {
|
||||
throw std::runtime_error("key '" + name + "' not found in section '" +
|
||||
section + "'.");
|
||||
}
|
||||
|
||||
std::string value = _value->second;
|
||||
|
||||
if constexpr (std::is_same<T, std::string>()) {
|
||||
return value;
|
||||
} else if constexpr (std::is_same<T, bool>()) {
|
||||
return BoolConverter(value);
|
||||
} else {
|
||||
return Converter<T>(value);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the value of the given key in the given section, return default
|
||||
* if not found
|
||||
* @param section The section name
|
||||
* @param name The key name
|
||||
* @param default_v The default value
|
||||
* @return The value of the given key in the given section, return default if
|
||||
* not found
|
||||
*/
|
||||
template <typename T>
|
||||
inline T INIReader::Get(const std::string& section, const std::string& name,
|
||||
T&& default_v) const {
|
||||
try {
|
||||
return Get<T>(section, name);
|
||||
} catch (std::runtime_error&) {
|
||||
return default_v;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the value array of the given key in the given section.
|
||||
* @param section The section name
|
||||
* @param name The key name
|
||||
* @return The value array of the given key in the given section.
|
||||
*
|
||||
* For example:
|
||||
* ```ini
|
||||
* [section]
|
||||
* key = 1 2 3 4
|
||||
* ```
|
||||
* ```cpp
|
||||
* const auto vs = ini.GetVector<std::vector<int>>("section", "key");
|
||||
* // vs = {1, 2, 3, 4}
|
||||
* ```
|
||||
*/
|
||||
template <typename T>
|
||||
inline std::vector<T> INIReader::GetVector(const std::string& section,
|
||||
const std::string& name) const {
|
||||
std::string value = Get(section, name);
|
||||
|
||||
std::istringstream out{value};
|
||||
const std::vector<std::string> strs{std::istream_iterator<std::string>{out},
|
||||
std::istream_iterator<std::string>()};
|
||||
try {
|
||||
std::vector<T> vs{};
|
||||
for (const std::string& s : strs) {
|
||||
vs.emplace_back(Converter<T>(s));
|
||||
}
|
||||
return vs;
|
||||
} catch (std::exception&) {
|
||||
throw std::runtime_error("cannot parse value " + value +
|
||||
" to vector<T>.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the value array of the given key in the given section, return
|
||||
* default if not found
|
||||
* @param section The section name
|
||||
* @param name The key name
|
||||
* @param default_v The default value
|
||||
* @return The value array of the given key in the given section, return default
|
||||
* if not found
|
||||
*
|
||||
* @see INIReader::GetVector
|
||||
*/
|
||||
template <typename T>
|
||||
inline std::vector<T> INIReader::GetVector(
|
||||
const std::string& section, const std::string& name,
|
||||
const std::vector<T>& default_v) const {
|
||||
try {
|
||||
return GetVector<T>(section, name);
|
||||
} catch (std::runtime_error&) {
|
||||
return default_v;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Insert a key-value pair into the INI file
|
||||
* @param section The section name
|
||||
* @param name The key name
|
||||
* @param v The value to insert
|
||||
* @throws std::runtime_error if the key already exists in the section
|
||||
*/
|
||||
template <typename T>
|
||||
inline void INIReader::InsertEntry(const std::string& section,
|
||||
const std::string& name, const T& v) {
|
||||
if (_values[section][name].size() > 0) {
|
||||
throw std::runtime_error("duplicate key '" + std::string(name) +
|
||||
"' in section '" + section + "'.");
|
||||
}
|
||||
_values[section][name] = V2String(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Insert a vector of values into the INI file
|
||||
* @param section The section name
|
||||
* @param name The key name
|
||||
* @param vs The vector of values to insert
|
||||
* @throws std::runtime_error if the key already exists in the section
|
||||
*/
|
||||
template <typename T>
|
||||
inline void INIReader::InsertEntry(const std::string& section,
|
||||
const std::string& name,
|
||||
const std::vector<T>& vs) {
|
||||
if (_values[section][name].size() > 0) {
|
||||
throw std::runtime_error("duplicate key '" + std::string(name) +
|
||||
"' in section '" + section + "'.");
|
||||
}
|
||||
_values[section][name] = Vec2String(vs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update a key-value pair in the INI file
|
||||
* @param section The section name
|
||||
* @param name The key name
|
||||
* @param v The new value to set
|
||||
* @throws std::runtime_error if the key does not exist in the section
|
||||
*/
|
||||
template <typename T>
|
||||
inline void INIReader::UpdateEntry(const std::string& section,
|
||||
const std::string& name, const T& v) {
|
||||
if (!_values[section][name].size()) {
|
||||
throw std::runtime_error("key '" + std::string(name) +
|
||||
"' not exist in section '" + section + "'.");
|
||||
}
|
||||
_values[section][name] = V2String(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update a vector of values in the INI file
|
||||
* @param section The section name
|
||||
* @param name The key name
|
||||
* @param vs The new vector of values to set
|
||||
* @throws std::runtime_error if the key does not exist in the section
|
||||
*/
|
||||
template <typename T>
|
||||
inline void INIReader::UpdateEntry(const std::string& section,
|
||||
const std::string& name,
|
||||
const std::vector<T>& vs) {
|
||||
if (!_values[section][name].size()) {
|
||||
throw std::runtime_error("key '" + std::string(name) +
|
||||
"' not exist in section '" + section + "'.");
|
||||
}
|
||||
_values[section][name] = Vec2String(vs);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline std::string INIReader::V2String(const T& v) const {
|
||||
std::stringstream ss;
|
||||
ss << v;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline std::string INIReader::Vec2String(const std::vector<T>& v) const {
|
||||
if (v.empty()) {
|
||||
return "";
|
||||
}
|
||||
std::ostringstream oss;
|
||||
std::copy(v.begin(), v.end() - 1, std::ostream_iterator<T>(oss, " "));
|
||||
oss << v.back();
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T INIReader::Converter(const std::string& s) const {
|
||||
try {
|
||||
T v{};
|
||||
std::istringstream _{s};
|
||||
_.exceptions(std::ios::failbit);
|
||||
_ >> v;
|
||||
return v;
|
||||
} catch (std::exception&) {
|
||||
throw std::runtime_error("cannot parse value '" + s + "' to type<T>.");
|
||||
};
|
||||
}
|
||||
|
||||
inline const bool INIReader::BoolConverter(std::string s) const {
|
||||
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
|
||||
static const std::unordered_map<std::string, bool> s2b{
|
||||
{"1", true}, {"true", true}, {"yes", true}, {"on", true},
|
||||
{"0", false}, {"false", false}, {"no", false}, {"off", false},
|
||||
};
|
||||
auto const value = s2b.find(s);
|
||||
if (value == s2b.end()) {
|
||||
throw std::runtime_error("'" + s + "' is not a valid boolean value.");
|
||||
}
|
||||
return value->second;
|
||||
}
|
||||
|
||||
inline int INIReader::ValueHandler(void* user, const char* section,
|
||||
const char* name, const char* value) {
|
||||
INIReader* reader = (INIReader*)user;
|
||||
if (reader->_values[section][name].size() > 0) {
|
||||
throw std::runtime_error("duplicate key '" + std::string(name) +
|
||||
"' in section '" + section + "'.");
|
||||
}
|
||||
reader->_values[section][name] = value;
|
||||
return 1;
|
||||
}
|
||||
#endif // __INIREADER__
|
||||
|
||||
#ifndef __INIWRITER_H__
|
||||
#define __INIWRITER_H__
|
||||
|
||||
class INIWriter {
|
||||
public:
|
||||
INIWriter(){};
|
||||
/**
|
||||
* @brief Write the contents of an INI file to a new file
|
||||
* @param filepath The path of the output file
|
||||
* @param reader The INIReader object to write to the file
|
||||
* @throws std::runtime_error if the output file already exists or cannot be
|
||||
* opened
|
||||
*/
|
||||
inline static void write(const std::string& filepath,
|
||||
const INIReader& reader) {
|
||||
if (struct stat buf; stat(filepath.c_str(), &buf) == 0) {
|
||||
throw std::runtime_error("file: " + filepath + " already exist.");
|
||||
}
|
||||
std::ofstream out;
|
||||
out.open(filepath);
|
||||
if (!out.is_open()) {
|
||||
throw std::runtime_error("cannot open output file: " + filepath);
|
||||
}
|
||||
for (const auto& section : reader.Sections()) {
|
||||
out << "[" << section << "]\n";
|
||||
for (const auto& key : reader.Keys(section)) {
|
||||
out << key << "=" << reader.Get(section, key) << "\n";
|
||||
}
|
||||
}
|
||||
out.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif /* __INIWRITER_H__ */
|
74
src/main.cpp
Normal file
74
src/main.cpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
#include <QApplication>
|
||||
#include <QLabel>
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "dock.h"
|
||||
|
||||
void customMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
|
||||
// static QFile logFile("debug_output.log");
|
||||
// if (!logFile.isOpen()) {
|
||||
// logFile.open(QIODevice::Append | QIODevice::Text);
|
||||
// }
|
||||
|
||||
// QTextStream out(&logFile);
|
||||
QTextStream console(stdout);
|
||||
|
||||
QString levelStr;
|
||||
QString colorStart;
|
||||
const QString colorEnd = "\033[0m";
|
||||
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
levelStr = "DEBUG";
|
||||
colorStart = "\033[36m"; // Cyan
|
||||
break;
|
||||
case QtInfoMsg:
|
||||
levelStr = "INFO";
|
||||
colorStart = "\033[32m"; // Green
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
levelStr = "WARNING";
|
||||
colorStart = "\033[33m"; // Yellow
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
levelStr = "CRITICAL";
|
||||
colorStart = "\033[31m"; // Red
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
levelStr = "FATAL";
|
||||
colorStart = "\033[41m\033[97m"; // Red background, white text
|
||||
break;
|
||||
}
|
||||
|
||||
QString time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
|
||||
|
||||
// Plain version for log file
|
||||
QString plainText = QString("[%1] [%2] %3 (%4:%5)")
|
||||
.arg(time, levelStr, msg, context.file, QString::number(context.line));
|
||||
|
||||
// Colored level only for console
|
||||
QString coloredText = QString("[%1] [%2%3%4] %5 (%6:%7)")
|
||||
.arg(time, colorStart, levelStr, colorEnd, msg, context.file, QString::number(context.line));
|
||||
|
||||
// out << plainText << "\n";
|
||||
// out.flush();
|
||||
|
||||
console << coloredText << "\n";
|
||||
console.flush();
|
||||
|
||||
if (type == QtFatalMsg)
|
||||
abort();
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
qInstallMessageHandler(customMessageHandler);
|
||||
|
||||
QApplication app(argc, argv);
|
||||
Dock dock{&app};
|
||||
|
||||
return 0;
|
||||
}
|
217
src/xdg.h
Normal file
217
src/xdg.h
Normal file
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* Copyright © 2020 Danilo Spinella <oss@danyspin97.org>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
namespace xdg {
|
||||
|
||||
class BaseDirectoryException : public std::exception {
|
||||
public:
|
||||
explicit BaseDirectoryException(std::string msg) : msg_(std::move(msg)) {}
|
||||
|
||||
[[nodiscard]] auto what() const noexcept -> const char * override {
|
||||
return msg_.c_str();
|
||||
}
|
||||
[[nodiscard]] auto msg() const noexcept -> std::string { return msg_; }
|
||||
|
||||
private:
|
||||
const std::string msg_;
|
||||
};
|
||||
|
||||
class BaseDirectories {
|
||||
public:
|
||||
BaseDirectories() {
|
||||
const char *home_env = getenv("HOME");
|
||||
if (home_env == nullptr) {
|
||||
throw BaseDirectoryException("$HOME must be set");
|
||||
}
|
||||
home_ = std::filesystem::path{home_env};
|
||||
|
||||
data_home_ = GetAbsolutePathFromEnvOrDefault(
|
||||
"XDG_DATA_HOME", home_ / ".local" / "share");
|
||||
config_home_ = GetAbsolutePathFromEnvOrDefault("XDG_CONFIG_HOME",
|
||||
home_ / ".config");
|
||||
data_ = GetPathsFromEnvOrDefault("XDG_DATA_DIRS",
|
||||
std::vector<std::filesystem::path>{
|
||||
"/usr/local/share", "/usr/share"});
|
||||
config_ = GetPathsFromEnvOrDefault(
|
||||
"XDG_CONFIG_DIRS", std::vector<std::filesystem::path>{"/etc/xdg"});
|
||||
cache_home_ =
|
||||
GetAbsolutePathFromEnvOrDefault("XDG_CACHE_HOME", home_ / ".cache");
|
||||
|
||||
SetRuntimeDir();
|
||||
}
|
||||
|
||||
static auto GetInstance() -> BaseDirectories & {
|
||||
static BaseDirectories dirs;
|
||||
|
||||
return dirs;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto DataHome() -> const std::filesystem::path & {
|
||||
return data_home_;
|
||||
}
|
||||
[[nodiscard]] auto ConfigHome() -> const std::filesystem::path & {
|
||||
return config_home_;
|
||||
}
|
||||
[[nodiscard]] auto Data() -> const std::vector<std::filesystem::path> & {
|
||||
return data_;
|
||||
}
|
||||
[[nodiscard]] auto Config() -> const std::vector<std::filesystem::path> & {
|
||||
return config_;
|
||||
}
|
||||
[[nodiscard]] auto CacheHome() -> const std::filesystem::path & {
|
||||
return cache_home_;
|
||||
}
|
||||
[[nodiscard]] auto Runtime()
|
||||
-> const std::optional<std::filesystem::path> & {
|
||||
return runtime_;
|
||||
}
|
||||
|
||||
private:
|
||||
void SetRuntimeDir() {
|
||||
const char *runtime_env = getenv("XDG_RUNTIME_DIR");
|
||||
if (runtime_env != nullptr) {
|
||||
std::filesystem::path runtime_dir{runtime_env};
|
||||
if (runtime_dir.is_absolute()) {
|
||||
if (!std::filesystem::exists(runtime_dir)) {
|
||||
throw BaseDirectoryException(
|
||||
"$XDG_RUNTIME_DIR must exist on the system");
|
||||
}
|
||||
|
||||
auto runtime_dir_perms =
|
||||
std::filesystem::status(runtime_dir).permissions();
|
||||
using perms = std::filesystem::perms;
|
||||
// Check XDG_RUNTIME_DIR permissions are 0700
|
||||
if (((runtime_dir_perms & perms::owner_all) == perms::none) ||
|
||||
((runtime_dir_perms & perms::group_all) != perms::none) ||
|
||||
((runtime_dir_perms & perms::others_all) != perms::none)) {
|
||||
throw BaseDirectoryException(
|
||||
"$XDG_RUNTIME_DIR must have 0700 as permissions");
|
||||
}
|
||||
runtime_.emplace(runtime_dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static auto
|
||||
GetAbsolutePathFromEnvOrDefault(const char *env_name,
|
||||
std::filesystem::path &&default_path)
|
||||
-> std::filesystem::path {
|
||||
const char *env_var = getenv(env_name);
|
||||
if (env_var == nullptr) {
|
||||
return std::move(default_path);
|
||||
}
|
||||
auto path = std::filesystem::path{env_var};
|
||||
if (!path.is_absolute()) {
|
||||
return std::move(default_path);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
static auto
|
||||
GetPathsFromEnvOrDefault(const char *env_name,
|
||||
std::vector<std::filesystem::path> &&default_paths)
|
||||
-> std::vector<std::filesystem::path> {
|
||||
auto *env = getenv(env_name);
|
||||
if (env == nullptr) {
|
||||
return std::move(default_paths);
|
||||
}
|
||||
std::string paths{env};
|
||||
|
||||
std::vector<std::filesystem::path> dirs{};
|
||||
size_t start = 0;
|
||||
size_t pos = 0;
|
||||
while ((pos = paths.find_first_of(':', start)) != std::string::npos) {
|
||||
std::filesystem::path current_path{
|
||||
paths.substr(start, pos - start)};
|
||||
if (current_path.is_absolute() &&
|
||||
!VectorContainsPath(dirs, current_path)) {
|
||||
dirs.emplace_back(current_path);
|
||||
}
|
||||
start = pos + 1;
|
||||
}
|
||||
std::filesystem::path current_path{paths.substr(start, pos - start)};
|
||||
if (current_path.is_absolute() &&
|
||||
!VectorContainsPath(dirs, current_path)) {
|
||||
dirs.emplace_back(current_path);
|
||||
}
|
||||
|
||||
if (dirs.empty()) {
|
||||
return std::move(default_paths);
|
||||
}
|
||||
|
||||
return dirs;
|
||||
}
|
||||
|
||||
static auto
|
||||
VectorContainsPath(const std::vector<std::filesystem::path> &paths,
|
||||
const std::filesystem::path &path) -> bool {
|
||||
return std::find(std::begin(paths), std::end(paths), path) !=
|
||||
paths.end();
|
||||
}
|
||||
|
||||
std::filesystem::path home_;
|
||||
|
||||
std::filesystem::path data_home_;
|
||||
std::filesystem::path config_home_;
|
||||
std::vector<std::filesystem::path> data_;
|
||||
std::vector<std::filesystem::path> config_;
|
||||
std::filesystem::path cache_home_;
|
||||
std::optional<std::filesystem::path> runtime_;
|
||||
|
||||
}; // namespace xdg
|
||||
|
||||
[[nodiscard]] inline auto DataHomeDir() -> const std::filesystem::path & {
|
||||
return BaseDirectories::GetInstance().DataHome();
|
||||
}
|
||||
[[nodiscard]] inline auto ConfigHomeDir() -> const std::filesystem::path & {
|
||||
return BaseDirectories::GetInstance().ConfigHome();
|
||||
}
|
||||
[[nodiscard]] inline auto DataDirs()
|
||||
-> const std::vector<std::filesystem::path> & {
|
||||
return BaseDirectories::GetInstance().Data();
|
||||
}
|
||||
[[nodiscard]] inline auto ConfigDirs()
|
||||
-> const std::vector<std::filesystem::path> & {
|
||||
return BaseDirectories::GetInstance().Config();
|
||||
}
|
||||
[[nodiscard]] inline auto CacheHomeDir() -> const std::filesystem::path & {
|
||||
return BaseDirectories::GetInstance().CacheHome();
|
||||
}
|
||||
[[nodiscard]] inline auto RuntimeDir()
|
||||
-> const std::optional<std::filesystem::path> & {
|
||||
return BaseDirectories::GetInstance().Runtime();
|
||||
}
|
||||
|
||||
} // namespace xdg
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue