Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add preliminary single-instance support for drop-down mode #1115

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -210,6 +213,7 @@ target_link_libraries(${EXE_NAME}
Qt6::Core
Qt6::Gui
Qt6::Widgets
Qt6::Network
qtermwidget6
util
)
Expand Down
168 changes: 168 additions & 0 deletions src/instance-locker.cpp
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>. *
***************************************************************************/

#include <unistd.h>

#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);
}
52 changes: 52 additions & 0 deletions src/instance-locker.h
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>. *
***************************************************************************/

#pragma once

#include <QObject>
#include <QString>
#include <QLockFile>
#include <QLocalServer>
#include <QLocalSocket>

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;
};
40 changes: 37 additions & 3 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
#include <QApplication>
#include <QtGlobal>

#include <QLockFile>
#include <QLocalServer>
#include <QLocalSocket>

#include <cassert>
#include <cstdio>
#include <getopt.h>
Expand All @@ -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

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -342,4 +377,3 @@ bool QTerminalApp::toggleDropdown() {


#endif

Loading