fcitx5-qt/qt5/platforminputcontext/fcitx4watcher.cpp

268 lines
6.6 KiB
C++

/*
* SPDX-FileCopyrightText: 2011~2023 CSSlayer <wengxt@gmail.com>
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include "fcitx4watcher.h"
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusServiceWatcher>
#include <QDebug>
#include <QDir>
#include <QFileSystemWatcher>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
// utils function in fcitx-utils and fcitx-config
bool _pid_exists(pid_t pid) {
if (pid <= 0)
return 0;
return !(kill(pid, 0) && (errno == ESRCH));
}
int displayNumber() {
QByteArray display(qgetenv("DISPLAY"));
QByteArray displayNumber("0");
int pos = display.indexOf(':');
if (pos >= 0) {
++pos;
int pos2 = display.indexOf('.', pos);
if (pos2 > 0) {
displayNumber = display.mid(pos, pos2 - pos);
} else {
displayNumber = display.mid(pos);
}
}
bool ok;
int d = displayNumber.toInt(&ok);
if (ok) {
return d;
}
return 0;
}
QString socketFile() {
QString filename =
QStringLiteral("%1-%2")
.arg(QString::fromLatin1(QDBusConnection::localMachineId()))
.arg(displayNumber());
QString home = QString::fromLocal8Bit(qgetenv("XDG_CONFIG_HOME"));
if (home.isEmpty()) {
home = QDir::homePath().append(QLatin1String("/.config"));
}
return QStringLiteral("%1/fcitx/dbus/%2").arg(home).arg(filename);
}
namespace fcitx {
QString newUniqueConnectionName() {
static int idx = 0;
const auto newIdx = idx++;
return QStringLiteral("_fcitx4_%1").arg(newIdx);
}
Fcitx4Watcher::Fcitx4Watcher(QDBusConnection sessionBus, QObject *parent)
: QObject(parent), connection_(nullptr), sessionBus_(sessionBus),
socketFile_(socketFile()),
serviceName_(QStringLiteral("org.fcitx.Fcitx-%1").arg(displayNumber())),
availability_(false), uniqueConnectionName_(newUniqueConnectionName()) {}
Fcitx4Watcher::~Fcitx4Watcher() {
cleanUpConnection();
unwatchSocketFile();
}
bool Fcitx4Watcher::availability() const { return availability_; }
QDBusConnection Fcitx4Watcher::connection() const {
if (connection_) {
return *connection_;
}
return sessionBus_;
}
QString Fcitx4Watcher::service() const {
if (connection_) {
return serviceName_;
}
if (mainPresent_) {
return serviceName_;
}
return QString();
}
void Fcitx4Watcher::setAvailability(bool availability) {
if (availability_ != availability) {
availability_ = availability;
Q_EMIT availabilityChanged(availability_);
}
}
void Fcitx4Watcher::watch() {
if (watched_) {
return;
}
serviceWatcher_ = new QDBusServiceWatcher(this);
connect(serviceWatcher_, &QDBusServiceWatcher::serviceOwnerChanged, this,
&Fcitx4Watcher::imChanged);
serviceWatcher_->setConnection(sessionBus_);
serviceWatcher_->addWatchedService(serviceName_);
if (sessionBus_.interface()->isServiceRegistered(serviceName_)) {
mainPresent_ = true;
}
watchSocketFile();
createConnection();
updateAvailability();
watched_ = true;
}
void Fcitx4Watcher::unwatch() {
if (!watched_) {
return;
}
delete serviceWatcher_;
serviceWatcher_ = nullptr;
unwatchSocketFile();
cleanUpConnection();
mainPresent_ = false;
watched_ = false;
updateAvailability();
}
QString Fcitx4Watcher::address() {
QString addr;
QByteArray addrVar = qgetenv("FCITX_DBUS_ADDRESS");
if (!addrVar.isNull())
return QString::fromLocal8Bit(addrVar);
QFile file(socketFile_);
if (!file.open(QIODevice::ReadOnly))
return QString();
const int BUFSIZE = 1024;
char buffer[BUFSIZE];
size_t sz = file.read(buffer, BUFSIZE);
file.close();
if (sz == 0)
return QString();
char *p = buffer;
while (*p)
p++;
size_t addrlen = p - buffer;
if (sz != addrlen + 2 * sizeof(pid_t) + 1)
return QString();
/* skip '\0' */
p++;
pid_t *ppid = (pid_t *)p;
pid_t daemonpid = ppid[0];
pid_t fcitxpid = ppid[1];
if (!_pid_exists(daemonpid) || !_pid_exists(fcitxpid))
return QString();
addr = QLatin1String(buffer);
return addr;
}
void Fcitx4Watcher::cleanUpConnection() {
QDBusConnection::disconnectFromBus(uniqueConnectionName_);
delete connection_;
connection_ = nullptr;
}
void Fcitx4Watcher::socketFileChanged() {
cleanUpConnection();
createConnection();
}
void Fcitx4Watcher::createConnection() {
QString addr = address();
if (!addr.isNull()) {
QDBusConnection connection(
QDBusConnection::connectToBus(addr, uniqueConnectionName_));
if (connection.isConnected()) {
connection_ = new QDBusConnection(connection);
} else {
QDBusConnection::disconnectFromBus(uniqueConnectionName_);
}
}
if (connection_) {
connection_->connect("org.freedesktop.DBus.Local",
"/org/freedesktop/DBus/Local",
"org.freedesktop.DBus.Local", "Disconnected", this,
SLOT(dbusDisconnected()));
unwatchSocketFile();
}
updateAvailability();
}
void Fcitx4Watcher::dbusDisconnected() {
cleanUpConnection();
watchSocketFile();
// Try recreation immediately to avoid race.
createConnection();
}
void Fcitx4Watcher::watchSocketFile() {
if (socketFile_.isEmpty()) {
return;
}
QFileInfo info(socketFile_);
QDir dir(info.path());
if (!dir.exists()) {
QDir rt(QDir::root());
rt.mkpath(info.path());
}
fsWatcher_ = new QFileSystemWatcher(this);
fsWatcher_->addPath(info.path());
if (info.exists()) {
fsWatcher_->addPath(info.filePath());
}
connect(fsWatcher_, &QFileSystemWatcher::fileChanged, this,
&Fcitx4Watcher::socketFileChanged);
connect(fsWatcher_, &QFileSystemWatcher::directoryChanged, this,
&Fcitx4Watcher::socketFileChanged);
}
void Fcitx4Watcher::unwatchSocketFile() {
if (fsWatcher_) {
fsWatcher_->disconnect(this);
fsWatcher_->deleteLater();
fsWatcher_ = nullptr;
}
}
void Fcitx4Watcher::imChanged(const QString &service, const QString &,
const QString &newOwner) {
if (service == serviceName_) {
if (!newOwner.isEmpty()) {
mainPresent_ = true;
} else {
mainPresent_ = false;
}
}
updateAvailability();
}
void Fcitx4Watcher::updateAvailability() {
setAvailability(mainPresent_ || connection_);
}
} // namespace fcitx