Implements an IPC scheme for kristall. Closes #139.
This commit is contained in:
parent
86507cbe9f
commit
a7a7ec20c2
|
@ -1,34 +1,54 @@
|
|||
[General]
|
||||
name=DOS
|
||||
version=1
|
||||
theme=0
|
||||
ansi_colors=black, darkred, darkgreen, darkgoldenrod, darkblue, darkmagenta, darkcyan, lightgray, gray, red, green, goldenrod, lightblue, magenta, cyan, white
|
||||
background_color=#00008b
|
||||
blockquote_color=#000000
|
||||
margins=55
|
||||
margins_h=55
|
||||
margins_v=55
|
||||
name=DOS
|
||||
theme=0
|
||||
version=1
|
||||
|
||||
[Standard]
|
||||
font="PxPlus IBM VGA9,16,-1,5,50,0,0,0,0,0,Regular"
|
||||
[Blockquote]
|
||||
color=#d3d3d3
|
||||
font="Source Code Pro,16,-1,5,50,0,0,0,0,0,Regular"
|
||||
|
||||
[Preformatted]
|
||||
font="PxPlus IBM VGA9,16,-1,5,50,0,0,0,0,0,Regular"
|
||||
color=#ffffff
|
||||
[Formatting]
|
||||
centre_h1=false
|
||||
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]
|
||||
font="PxPlus IBM VGA9,16,-1,5,50,0,0,0,0,0,Regular"
|
||||
color=#ffff00
|
||||
font="Source Code Pro,16,-1,5,50,0,0,0,0,0,Regular"
|
||||
|
||||
[H2]
|
||||
font="PxPlus IBM VGA9,16,-1,5,50,0,0,0,0,0,Regular"
|
||||
color=#00ff00
|
||||
font="Source Code Pro,16,-1,5,50,0,0,0,0,0,Regular"
|
||||
|
||||
[H3]
|
||||
font="PxPlus IBM VGA9,16,-1,5,50,0,0,0,0,0,Regular"
|
||||
color=#ffffff
|
||||
font="Source Code Pro,16,-1,5,50,0,0,0,0,0,Regular"
|
||||
|
||||
[Link]
|
||||
color_internal=#00ffff
|
||||
color_external=#00ffff
|
||||
color_cross_scheme=#00ffff
|
||||
internal_prefix="\x2192 "
|
||||
color_external=#00ffff
|
||||
color_internal=#00ffff
|
||||
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:updates
|
||||
=> about:style-preview
|
||||
=> about:style-display
|
||||
=> about:cache
|
||||
|
||||
## 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
|
||||
* 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
|
||||
* Adds support for transient client certificates
|
||||
* Adds support for permanent client certificates
|
||||
|
@ -46,7 +50,7 @@
|
|||
* Implement Ctrl+S/*Save as...* menu item
|
||||
* Add display for "non-recognized files"
|
||||
* 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 file:// scheme
|
||||
* 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 <QStandardPaths>
|
||||
#include <QFontDatabase>
|
||||
#include <QLocalSocket>
|
||||
#include <QLocalServer>
|
||||
#include <cassert>
|
||||
|
||||
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[])
|
||||
{
|
||||
|
@ -123,12 +301,94 @@ int main(int argc, char *argv[])
|
|||
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);
|
||||
|
||||
|
@ -299,22 +559,23 @@ int main(int argc, char *argv[])
|
|||
MainWindow w(&app);
|
||||
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) {
|
||||
for(const auto &url_str : urls) {
|
||||
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()) {
|
||||
w.addNewTab(false, url);
|
||||
} else {
|
||||
qDebug() << "Invalid url: " << url_str;
|
||||
}
|
||||
for(const auto & url : urls) {
|
||||
w.addNewTab(false, url);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
Loading…
Reference in New Issue