diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d57d3f6..f3ce2964 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ find_package(Qt6Core ${QT_MINIMUM_VERSION} REQUIRED) find_package(Qt6Gui ${QT_MINIMUM_VERSION} REQUIRED) find_package(Qt6LinguistTools ${QT_MINIMUM_VERSION} REQUIRED) find_package(Qt6Widgets ${QT_MINIMUM_VERSION} REQUIRED) +find_package(Qt6Network ${QT_MINIMUM_VERSION} REQUIRED) if(UNIX) find_package(Qt6DBus ${QT_MINIMUM_VERSION} REQUIRED) find_package(Qt6 COMPONENTS Core Core5Compat REQUIRED) @@ -74,6 +75,7 @@ set(EXE_NAME qterminal) set(QTERM_SRC src/main.cpp src/mainwindow.cpp + src/instance-locker.cpp src/tabbar.cpp src/tabwidget.cpp src/termwidget.cpp @@ -91,6 +93,7 @@ set(QTERM_SRC set(QTERM_MOC_SRC src/qterminalapp.h src/mainwindow.h + src/instance-locker.h src/tabbar.h src/tabwidget.h src/termwidget.h @@ -210,6 +213,7 @@ target_link_libraries(${EXE_NAME} Qt6::Core Qt6::Gui Qt6::Widgets + Qt6::Network qtermwidget6 util ) diff --git a/src/instance-locker.cpp b/src/instance-locker.cpp new file mode 100644 index 00000000..dd500acb --- /dev/null +++ b/src/instance-locker.cpp @@ -0,0 +1,168 @@ +/*************************************************************************** +* Copyright (C) 2024 by Marcus Britanicus * +* marcusbritanicus@gmail.com * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with this program. If not, see . * +***************************************************************************/ + +#include + +#include "instance-locker.h" + +static inline QString getSocketName(QString appId) +{ + /** Get the env-var XDG_RUNTIME_DIR */ + QString sockName = QString::fromUtf8(qgetenv("XDG_RUNTIME_DIR")); + + /** The env-var was not set. We will use /tmp/ */ + if (not sockName.length()) + { + sockName = QString::fromUtf8("/tmp/"); + } + + if (not sockName.endsWith(QString::fromUtf8("/"))) + { + sockName += QString::fromUtf8("/"); + } + + /** Append a random number */ + sockName += QString::fromUtf8("%1-Scoket-%2").arg(appId).arg(getuid()); + + return sockName; +} + + +InstanceLocker::InstanceLocker(QString appId, QObject *parent) : QObject(parent) +{ + mServer = nullptr; + + /* App ID */ + mAppId = appId; + + /** Get the env-var XDG_RUNTIME_DIR */ + mSocketName = getSocketName(mAppId); + + /* Lock File */ + lockFile = new QLockFile(mSocketName + QString::fromUtf8(".lock")); + + /* Try to lock the @lockFile, if it fails, then we're not the first instance */ + if (lockFile->tryLock()) + { + /* Local Server for communication */ + mServer = new QLocalServer(this); + + /* Start the server */ + bool res = mServer->listen(mSocketName); + + /* @res can't be false at the moment, because we're the first instance. */ + /* The only reason why @res is false, the socket file exists from a previous */ + /* crash. So delete it and try again. */ + if (not res && (mServer->serverError() == QAbstractSocket::AddressInUseError)) + { + QLocalServer::removeServer(mSocketName); + res = mServer->listen(mSocketName); + + if (!res) + { + qWarning("InstanceLocker: listen on local socket failed, %s", qPrintable(mServer->errorString())); + } + } + } +} + + +InstanceLocker::~InstanceLocker() +{ + disconnect(); + + delete lockFile; +} + + +bool InstanceLocker::isRunning() +{ + /* If we have the lock, we're the server */ + /* In other words, if we're not there, there is no server */ + if (lockFile->isLocked()) + { + return false; + } + + /* If we cannot get the lock then the server is running elsewhere */ + if (not lockFile->tryLock()) + { + return true; + } + + /* Be default, we'll assume that the server is running elsewhere */ + return true; +} + + +bool InstanceLocker::sendMessage(const QString& message, int timeout) +{ + if (not isRunning()) + { + return false; + } + + /* Preparing socket */ + QLocalSocket socket(this); + + /* Connecting to server */ + socket.connectToServer(mSocketName); + + /* Wait for ACK (500 ms) */ + if (not socket.waitForConnected(timeout)) + { + return false; + } + + /* Send the message to the server */ + socket.write(message.toUtf8()); + + /** Should finish writing in 500 ms */ + return socket.waitForBytesWritten(timeout); +} + + +void InstanceLocker::disconnect() +{ + if (mServer) + { + mServer->close(); + } + + lockFile->unlock(); +} + + +QString InstanceLocker::handleConnection() +{ + /* Preparing socket */ + QLocalSocket *socket = mServer->nextPendingConnection(); + + if (socket == nullptr) + { + return QString(); + } + + socket->waitForReadyRead(2000); + QByteArray msg = socket->readAll(); + + /** Close the connection */ + socket->close(); + + return QString::fromUtf8(msg); +} diff --git a/src/instance-locker.h b/src/instance-locker.h new file mode 100644 index 00000000..951d50a5 --- /dev/null +++ b/src/instance-locker.h @@ -0,0 +1,52 @@ +/*************************************************************************** +* Copyright (C) 2024 by Marcus Britanicus * +* marcusbritanicus@gmail.com * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with this program. If not, see . * +***************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +class InstanceLocker : public QObject { +Q_OBJECT + +public: +InstanceLocker(QString appId, QObject *parent = nullptr); +~InstanceLocker(); + +/** Check if another isntance is running */ +bool isRunning(); + +/** Send a message to the original instance */ +bool sendMessage(const QString& message, int timeout = 500); + +/** Disconnect from the server */ +void disconnect(); + +QString handleConnection(); + +QLockFile *lockFile = nullptr; +bool mLocked = false; + +QString mSocketName; +QString mAppId; + +QLocalServer *mServer; +}; diff --git a/src/main.cpp b/src/main.cpp index 8b456298..6360ce48 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,10 @@ #include #include +#include +#include +#include + #include #include #include @@ -29,11 +33,11 @@ #include "processadaptor.h" #endif - #include "mainwindow.h" #include "qterminalapp.h" #include "qterminalutils.h" #include "terminalconfig.h" +#include "instance-locker.h" #define out @@ -197,7 +201,38 @@ int main(int argc, char *argv[]) app->installTranslator(&translator); TerminalConfig initConfig = TerminalConfig(workdir, shell_command); - app->newWindow(dropMode, initConfig); + MainWindow *window = app->newWindow(dropMode, initConfig); + + /** Instance locker */ + if ( dropMode ) { + InstanceLocker *locker = new InstanceLocker( QString::fromUtf8( "%1-%2" ).arg( qApp->organizationName() ).arg( qApp->applicationName() ), nullptr ); + + /** Another isntance is running */ + if ( locker->isRunning() ) { + /** Ask the other instance to toggle the instance. */ + locker->sendMessage( QString::fromUtf8( "toggle" ) ); + + delete locker; + + return 0; + } + + QObject::connect( + locker->mServer, &QLocalServer::newConnection, [ = ] () { + QString msg = locker->handleConnection(); + if (msg == QString::fromUtf8( "toggle" )) + { + if ( window->isVisible() ) { + window->hide(); + } + + else { + window->show(); + } + } + } + ); + } int ret = app->exec(); delete Properties::Instance(); @@ -342,4 +377,3 @@ bool QTerminalApp::toggleDropdown() { #endif -