861 lines
28 KiB
C++
861 lines
28 KiB
C++
#include "mainwindow.hpp"
|
|
#include "kristall.hpp"
|
|
|
|
#include <QApplication>
|
|
#include <QUrl>
|
|
#include <QSettings>
|
|
#include <QCommandLineParser>
|
|
#include <QDebug>
|
|
#include <QStandardPaths>
|
|
#include <QFontDatabase>
|
|
#include <QLocalSocket>
|
|
#include <QLocalServer>
|
|
#include <cassert>
|
|
|
|
ProtocolSetup kristall::protocols;
|
|
IdentityCollection kristall::identities;
|
|
QSettings * kristall::settings;
|
|
QClipboard * kristall::clipboard;
|
|
SslTrust kristall::trust::gemini;
|
|
SslTrust kristall::trust::https;
|
|
FavouriteCollection kristall::favourites;
|
|
GenericSettings kristall::options;
|
|
DocumentStyle kristall::document_style(false);
|
|
CacheHandler kristall::cache;
|
|
QString kristall::default_font_family;
|
|
QString kristall::default_font_family_fixed;
|
|
|
|
QDir kristall::dirs::config_root;
|
|
QDir kristall::dirs::cache_root;
|
|
QDir kristall::dirs::offline_pages;
|
|
QDir kristall::dirs::themes;
|
|
QDir kristall::dirs::styles;
|
|
|
|
// We need QFont::setFamilies for emojis to work properly,
|
|
// Qt versions below 5.13 don't support this.
|
|
const bool kristall::EMOJIS_SUPPORTED =
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
|
|
false;
|
|
#else
|
|
true;
|
|
#endif
|
|
|
|
QString toFingerprintString(QSslCertificate const & certificate)
|
|
{
|
|
return QCryptographicHash::hash(certificate.toDer(), QCryptographicHash::Sha256).toHex(':');
|
|
}
|
|
|
|
static QSettings * app_settings_ptr;
|
|
static QApplication * app;
|
|
static MainWindow * main_window = nullptr;
|
|
static bool closing_state_saved = false;
|
|
|
|
#define SSTR(X) STR(X)
|
|
#define STR(X) #X
|
|
|
|
static QDir derive_dir(QDir const & parent, QString const & subdir)
|
|
{
|
|
QDir child = parent;
|
|
if(not child.mkpath(subdir)) {
|
|
qWarning() << "failed to initialize directory:" << subdir;
|
|
return QDir { };
|
|
}
|
|
if(not child.cd(subdir)) {
|
|
qWarning() << "failed to setup directory:" << subdir;
|
|
return QDir { };
|
|
}
|
|
return child;
|
|
}
|
|
|
|
static void addEmojiSubstitutions()
|
|
{
|
|
QFontDatabase db;
|
|
|
|
auto const families = db.families();
|
|
|
|
// Provide OpenMoji font for a safe fallback
|
|
QFontDatabase::addApplicationFont(":/fonts/OpenMoji-Color.ttf");
|
|
QFontDatabase::addApplicationFont(":/fonts/NotoColorEmoji.ttf");
|
|
|
|
QStringList emojiFonts = {
|
|
// Use system fonts on windows/mac
|
|
"Apple Color Emoji",
|
|
"Segoe UI Emoji",
|
|
|
|
// Provide common fonts as a fallback:
|
|
// "Noto Color Emoji", // this font seems to replace a lot of text characters?
|
|
// "JoyPixels", // this font seems to replace a lot of text characters?
|
|
|
|
// Built-in font fallback
|
|
"OpenMoji",
|
|
};
|
|
|
|
for(auto const & family: families)
|
|
{
|
|
auto current = QFont::substitutes(family);
|
|
current << emojiFonts;
|
|
// TODO: QFont::insertSubstitutions(family, current);
|
|
}
|
|
}
|
|
|
|
// Explanation to the IPC protocol:
|
|
// Each IPC request is required to open a new connection.
|
|
// The first some bytes are the ipc::Message struct that is inspected.
|
|
// After that, message-dependent bytes are sent and the IPC process is terminated.
|
|
// There is now way of indicating an error.
|
|
// Messages are not allowed to be larger than 64k
|
|
// We don't need to think about endianess or alignment as we only communicate with
|
|
// the same machine.
|
|
namespace ipc
|
|
{
|
|
static char const * socket_name = "net.random-projects.kristall";
|
|
|
|
struct Message
|
|
{
|
|
enum Type
|
|
{
|
|
/// Requests that a series of urls URLs are opened
|
|
/// as new tabs in the currently focused window.
|
|
/// Payload description:
|
|
/// The message will contain a sequence of UTF-8 encoded bytes
|
|
/// that encode URLs. The URLs are separated by LF.
|
|
open_in_tabs = 0,
|
|
|
|
/// Same format as open_in_tabs, but requests that these urls are
|
|
/// opened in a new window instead of new tabs.
|
|
open_in_window = 1,
|
|
};
|
|
|
|
enum Protocol : uint16_t
|
|
{
|
|
version_1 = 1,
|
|
};
|
|
|
|
Protocol version;
|
|
Type type;
|
|
};
|
|
|
|
static_assert(std::is_trivial_v<Message>, "Message needs to be flat-copyable!");
|
|
static_assert(std::is_trivially_copyable_v<Message>, "Message needs to be flat-copyable!");
|
|
|
|
//! Implements the
|
|
struct ConnectedClient : QObject
|
|
{
|
|
QLocalSocket * socket;
|
|
QByteArray receive_buffer;
|
|
bool everything_ok;
|
|
|
|
ConnectedClient(QLocalSocket * socket) :
|
|
QObject(socket),
|
|
socket(socket),
|
|
everything_ok(true)
|
|
{
|
|
QObject::connect(socket, &QLocalSocket::readyRead, this, &ConnectedClient::on_readyRead);
|
|
QObject::connect(socket, &QLocalSocket::disconnected, this, &ConnectedClient::on_disconnected);
|
|
}
|
|
|
|
void on_readyRead()
|
|
{
|
|
auto const buffer = socket->readAll();
|
|
if(buffer.size() + this->receive_buffer.size() > 65536) {
|
|
qCritical() << "ipc failure: IPC client sent more than 64k bytes of data!";
|
|
this->everything_ok = false;
|
|
this->socket->close();
|
|
}
|
|
|
|
this->receive_buffer.append(buffer);
|
|
}
|
|
|
|
void on_disconnected()
|
|
{
|
|
if(not this->everything_ok)
|
|
return;
|
|
if(size_t(this->receive_buffer.size()) < sizeof(Message)) {
|
|
qCritical() << "ipc failure: IPC client did not send enough data!";
|
|
return;
|
|
}
|
|
Message message;
|
|
memcpy(&message, this->receive_buffer.data(), sizeof(Message));
|
|
switch(message.version)
|
|
{
|
|
case Message::version_1: {
|
|
this->processRequest(
|
|
message.type,
|
|
this->receive_buffer.mid(sizeof(Message))
|
|
);
|
|
break;
|
|
}
|
|
default: {
|
|
qCritical() << "ipc failure: IPC client used a unsupported protocol version!";
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void processRequest(Message::Type type, QByteArray const & payload)
|
|
{
|
|
switch(type)
|
|
{
|
|
case Message::open_in_tabs: {
|
|
for(auto const & data : payload.split('\n'))
|
|
{
|
|
QUrl url { QString::fromUtf8(data) };
|
|
if(url.isValid()) {
|
|
if(main_window != nullptr) {
|
|
main_window->addNewTab(true, url);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Message::open_in_window: {
|
|
for(auto const & data : payload.split('\n'))
|
|
{
|
|
QUrl url { QString::fromUtf8(data) };
|
|
if(url.isValid()) {
|
|
// TODO: Implement opening these urls in a new
|
|
// window instead of a new tab!
|
|
if(main_window != nullptr) {
|
|
main_window->addNewTab(true, url);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
qCritical() << "ipc failure: IPC client used a unsupported message type!";
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void send(QLocalSocket & socket, void const * buffer, size_t length)
|
|
{
|
|
size_t offset = 0;
|
|
while(offset < length)
|
|
{
|
|
auto const sent = socket.write(
|
|
reinterpret_cast<char const *>(buffer) + offset,
|
|
length - offset
|
|
);
|
|
if(sent <= 0)
|
|
return;
|
|
offset += sent;
|
|
}
|
|
}
|
|
|
|
/// Sends a open_in_tabs request and closes the socket.
|
|
void sendOpenInTabs(QLocalSocket & socket, QVector<QUrl> const & urls)
|
|
{
|
|
Message msg { Message::version_1, Message::open_in_tabs };
|
|
send(socket, &msg, sizeof msg);
|
|
for(int i = 0; i < urls.size(); i++)
|
|
{
|
|
if(i > 0)
|
|
send(socket, "\n", 1);
|
|
auto const bits = urls[i].toString(QUrl::FullyEncoded).toUtf8();
|
|
send(socket, bits.data(), bits.size());
|
|
}
|
|
}
|
|
|
|
/// Sends a open_in_window request and closes the socket.
|
|
void sendOpenInWindow(QLocalSocket & socket, QVector<QUrl> const & urls)
|
|
{
|
|
Message msg { Message::version_1, Message::open_in_window };
|
|
send(socket, &msg, sizeof msg);
|
|
for(int i = 0; i < urls.size(); i++)
|
|
{
|
|
if(i > 0)
|
|
send(socket, "\n", 1);
|
|
auto const bits = urls[i].toString(QUrl::FullyEncoded).toUtf8();
|
|
send(socket, bits.data(), bits.size());
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
QApplication app(argc, argv);
|
|
app.setApplicationVersion(SSTR(KRISTALL_VERSION));
|
|
|
|
::app = &app;
|
|
|
|
{
|
|
// Initialise default fonts
|
|
#ifdef Q_OS_WIN32
|
|
// Windows default fonts are ugly, so we use standard ones.
|
|
kristall::default_font_family = "Segoe UI";
|
|
kristall::default_font_family_fixed = "Consolas";
|
|
#else
|
|
// *nix
|
|
kristall::default_font_family = QFontDatabase::systemFont(QFontDatabase::GeneralFont).family();
|
|
kristall::default_font_family_fixed = QFontInfo(QFont("monospace")).family();
|
|
#endif
|
|
kristall::document_style.initialiseDefaultFonts();
|
|
}
|
|
|
|
kristall::clipboard = app.clipboard();
|
|
|
|
addEmojiSubstitutions();
|
|
|
|
QCommandLineParser cli_parser;
|
|
|
|
QCommandLineOption newWindowOption {
|
|
{ "w", "new-window" },
|
|
app.tr("Opens the provided links in a new window instead of tabs."),
|
|
};
|
|
|
|
QCommandLineOption isolatedOption {
|
|
{ "i", "isolated" },
|
|
app.tr("Starts the instance of kristall as a isolated session that cannot communicate with other windows."),
|
|
};
|
|
|
|
cli_parser.addVersionOption();
|
|
cli_parser.addHelpOption();
|
|
cli_parser.addOption(newWindowOption);
|
|
cli_parser.addOption(isolatedOption);
|
|
|
|
cli_parser.addPositionalArgument("urls", app.tr("The urls that should be opened instead of the start page"), "[urls...]");
|
|
|
|
cli_parser.process(app);
|
|
|
|
QVector<QUrl> urls;
|
|
{
|
|
auto cli_args = cli_parser.positionalArguments();
|
|
for(auto const & url_str : cli_args)
|
|
{
|
|
QUrl url { url_str };
|
|
if (url.isRelative())
|
|
{
|
|
if (QFile::exists(url_str)) {
|
|
url = QUrl::fromLocalFile(QFileInfo(url_str).absoluteFilePath());
|
|
} else {
|
|
url = QUrl("gemini://" + url_str);
|
|
}
|
|
}
|
|
if(url.isValid()) {
|
|
urls.append(url);
|
|
} else {
|
|
qDebug() << "Invalid url: " << url_str;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto const open_new_window = cli_parser.isSet(newWindowOption);
|
|
|
|
auto const isolated_session = cli_parser.isSet(isolatedOption);
|
|
|
|
std::unique_ptr<QLocalServer> ipc_server { nullptr };
|
|
|
|
if(not isolated_session)
|
|
{
|
|
// try connecting to a already existing instance of kristall
|
|
{
|
|
QLocalSocket socket;
|
|
socket.connectToServer(ipc::socket_name);
|
|
// do not use less time here as we need to give the other task a bit
|
|
// of time. Most OS have a "loop time" of ~10 ms, so we use twice the
|
|
// time here to allow a response.
|
|
if(socket.waitForConnected(20))
|
|
{
|
|
qDebug() << "we already have a kristall instance running!";
|
|
if(urls.length() > 0)
|
|
{
|
|
if(open_new_window)
|
|
ipc::sendOpenInWindow(socket, urls);
|
|
else
|
|
ipc::sendOpenInTabs(socket, urls);
|
|
}
|
|
socket.waitForBytesWritten();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Otherwise, spawn a new local socket that will accept messages
|
|
// to provide proper IPC
|
|
{
|
|
std::unique_ptr<QLocalServer> server { new QLocalServer };
|
|
if(server->listen(ipc::socket_name))
|
|
{
|
|
qDebug() << "successfully started the IPC socket.";
|
|
ipc_server = std::move(server);
|
|
}
|
|
else
|
|
{
|
|
qCritical() << "failed to create IPC socket: " << server->errorString();
|
|
}
|
|
}
|
|
}
|
|
|
|
QString cache_root = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
|
|
QString config_root = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
|
|
|
|
kristall::dirs::config_root = QDir { config_root };
|
|
kristall::dirs::cache_root = QDir { cache_root };
|
|
|
|
kristall::dirs::offline_pages = derive_dir(kristall::dirs::cache_root, "offline-pages");
|
|
kristall::dirs::themes = derive_dir(kristall::dirs::config_root, "themes");
|
|
|
|
kristall::dirs::styles = derive_dir(kristall::dirs::config_root, "styles");
|
|
kristall::dirs::styles.setNameFilters(QStringList { "*.kthm" });
|
|
kristall::dirs::styles.setFilter(QDir::Files);
|
|
|
|
QSettings app_settings {
|
|
kristall::dirs::config_root.absoluteFilePath("config.ini"),
|
|
QSettings::IniFormat
|
|
};
|
|
app_settings_ptr = &app_settings;
|
|
|
|
{
|
|
QSettings deprecated_settings { "xqTechnologies", "Kristall" };
|
|
if(QFile(deprecated_settings.fileName()).exists())
|
|
{
|
|
if(deprecated_settings.value("deprecated", false) == false)
|
|
{
|
|
qDebug() << "Migrating to new configuration style.";
|
|
for(auto const & key : deprecated_settings.allKeys())
|
|
{
|
|
app_settings.setValue(key, deprecated_settings.value(key));
|
|
}
|
|
|
|
// Migrate themes to new model
|
|
{
|
|
int items = deprecated_settings.beginReadArray("Themes");
|
|
|
|
for(int i = 0; i < items; i++)
|
|
{
|
|
deprecated_settings.setArrayIndex(i);
|
|
|
|
QString name = deprecated_settings.value("name").toString();
|
|
|
|
DocumentStyle style;
|
|
style.load(deprecated_settings);
|
|
|
|
QString fileName;
|
|
int index = 0;
|
|
do {
|
|
fileName = DocumentStyle::createFileNameFromName(name, index);
|
|
index += 1;
|
|
} while(kristall::dirs::styles.exists(fileName));
|
|
|
|
QSettings style_sheet {
|
|
kristall::dirs::styles.absoluteFilePath(fileName),
|
|
QSettings::IniFormat
|
|
};
|
|
style_sheet.setValue("name", name);
|
|
style.save(style_sheet);
|
|
style_sheet.sync();
|
|
}
|
|
|
|
deprecated_settings.endArray();
|
|
}
|
|
|
|
// Remove old theming stuff
|
|
app_settings.remove("Theme");
|
|
app_settings.remove("Themes");
|
|
|
|
// Migrate "current theme" to new format
|
|
{
|
|
DocumentStyle current_style;
|
|
deprecated_settings.beginGroup("Theme");
|
|
current_style.load(deprecated_settings);
|
|
deprecated_settings.endGroup();
|
|
|
|
app_settings.beginGroup("Theme");
|
|
current_style.save(app_settings);
|
|
app_settings.endGroup();
|
|
}
|
|
|
|
deprecated_settings.setValue("deprecated", true);
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "Migration complete. Please delete" << deprecated_settings.fileName();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Migrate to new favourites format
|
|
if(int len = app_settings.beginReadArray("favourites"); len > 0)
|
|
{
|
|
qDebug() << "Migrating old-style favourites...";
|
|
std::vector<Favourite> favs;
|
|
|
|
favs.reserve(len);
|
|
for(int i = 0; i < len; i++)
|
|
{
|
|
app_settings.setArrayIndex(i);
|
|
|
|
Favourite fav;
|
|
fav.destination = app_settings.value("url").toString();
|
|
fav.title = QString { };
|
|
|
|
favs.emplace_back(std::move(fav));
|
|
}
|
|
app_settings.endArray();
|
|
|
|
|
|
app_settings.beginGroup("Favourites");
|
|
{
|
|
app_settings.beginWriteArray("groups");
|
|
|
|
app_settings.setArrayIndex(0);
|
|
app_settings.setValue("name", QObject::tr("Unsorted"));
|
|
|
|
{
|
|
app_settings.beginWriteArray("favourites", len);
|
|
for(int i = 0; i < len; i++)
|
|
{
|
|
auto const & fav = favs.at(i);
|
|
app_settings.setArrayIndex(i);
|
|
app_settings.setValue("title", fav.title);
|
|
app_settings.setValue("url", fav.destination);
|
|
}
|
|
app_settings.endArray();
|
|
}
|
|
|
|
app_settings.endArray();
|
|
}
|
|
app_settings.endGroup();
|
|
|
|
app_settings.remove("favourites");
|
|
}
|
|
else {
|
|
app_settings.endArray();
|
|
}
|
|
|
|
kristall::settings = &app_settings;
|
|
|
|
kristall::options.load(app_settings);
|
|
|
|
app_settings.beginGroup("Protocols");
|
|
kristall::protocols.load(app_settings);
|
|
app_settings.endGroup();
|
|
|
|
app_settings.beginGroup("Client Identities");
|
|
kristall::identities.load(app_settings);
|
|
app_settings.endGroup();
|
|
|
|
app_settings.beginGroup("Trusted Servers");
|
|
kristall::trust::gemini.load(app_settings);
|
|
app_settings.endGroup();
|
|
|
|
app_settings.beginGroup("Trusted HTTPS Servers");
|
|
kristall::trust::https.load(app_settings);
|
|
app_settings.endGroup();
|
|
|
|
app_settings.beginGroup("Theme");
|
|
kristall::document_style.load(app_settings);
|
|
app_settings.endGroup();
|
|
|
|
app_settings.beginGroup("Favourites");
|
|
kristall::favourites.load(app_settings);
|
|
app_settings.endGroup();
|
|
|
|
kristall::setTheme(kristall::options.theme);
|
|
|
|
MainWindow w(&app);
|
|
main_window = &w;
|
|
|
|
QObject::connect(ipc_server.get(), &QLocalServer::newConnection, [&ipc_server]() {
|
|
auto * const socket = ipc_server->nextPendingConnection();
|
|
if(socket != nullptr) {
|
|
// this will set up everything needed:
|
|
// - signals from socket
|
|
// - set itself as the socket child, so it will be deleted when the socket is closed
|
|
(void) new ipc::ConnectedClient(socket);
|
|
|
|
// destroy the socket when the connection was closed.
|
|
QObject::connect(socket, &QLocalSocket::disconnected, socket, &QObject::deleteLater);
|
|
}
|
|
});
|
|
|
|
// Open all URLs in the new window
|
|
if(urls.size() > 0) {
|
|
for(const auto & url : urls) {
|
|
w.addNewTab(false, url);
|
|
}
|
|
}
|
|
else {
|
|
w.addEmptyTab(true, true);
|
|
}
|
|
|
|
app_settings.beginGroup("Window State");
|
|
if(app_settings.contains("geometry")) {
|
|
w.restoreGeometry(app_settings.value("geometry").toByteArray());
|
|
}
|
|
if(app_settings.contains("state")) {
|
|
w.restoreState(app_settings.value("state").toByteArray());
|
|
}
|
|
app_settings.endGroup();
|
|
|
|
w.show();
|
|
|
|
int exit_code = app.exec();
|
|
|
|
if (!closing_state_saved)
|
|
kristall::saveWindowState();
|
|
|
|
return exit_code;
|
|
}
|
|
|
|
void GenericSettings::load(QSettings &settings)
|
|
{
|
|
network_timeout = settings.value("network_timeout", 5000).toInt();
|
|
start_page = settings.value("start_page", "about:favourites").toString();
|
|
search_engine = settings.value("search_engine", "gemini://geminispace.info/search?%1").toString();
|
|
|
|
if(settings.value("text_display", "fancy").toString() == "plain")
|
|
text_display = PlainText;
|
|
else
|
|
text_display = FormattedText;
|
|
|
|
enable_text_decoration = settings.value("text_decoration", false).toBool();
|
|
|
|
QString theme_name = settings.value("theme", "os_default").toString();
|
|
if(theme_name == "dark")
|
|
theme = Theme::dark;
|
|
else if(theme_name == "light")
|
|
theme = Theme::light;
|
|
else
|
|
theme = Theme::os_default;
|
|
|
|
QString icon_theme_name = settings.value("icon_theme", "auto").toString();
|
|
if (icon_theme_name == "light")
|
|
icon_theme = IconTheme::light;
|
|
else if (icon_theme_name == "dark")
|
|
icon_theme = IconTheme::dark;
|
|
else
|
|
icon_theme = IconTheme::automatic;
|
|
|
|
QString density = settings.value("ui_density", "compact").toString();
|
|
if(density == "compact")
|
|
ui_density = UIDensity::compact;
|
|
else if (density == "classic")
|
|
ui_density = UIDensity::classic;
|
|
|
|
if(settings.value("gophermap_display", "rendered").toString() == "rendered")
|
|
gophermap_display = FormattedText;
|
|
else
|
|
gophermap_display = PlainText;
|
|
|
|
use_os_scheme_handler = settings.value("use_os_scheme_handler", false).toBool();
|
|
|
|
show_hidden_files_in_dirs = settings.value("show_hidden_files_in_dirs", false).toBool();
|
|
|
|
fancy_urlbar = settings.value("fancy_urlbar", true).toBool();
|
|
|
|
fancy_quotes = settings.value("fancy_quotes", true).toBool();
|
|
|
|
emojis_enabled = kristall::EMOJIS_SUPPORTED
|
|
? settings.value("emojis_enabled", true).toBool()
|
|
: false;
|
|
|
|
max_redirections = settings.value("max_redirections", 5).toInt();
|
|
redirection_policy = RedirectionWarning(settings.value("redirection_policy ", WarnOnHostChange).toInt());
|
|
|
|
enable_home_btn = settings.value("enable_home_btn", false).toBool();
|
|
enable_newtab_btn = settings.value("enable_newtab_btn", true).toBool();
|
|
enable_root_btn = settings.value("enable_root_btn", false).toBool();
|
|
enable_parent_btn = settings.value("enable_parent_btn", false).toBool();
|
|
|
|
cache_limit = settings.value("cache_limit", 1000).toInt();
|
|
cache_threshold = settings.value("cache_threshold", 125).toInt();
|
|
cache_life = settings.value("cache_life", 15).toInt();
|
|
cache_unlimited_life = settings.value("cache_unlimited_life", true).toBool();
|
|
}
|
|
|
|
void GenericSettings::save(QSettings &settings) const
|
|
{
|
|
settings.setValue("start_page", this->start_page);
|
|
settings.setValue("search_engine", this->search_engine);
|
|
settings.setValue("text_display", (text_display == FormattedText) ? "fancy" : "plain");
|
|
settings.setValue("text_decoration", enable_text_decoration);
|
|
QString theme_name = "os_default";
|
|
switch(theme) {
|
|
case Theme::dark: theme_name = "dark"; break;
|
|
case Theme::light: theme_name = "light"; break;
|
|
case Theme::os_default: theme_name = "os_default"; break;
|
|
}
|
|
settings.setValue("theme", theme_name);
|
|
|
|
QString icon_theme_name = "auto";
|
|
switch(icon_theme) {
|
|
case IconTheme::dark: icon_theme_name = "dark"; break;
|
|
case IconTheme::light: icon_theme_name = "light"; break;
|
|
case IconTheme::automatic: icon_theme_name = "auto"; break;
|
|
}
|
|
settings.setValue("icon_theme", icon_theme_name);
|
|
|
|
QString density = "compact";
|
|
switch(ui_density) {
|
|
case UIDensity::compact: density = "compact"; break;
|
|
case UIDensity::classic: density = "classic"; break;
|
|
}
|
|
settings.setValue("ui_density", density);
|
|
|
|
settings.setValue("gophermap_display", (gophermap_display == FormattedText) ? "rendered" : "text");
|
|
settings.setValue("use_os_scheme_handler", use_os_scheme_handler);
|
|
settings.setValue("show_hidden_files_in_dirs", show_hidden_files_in_dirs);
|
|
settings.setValue("fancy_urlbar", fancy_urlbar);
|
|
settings.setValue("fancy_quotes", fancy_quotes);
|
|
settings.setValue("max_redirections", max_redirections);
|
|
settings.setValue("redirection_policy", int(redirection_policy));
|
|
settings.setValue("network_timeout", network_timeout);
|
|
settings.setValue("enable_home_btn", enable_home_btn);
|
|
settings.setValue("enable_newtab_btn", enable_newtab_btn);
|
|
settings.setValue("enable_root_btn", enable_root_btn);
|
|
settings.setValue("enable_parent_btn", enable_parent_btn);
|
|
|
|
settings.setValue("cache_limit", cache_limit);
|
|
settings.setValue("cache_threshold", cache_threshold);
|
|
settings.setValue("cache_life", cache_life);
|
|
settings.setValue("cache_unlimited_life", cache_unlimited_life);
|
|
|
|
if (kristall::EMOJIS_SUPPORTED)
|
|
{
|
|
// Save emoji pref only if emojis are supported, so if user changes to a build
|
|
// with emoji support, they get it out of the box.
|
|
settings.setValue("emojis_enabled", emojis_enabled);
|
|
}
|
|
}
|
|
|
|
|
|
void kristall::saveSettings()
|
|
{
|
|
assert(app_settings_ptr != nullptr);
|
|
QSettings & app_settings = *app_settings_ptr;
|
|
|
|
app_settings.beginGroup("Favourites");
|
|
kristall::favourites.save(app_settings);
|
|
app_settings.endGroup();
|
|
|
|
app_settings.beginGroup("Protocols");
|
|
kristall::protocols.save(app_settings);
|
|
app_settings.endGroup();
|
|
|
|
app_settings.beginGroup("Client Identities");
|
|
kristall::identities.save(app_settings);
|
|
app_settings.endGroup();
|
|
|
|
app_settings.beginGroup("Trusted Servers");
|
|
kristall::trust::gemini.save(app_settings);
|
|
app_settings.endGroup();
|
|
|
|
app_settings.beginGroup("Trusted HTTPS Servers");
|
|
kristall::trust::https.save(app_settings);
|
|
app_settings.endGroup();
|
|
|
|
app_settings.beginGroup("Theme");
|
|
kristall::document_style.save(app_settings);
|
|
app_settings.endGroup();
|
|
|
|
kristall::options.save(app_settings);
|
|
|
|
app_settings.sync();
|
|
}
|
|
|
|
void kristall::setTheme(Theme theme)
|
|
{
|
|
assert(app != nullptr);
|
|
|
|
if(theme == Theme::os_default)
|
|
{
|
|
app->setStyleSheet("");
|
|
|
|
// Use "mid" colour for our URL bar dim colour:
|
|
QColor col = app->palette().color(QPalette::WindowText);
|
|
col.setAlpha(150);
|
|
kristall::options.fancy_urlbar_dim_colour = std::move(col);
|
|
}
|
|
else if(theme == Theme::light)
|
|
{
|
|
QFile file(":/light.qss");
|
|
file.open(QFile::ReadOnly | QFile::Text);
|
|
QTextStream stream(&file);
|
|
app->setStyleSheet(stream.readAll());
|
|
|
|
kristall::options.fancy_urlbar_dim_colour = QColor(128, 128, 128, 255);
|
|
}
|
|
else if(theme == Theme::dark)
|
|
{
|
|
QFile file(":/dark.qss");
|
|
file.open(QFile::ReadOnly | QFile::Text);
|
|
QTextStream stream(&file);
|
|
app->setStyleSheet(stream.readAll());
|
|
|
|
kristall::options.fancy_urlbar_dim_colour = QColor(150, 150, 150, 255);
|
|
}
|
|
|
|
kristall::setIconTheme(kristall::options.icon_theme, theme);
|
|
|
|
if (main_window && main_window->curTab())
|
|
main_window->curTab()->updateUrlBarStyle();
|
|
}
|
|
|
|
void kristall::setIconTheme(IconTheme icotheme, Theme uitheme)
|
|
{
|
|
assert(app != nullptr);
|
|
|
|
static const QString icothemes[] = {
|
|
"light", // Light theme (dark icons)
|
|
"dark" // Dark theme (light icons)
|
|
};
|
|
|
|
auto ret = []() {
|
|
if (main_window && main_window->curTab())
|
|
main_window->curTab()->refreshToolbarIcons();
|
|
};
|
|
|
|
if (icotheme == IconTheme::automatic)
|
|
{
|
|
if (uitheme == Theme::os_default)
|
|
{
|
|
// For Linux we use standard system icon set,
|
|
// for Windows & Mac we just use our default light theme icons.
|
|
#if defined Q_OS_WIN32 || defined Q_OS_DARWIN
|
|
QIcon::setThemeName("light");
|
|
#else
|
|
QIcon::setThemeName("");
|
|
#endif
|
|
|
|
kristall::options.explicit_icon_theme = IconTheme::dark;
|
|
|
|
ret();
|
|
return;
|
|
}
|
|
|
|
// Use icon theme based on UI theme
|
|
QIcon::setThemeName(icothemes[(int)uitheme]);
|
|
kristall::options.explicit_icon_theme = (IconTheme)uitheme;
|
|
ret();
|
|
return;
|
|
}
|
|
|
|
// Use icon specified by user
|
|
QIcon::setThemeName(icothemes[(int)icotheme]);
|
|
kristall::options.explicit_icon_theme = (IconTheme)icotheme;
|
|
ret();
|
|
}
|
|
|
|
void kristall::setUiDensity(UIDensity density, bool previewing)
|
|
{
|
|
assert(app != nullptr);
|
|
assert(main_window != nullptr);
|
|
main_window->setUiDensity(density, previewing);
|
|
}
|
|
|
|
void kristall::saveWindowState()
|
|
{
|
|
closing_state_saved = true;
|
|
|
|
app_settings_ptr->beginGroup("Window State");
|
|
app_settings_ptr->setValue("geometry", main_window->saveGeometry());
|
|
app_settings_ptr->setValue("state", main_window->saveState());
|
|
app_settings_ptr->endGroup();
|
|
|
|
kristall::saveSettings();
|
|
}
|