Implements an IPC scheme for kristall. Closes #139.
This commit is contained in:
parent
86507cbe9f
commit
a7a7ec20c2
|
@ -1,34 +1,54 @@
|
||||||
[General]
|
[General]
|
||||||
name=DOS
|
ansi_colors=black, darkred, darkgreen, darkgoldenrod, darkblue, darkmagenta, darkcyan, lightgray, gray, red, green, goldenrod, lightblue, magenta, cyan, white
|
||||||
version=1
|
|
||||||
theme=0
|
|
||||||
background_color=#00008b
|
background_color=#00008b
|
||||||
blockquote_color=#000000
|
blockquote_color=#000000
|
||||||
margins=55
|
margins_h=55
|
||||||
|
margins_v=55
|
||||||
|
name=DOS
|
||||||
|
theme=0
|
||||||
|
version=1
|
||||||
|
|
||||||
[Standard]
|
[Blockquote]
|
||||||
font="PxPlus IBM VGA9,16,-1,5,50,0,0,0,0,0,Regular"
|
|
||||||
color=#d3d3d3
|
color=#d3d3d3
|
||||||
|
font="Source Code Pro,16,-1,5,50,0,0,0,0,0,Regular"
|
||||||
|
|
||||||
[Preformatted]
|
[Formatting]
|
||||||
font="PxPlus IBM VGA9,16,-1,5,50,0,0,0,0,0,Regular"
|
centre_h1=false
|
||||||
color=#ffffff
|
indent_bq=0
|
||||||
|
indent_h=0
|
||||||
|
indent_l=2
|
||||||
|
indent_p=0
|
||||||
|
indent_size=16
|
||||||
|
justify_text=true
|
||||||
|
line_height_h=0
|
||||||
|
line_height_p=0
|
||||||
|
list_symbol=-3
|
||||||
|
text_width=900
|
||||||
|
text_width_enabled=true
|
||||||
|
|
||||||
[H1]
|
[H1]
|
||||||
font="PxPlus IBM VGA9,16,-1,5,50,0,0,0,0,0,Regular"
|
|
||||||
color=#ffff00
|
color=#ffff00
|
||||||
|
font="Source Code Pro,16,-1,5,50,0,0,0,0,0,Regular"
|
||||||
|
|
||||||
[H2]
|
[H2]
|
||||||
font="PxPlus IBM VGA9,16,-1,5,50,0,0,0,0,0,Regular"
|
|
||||||
color=#00ff00
|
color=#00ff00
|
||||||
|
font="Source Code Pro,16,-1,5,50,0,0,0,0,0,Regular"
|
||||||
|
|
||||||
[H3]
|
[H3]
|
||||||
font="PxPlus IBM VGA9,16,-1,5,50,0,0,0,0,0,Regular"
|
|
||||||
color=#ffffff
|
color=#ffffff
|
||||||
|
font="Source Code Pro,16,-1,5,50,0,0,0,0,0,Regular"
|
||||||
|
|
||||||
[Link]
|
[Link]
|
||||||
color_internal=#00ffff
|
|
||||||
color_external=#00ffff
|
|
||||||
color_cross_scheme=#00ffff
|
color_cross_scheme=#00ffff
|
||||||
internal_prefix="\x2192 "
|
color_external=#00ffff
|
||||||
|
color_internal=#00ffff
|
||||||
external_prefix="\x21d2 "
|
external_prefix="\x21d2 "
|
||||||
|
internal_prefix="\x2192 "
|
||||||
|
|
||||||
|
[Preformatted]
|
||||||
|
color=#ffffff
|
||||||
|
font="Source Code Pro,16,-1,5,50,0,0,0,0,0,Regular"
|
||||||
|
|
||||||
|
[Standard]
|
||||||
|
color=#d3d3d3
|
||||||
|
font="Source Code Pro,16,-1,5,50,0,0,0,0,0,Regular"
|
||||||
|
|
|
@ -365,6 +365,7 @@ There is also the scheme about: which can be used to access internal sites for c
|
||||||
=> about:help
|
=> about:help
|
||||||
=> about:updates
|
=> about:updates
|
||||||
=> about:style-preview
|
=> about:style-preview
|
||||||
|
=> about:style-display
|
||||||
=> about:cache
|
=> about:cache
|
||||||
|
|
||||||
## Security Concept
|
## Security Concept
|
||||||
|
|
|
@ -14,6 +14,10 @@
|
||||||
* New action: "View/Show document source" will display the raw data of the document in a small separate window
|
* New action: "View/Show document source" will display the raw data of the document in a small separate window
|
||||||
* Change: Makes TLS editor columns sortable
|
* Change: Makes TLS editor columns sortable
|
||||||
|
|
||||||
|
-- There's a lot missing here still
|
||||||
|
|
||||||
|
* Adds single-session mode so opening links will open them in the currently focuse kristall window instead of a new instance every time.
|
||||||
|
|
||||||
## 0.3 - TLS and security
|
## 0.3 - TLS and security
|
||||||
* Adds support for transient client certificates
|
* Adds support for transient client certificates
|
||||||
* Adds support for permanent client certificates
|
* Adds support for permanent client certificates
|
||||||
|
@ -46,7 +50,7 @@
|
||||||
* Implement Ctrl+S/*Save as...* menu item
|
* Implement Ctrl+S/*Save as...* menu item
|
||||||
* Add display for "non-recognized files"
|
* Add display for "non-recognized files"
|
||||||
* Added support for gopher:// and gophermaps
|
* Added support for gopher:// and gophermaps
|
||||||
* Added "go to home" menu
|
* Added "go to home" menu
|
||||||
* Added support for video/* and audio/* via QMediaPlayer
|
* Added support for video/* and audio/* via QMediaPlayer
|
||||||
* Added support for file:// scheme
|
* Added support for file:// scheme
|
||||||
* Added status bar with loading time, file size and mime type
|
* Added status bar with loading time, file size and mime type
|
||||||
|
|
291
src/main.cpp
291
src/main.cpp
|
@ -8,6 +8,8 @@
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QFontDatabase>
|
#include <QFontDatabase>
|
||||||
|
#include <QLocalSocket>
|
||||||
|
#include <QLocalServer>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
ProtocolSetup kristall::protocols;
|
ProtocolSetup kristall::protocols;
|
||||||
|
@ -96,6 +98,182 @@ static void addEmojiSubstitutions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
@ -123,12 +301,94 @@ int main(int argc, char *argv[])
|
||||||
addEmojiSubstitutions();
|
addEmojiSubstitutions();
|
||||||
|
|
||||||
QCommandLineParser cli_parser;
|
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.addVersionOption();
|
||||||
cli_parser.addHelpOption();
|
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.addPositionalArgument("urls", app.tr("The urls that should be opened instead of the start page"), "[urls...]");
|
||||||
|
|
||||||
cli_parser.process(app);
|
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 cache_root = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
|
||||||
QString config_root = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
|
QString config_root = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
|
||||||
|
|
||||||
|
@ -299,22 +559,23 @@ int main(int argc, char *argv[])
|
||||||
MainWindow w(&app);
|
MainWindow w(&app);
|
||||||
main_window = &w;
|
main_window = &w;
|
||||||
|
|
||||||
auto urls = cli_parser.positionalArguments();
|
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) {
|
if(urls.size() > 0) {
|
||||||
for(const auto &url_str : urls) {
|
for(const auto & url : urls) {
|
||||||
QUrl url { url_str };
|
w.addNewTab(false, url);
|
||||||
if (url.isRelative()) {
|
|
||||||
if (QFile::exists(url_str)) {
|
|
||||||
url = QUrl::fromLocalFile(QFileInfo(url_str).absoluteFilePath());
|
|
||||||
} else {
|
|
||||||
url = QUrl("gemini://" + url_str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(url.isValid()) {
|
|
||||||
w.addNewTab(false, url);
|
|
||||||
} else {
|
|
||||||
qDebug() << "Invalid url: " << url_str;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
Loading…
Reference in New Issue