cf12defd28
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
1345 lines
39 KiB
C++
1345 lines
39 KiB
C++
/*
|
|
SPDX-FileCopyrightText: 2001-2003 Lubos Lunak <l.lunak@kde.org>
|
|
|
|
SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
// qDebug() can't be turned off in kdeinit
|
|
#if 0
|
|
#define KSTARTUPINFO_ALL_DEBUG
|
|
#ifdef __GNUC__
|
|
#warning Extra KStartupInfo debug messages enabled.
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef QT_NO_CAST_FROM_ASCII
|
|
#undef QT_NO_CAST_FROM_ASCII
|
|
#endif
|
|
|
|
#include "kstartupinfo.h"
|
|
#include "kwindowsystem_debug.h"
|
|
|
|
#include <QDateTime>
|
|
|
|
// need to resolve INT32(qglobal.h)<>INT32(Xlibint.h) conflict
|
|
#ifndef QT_CLEAN_NAMESPACE
|
|
#define QT_CLEAN_NAMESPACE
|
|
#endif
|
|
|
|
#include <QTimer>
|
|
#include <netwm.h>
|
|
#include <stdlib.h>
|
|
#include <sys/time.h>
|
|
#include <unistd.h>
|
|
|
|
#include <QCoreApplication>
|
|
#include <QDebug>
|
|
#include <QStandardPaths>
|
|
#include <X11/Xlib.h>
|
|
#include <fixx11h.h>
|
|
#include <kwindowsystem.h>
|
|
#include <kx11extras.h>
|
|
#include <kxmessages.h>
|
|
#include <private/qtx11extras_p.h>
|
|
#include <signal.h>
|
|
|
|
static const char NET_STARTUP_MSG[] = "_NET_STARTUP_INFO";
|
|
|
|
// DESKTOP_STARTUP_ID is used also in kinit/wrapper.c ,
|
|
// kdesu in both kdelibs and kdebase and who knows where else
|
|
static const char NET_STARTUP_ENV[] = "DESKTOP_STARTUP_ID";
|
|
|
|
static QByteArray s_startup_id;
|
|
|
|
static long get_num(const QString &item_P);
|
|
static QString get_str(const QString &item_P);
|
|
static QByteArray get_cstr(const QString &item_P);
|
|
static QStringList get_fields(const QString &txt_P);
|
|
static QString escape_str(const QString &str_P);
|
|
|
|
class Q_DECL_HIDDEN KStartupInfo::Data : public KStartupInfoData
|
|
{
|
|
public:
|
|
Data()
|
|
: age(0)
|
|
{
|
|
} // just because it's in a QMap
|
|
Data(const QString &txt_P)
|
|
: KStartupInfoData(txt_P)
|
|
, age(0)
|
|
{
|
|
}
|
|
unsigned int age;
|
|
};
|
|
|
|
struct Q_DECL_HIDDEN KStartupInfoId::Private {
|
|
Private()
|
|
: id("")
|
|
{
|
|
}
|
|
|
|
QString to_text() const;
|
|
|
|
QByteArray id; // id
|
|
};
|
|
|
|
struct Q_DECL_HIDDEN KStartupInfoData::Private {
|
|
Private()
|
|
: desktop(0)
|
|
, wmclass("")
|
|
, hostname("")
|
|
, silent(KStartupInfoData::Unknown)
|
|
, screen(-1)
|
|
, xinerama(-1)
|
|
{
|
|
}
|
|
|
|
QString to_text() const;
|
|
void remove_pid(pid_t pid);
|
|
|
|
QString bin;
|
|
QString name;
|
|
QString description;
|
|
QString icon;
|
|
int desktop;
|
|
QList<pid_t> pids;
|
|
QByteArray wmclass;
|
|
QByteArray hostname;
|
|
KStartupInfoData::TriState silent;
|
|
int screen;
|
|
int xinerama;
|
|
QString application_id;
|
|
};
|
|
|
|
class Q_DECL_HIDDEN KStartupInfo::Private
|
|
{
|
|
public:
|
|
// private slots
|
|
void startups_cleanup();
|
|
void startups_cleanup_no_age();
|
|
void got_message(const QString &msg);
|
|
void window_added(WId w);
|
|
void slot_window_added(WId w);
|
|
|
|
void init(int flags);
|
|
void got_startup_info(const QString &msg_P, bool update_only_P);
|
|
void got_remove_startup_info(const QString &msg_P);
|
|
void new_startup_info_internal(const KStartupInfoId &id_P, Data &data_P, bool update_only_P);
|
|
void removeAllStartupInfoInternal(const KStartupInfoId &id_P);
|
|
/**
|
|
* Emits the gotRemoveStartup signal and erases the @p it from the startups map.
|
|
* @returns Iterator to next item in the startups map.
|
|
**/
|
|
QMap<KStartupInfoId, Data>::iterator removeStartupInfoInternal(QMap<KStartupInfoId, Data>::iterator it);
|
|
void remove_startup_pids(const KStartupInfoId &id, const KStartupInfoData &data);
|
|
void remove_startup_pids(const KStartupInfoData &data);
|
|
startup_t check_startup_internal(WId w, KStartupInfoId *id, KStartupInfoData *data);
|
|
bool find_id(const QByteArray &id_P, KStartupInfoId *id_O, KStartupInfoData *data_O);
|
|
bool find_pid(pid_t pid_P, const QByteArray &hostname, KStartupInfoId *id_O, KStartupInfoData *data_O);
|
|
bool find_wclass(const QByteArray &res_name_P, const QByteArray &res_class_P, KStartupInfoId *id_O, KStartupInfoData *data_O);
|
|
void startups_cleanup_internal(bool age_P);
|
|
void clean_all_noncompliant();
|
|
static QString check_required_startup_fields(const QString &msg, const KStartupInfoData &data, int screen);
|
|
static void setWindowStartupId(WId w_P, const QByteArray &id_P);
|
|
|
|
KStartupInfo *q;
|
|
unsigned int timeout;
|
|
QMap<KStartupInfoId, KStartupInfo::Data> startups;
|
|
// contains silenced ASN's only if !AnnounceSilencedChanges
|
|
QMap<KStartupInfoId, KStartupInfo::Data> silent_startups;
|
|
// contains ASN's that had change: but no new: yet
|
|
QMap<KStartupInfoId, KStartupInfo::Data> uninited_startups;
|
|
KXMessages msgs;
|
|
QTimer *cleanup;
|
|
int flags;
|
|
|
|
Private(int flags_P, KStartupInfo *qq)
|
|
: q(qq)
|
|
, timeout(60)
|
|
, msgs(NET_STARTUP_MSG)
|
|
, cleanup(nullptr)
|
|
, flags(flags_P)
|
|
{
|
|
}
|
|
|
|
void createConnections()
|
|
{
|
|
// d == nullptr means "disabled"
|
|
if (!QX11Info::isPlatformX11() || !QX11Info::display()) {
|
|
return;
|
|
}
|
|
|
|
if (!(flags & DisableKWinModule)) {
|
|
QObject::connect(KX11Extras::self(), SIGNAL(windowAdded(WId)), q, SLOT(slot_window_added(WId)));
|
|
}
|
|
QObject::connect(&msgs, SIGNAL(gotMessage(QString)), q, SLOT(got_message(QString)));
|
|
cleanup = new QTimer(q);
|
|
QObject::connect(cleanup, SIGNAL(timeout()), q, SLOT(startups_cleanup()));
|
|
}
|
|
};
|
|
|
|
KStartupInfo::KStartupInfo(int flags_P, QObject *parent_P)
|
|
: QObject(parent_P)
|
|
, d(new Private(flags_P, this))
|
|
{
|
|
d->createConnections();
|
|
}
|
|
|
|
KStartupInfo::~KStartupInfo()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
void KStartupInfo::Private::got_message(const QString &msg_P)
|
|
{
|
|
// TODO do something with SCREEN= ?
|
|
// qCDebug(LOG_KWINDOWSYSTEM) << "got:" << msg_P;
|
|
QString msg = msg_P.trimmed();
|
|
if (msg.startsWith(QLatin1String("new:"))) { // must match length below
|
|
got_startup_info(msg.mid(4), false);
|
|
} else if (msg.startsWith(QLatin1String("change:"))) { // must match length below
|
|
got_startup_info(msg.mid(7), true);
|
|
} else if (msg.startsWith(QLatin1String("remove:"))) { // must match length below
|
|
got_remove_startup_info(msg.mid(7));
|
|
}
|
|
}
|
|
|
|
// if the application stops responding for a while, KWindowSystem may get
|
|
// the information about the already mapped window before KXMessages
|
|
// actually gets the info about the started application (depends
|
|
// on their order in the native x11 event filter)
|
|
// simply delay info from KWindowSystem a bit
|
|
// SELI???
|
|
namespace
|
|
{
|
|
class DelayedWindowEvent : public QEvent
|
|
{
|
|
public:
|
|
DelayedWindowEvent(WId w_P)
|
|
: QEvent(uniqueType())
|
|
, w(w_P)
|
|
{
|
|
}
|
|
Window w;
|
|
static Type uniqueType()
|
|
{
|
|
return Type(QEvent::User + 15);
|
|
}
|
|
};
|
|
}
|
|
|
|
void KStartupInfo::Private::slot_window_added(WId w_P)
|
|
{
|
|
qApp->postEvent(q, new DelayedWindowEvent(w_P));
|
|
}
|
|
|
|
void KStartupInfo::customEvent(QEvent *e_P)
|
|
{
|
|
if (e_P->type() == DelayedWindowEvent::uniqueType()) {
|
|
d->window_added(static_cast<DelayedWindowEvent *>(e_P)->w);
|
|
} else
|
|
QObject::customEvent(e_P);
|
|
}
|
|
|
|
void KStartupInfo::Private::window_added(WId w_P)
|
|
{
|
|
KStartupInfoId id;
|
|
KStartupInfoData data;
|
|
startup_t ret = check_startup_internal(w_P, &id, &data);
|
|
switch (ret) {
|
|
case Match:
|
|
// qCDebug(LOG_KWINDOWSYSTEM) << "new window match";
|
|
break;
|
|
case NoMatch:
|
|
break; // nothing
|
|
case CantDetect:
|
|
if (flags & CleanOnCantDetect) {
|
|
clean_all_noncompliant();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void KStartupInfo::Private::got_startup_info(const QString &msg_P, bool update_P)
|
|
{
|
|
KStartupInfoId id(msg_P);
|
|
if (id.isNull()) {
|
|
return;
|
|
}
|
|
KStartupInfo::Data data(msg_P);
|
|
new_startup_info_internal(id, data, update_P);
|
|
}
|
|
|
|
void KStartupInfo::Private::new_startup_info_internal(const KStartupInfoId &id_P, KStartupInfo::Data &data_P, bool update_P)
|
|
{
|
|
if (id_P.isNull()) {
|
|
return;
|
|
}
|
|
if (startups.contains(id_P)) {
|
|
// already reported, update
|
|
startups[id_P].update(data_P);
|
|
startups[id_P].age = 0; // CHECKME
|
|
// qCDebug(LOG_KWINDOWSYSTEM) << "updating";
|
|
if (startups[id_P].silent() == KStartupInfo::Data::Yes && !(flags & AnnounceSilenceChanges)) {
|
|
silent_startups[id_P] = startups[id_P];
|
|
startups.remove(id_P);
|
|
Q_EMIT q->gotRemoveStartup(id_P, silent_startups[id_P]);
|
|
return;
|
|
}
|
|
Q_EMIT q->gotStartupChange(id_P, startups[id_P]);
|
|
return;
|
|
}
|
|
if (silent_startups.contains(id_P)) {
|
|
// already reported, update
|
|
silent_startups[id_P].update(data_P);
|
|
silent_startups[id_P].age = 0; // CHECKME
|
|
// qCDebug(LOG_KWINDOWSYSTEM) << "updating silenced";
|
|
if (silent_startups[id_P].silent() != Data::Yes) {
|
|
startups[id_P] = silent_startups[id_P];
|
|
silent_startups.remove(id_P);
|
|
q->Q_EMIT gotNewStartup(id_P, startups[id_P]);
|
|
return;
|
|
}
|
|
Q_EMIT q->gotStartupChange(id_P, silent_startups[id_P]);
|
|
return;
|
|
}
|
|
if (uninited_startups.contains(id_P)) {
|
|
uninited_startups[id_P].update(data_P);
|
|
// qCDebug(LOG_KWINDOWSYSTEM) << "updating uninited";
|
|
if (!update_P) { // uninited finally got new:
|
|
startups[id_P] = uninited_startups[id_P];
|
|
uninited_startups.remove(id_P);
|
|
Q_EMIT q->gotNewStartup(id_P, startups[id_P]);
|
|
return;
|
|
}
|
|
// no change announce, it's still uninited
|
|
return;
|
|
}
|
|
if (update_P) { // change: without any new: first
|
|
// qCDebug(LOG_KWINDOWSYSTEM) << "adding uninited";
|
|
uninited_startups.insert(id_P, data_P);
|
|
} else if (data_P.silent() != Data::Yes || flags & AnnounceSilenceChanges) {
|
|
// qCDebug(LOG_KWINDOWSYSTEM) << "adding";
|
|
startups.insert(id_P, data_P);
|
|
Q_EMIT q->gotNewStartup(id_P, data_P);
|
|
} else { // new silenced, and silent shouldn't be announced
|
|
// qCDebug(LOG_KWINDOWSYSTEM) << "adding silent";
|
|
silent_startups.insert(id_P, data_P);
|
|
}
|
|
cleanup->start(1000); // 1 sec
|
|
}
|
|
|
|
void KStartupInfo::Private::got_remove_startup_info(const QString &msg_P)
|
|
{
|
|
KStartupInfoId id(msg_P);
|
|
KStartupInfoData data(msg_P);
|
|
if (!data.pids().isEmpty()) {
|
|
if (!id.isNull()) {
|
|
remove_startup_pids(id, data);
|
|
} else {
|
|
remove_startup_pids(data);
|
|
}
|
|
return;
|
|
}
|
|
removeAllStartupInfoInternal(id);
|
|
}
|
|
|
|
void KStartupInfo::Private::removeAllStartupInfoInternal(const KStartupInfoId &id_P)
|
|
{
|
|
auto it = startups.find(id_P);
|
|
if (it != startups.end()) {
|
|
// qCDebug(LOG_KWINDOWSYSTEM) << "removing";
|
|
Q_EMIT q->gotRemoveStartup(it.key(), it.value());
|
|
startups.erase(it);
|
|
return;
|
|
}
|
|
it = silent_startups.find(id_P);
|
|
if (it != silent_startups.end()) {
|
|
silent_startups.erase(it);
|
|
return;
|
|
}
|
|
it = uninited_startups.find(id_P);
|
|
if (it != uninited_startups.end()) {
|
|
uninited_startups.erase(it);
|
|
}
|
|
}
|
|
|
|
QMap<KStartupInfoId, KStartupInfo::Data>::iterator KStartupInfo::Private::removeStartupInfoInternal(QMap<KStartupInfoId, Data>::iterator it)
|
|
{
|
|
Q_EMIT q->gotRemoveStartup(it.key(), it.value());
|
|
return startups.erase(it);
|
|
}
|
|
|
|
void KStartupInfo::Private::remove_startup_pids(const KStartupInfoData &data_P)
|
|
{
|
|
// first find the matching info
|
|
for (QMap<KStartupInfoId, KStartupInfo::Data>::Iterator it = startups.begin(); it != startups.end(); ++it) {
|
|
if ((*it).hostname() != data_P.hostname()) {
|
|
continue;
|
|
}
|
|
if (!(*it).is_pid(data_P.pids().first())) {
|
|
continue; // not the matching info
|
|
}
|
|
remove_startup_pids(it.key(), data_P);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void KStartupInfo::Private::remove_startup_pids(const KStartupInfoId &id_P, const KStartupInfoData &data_P)
|
|
{
|
|
if (data_P.pids().isEmpty()) {
|
|
qFatal("data_P.pids().isEmpty()");
|
|
}
|
|
Data *data = nullptr;
|
|
if (startups.contains(id_P)) {
|
|
data = &startups[id_P];
|
|
} else if (silent_startups.contains(id_P)) {
|
|
data = &silent_startups[id_P];
|
|
} else if (uninited_startups.contains(id_P)) {
|
|
data = &uninited_startups[id_P];
|
|
} else {
|
|
return;
|
|
}
|
|
const auto pids = data_P.pids();
|
|
for (auto pid : pids) {
|
|
data->d->remove_pid(pid); // remove all pids from the info
|
|
}
|
|
if (data->pids().isEmpty()) { // all pids removed -> remove info
|
|
removeAllStartupInfoInternal(id_P);
|
|
}
|
|
}
|
|
|
|
bool KStartupInfo::sendStartup(const KStartupInfoId &id_P, const KStartupInfoData &data_P)
|
|
{
|
|
if (id_P.isNull()) {
|
|
return false;
|
|
}
|
|
return sendStartupXcb(QX11Info::connection(), QX11Info::appScreen(), id_P, data_P);
|
|
}
|
|
|
|
bool KStartupInfo::sendStartupXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P, const KStartupInfoData &data_P)
|
|
{
|
|
if (id_P.isNull()) {
|
|
return false;
|
|
}
|
|
QString msg = QStringLiteral("new: %1 %2").arg(id_P.d->to_text(), data_P.d->to_text());
|
|
msg = Private::check_required_startup_fields(msg, data_P, screen);
|
|
#ifdef KSTARTUPINFO_ALL_DEBUG
|
|
qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg;
|
|
#endif
|
|
return KXMessages::broadcastMessageX(conn, NET_STARTUP_MSG, msg, screen);
|
|
}
|
|
|
|
QString KStartupInfo::Private::check_required_startup_fields(const QString &msg, const KStartupInfoData &data_P, int screen)
|
|
{
|
|
QString ret = msg;
|
|
if (data_P.name().isEmpty()) {
|
|
// qWarning() << "NAME not specified in initial startup message";
|
|
QString name = data_P.bin();
|
|
if (name.isEmpty()) {
|
|
name = QStringLiteral("UNKNOWN");
|
|
}
|
|
ret += QStringLiteral(" NAME=\"%1\"").arg(escape_str(name));
|
|
}
|
|
if (data_P.screen() == -1) { // add automatically if needed
|
|
ret += QStringLiteral(" SCREEN=%1").arg(screen);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool KStartupInfo::sendChange(const KStartupInfoId &id_P, const KStartupInfoData &data_P)
|
|
{
|
|
if (id_P.isNull()) {
|
|
return false;
|
|
}
|
|
return sendChangeXcb(QX11Info::connection(), QX11Info::appScreen(), id_P, data_P);
|
|
}
|
|
|
|
bool KStartupInfo::sendChangeXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P, const KStartupInfoData &data_P)
|
|
{
|
|
if (id_P.isNull()) {
|
|
return false;
|
|
}
|
|
QString msg = QStringLiteral("change: %1 %2").arg(id_P.d->to_text(), data_P.d->to_text());
|
|
#ifdef KSTARTUPINFO_ALL_DEBUG
|
|
qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg;
|
|
#endif
|
|
return KXMessages::broadcastMessageX(conn, NET_STARTUP_MSG, msg, screen);
|
|
}
|
|
|
|
bool KStartupInfo::sendFinish(const KStartupInfoId &id_P)
|
|
{
|
|
if (id_P.isNull()) {
|
|
return false;
|
|
}
|
|
return sendFinishXcb(QX11Info::connection(), QX11Info::appScreen(), id_P);
|
|
}
|
|
|
|
bool KStartupInfo::sendFinishXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P)
|
|
{
|
|
if (id_P.isNull()) {
|
|
return false;
|
|
}
|
|
QString msg = QStringLiteral("remove: %1").arg(id_P.d->to_text());
|
|
#ifdef KSTARTUPINFO_ALL_DEBUG
|
|
qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg;
|
|
#endif
|
|
return KXMessages::broadcastMessageX(conn, NET_STARTUP_MSG, msg, screen);
|
|
}
|
|
|
|
bool KStartupInfo::sendFinish(const KStartupInfoId &id_P, const KStartupInfoData &data_P)
|
|
{
|
|
// if( id_P.isNull()) // id may be null, the pids and hostname matter then
|
|
// return false;
|
|
return sendFinishXcb(QX11Info::connection(), QX11Info::appScreen(), id_P, data_P);
|
|
}
|
|
|
|
bool KStartupInfo::sendFinishXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P, const KStartupInfoData &data_P)
|
|
{
|
|
// if( id_P.isNull()) // id may be null, the pids and hostname matter then
|
|
// return false;
|
|
QString msg = QStringLiteral("remove: %1 %2").arg(id_P.d->to_text(), data_P.d->to_text());
|
|
#ifdef KSTARTUPINFO_ALL_DEBUG
|
|
qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg;
|
|
#endif
|
|
return KXMessages::broadcastMessageX(conn, NET_STARTUP_MSG, msg, screen);
|
|
}
|
|
|
|
void KStartupInfo::appStarted()
|
|
{
|
|
QByteArray startupId = s_startup_id;
|
|
|
|
if (startupId.isEmpty()) {
|
|
startupId = QX11Info::nextStartupId();
|
|
}
|
|
|
|
appStarted(startupId);
|
|
setStartupId("0"); // reset the id, no longer valid (must use clearStartupId() to avoid infinite loop)
|
|
}
|
|
|
|
void KStartupInfo::appStarted(const QByteArray &startup_id)
|
|
{
|
|
KStartupInfoId id;
|
|
id.initId(startup_id);
|
|
if (id.isNull()) {
|
|
return;
|
|
}
|
|
if (QX11Info::isPlatformX11() && !qEnvironmentVariableIsEmpty("DISPLAY")) { // don't rely on QX11Info::display()
|
|
KStartupInfo::sendFinish(id);
|
|
}
|
|
}
|
|
|
|
void KStartupInfo::setStartupId(const QByteArray &startup_id)
|
|
{
|
|
if (startup_id == s_startup_id) {
|
|
return;
|
|
}
|
|
if (startup_id.isEmpty()) {
|
|
s_startup_id = "0";
|
|
} else {
|
|
s_startup_id = startup_id;
|
|
if (QX11Info::isPlatformX11()) {
|
|
KStartupInfoId id;
|
|
id.initId(startup_id);
|
|
long timestamp = id.timestamp();
|
|
if (timestamp != 0) {
|
|
if (QX11Info::appUserTime() == 0 || NET::timestampCompare(timestamp, QX11Info::appUserTime()) > 0) { // time > appUserTime
|
|
QX11Info::setAppUserTime(timestamp);
|
|
}
|
|
if (QX11Info::appTime() == 0 || NET::timestampCompare(timestamp, QX11Info::appTime()) > 0) { // time > appTime
|
|
QX11Info::setAppTime(timestamp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void KStartupInfo::setNewStartupId(QWindow *window, const QByteArray &startup_id)
|
|
{
|
|
Q_ASSERT(window);
|
|
setStartupId(startup_id);
|
|
bool activate = true;
|
|
if (window != nullptr && QX11Info::isPlatformX11()) {
|
|
if (!startup_id.isEmpty() && startup_id != "0") {
|
|
NETRootInfo i(QX11Info::connection(), NET::Supported);
|
|
if (i.isSupported(NET::WM2StartupId)) {
|
|
KStartupInfo::Private::setWindowStartupId(window->winId(), startup_id);
|
|
activate = false; // WM will take care of it
|
|
}
|
|
}
|
|
if (activate) {
|
|
KX11Extras::setOnDesktop(window->winId(), KX11Extras::currentDesktop());
|
|
// This is not very nice, but there's no way how to get any
|
|
// usable timestamp without ASN, so force activating the window.
|
|
// And even with ASN, it's not possible to get the timestamp here,
|
|
// so if the WM doesn't have support for ASN, it can't be used either.
|
|
KX11Extras::forceActiveWindow(window->winId());
|
|
}
|
|
}
|
|
}
|
|
|
|
KStartupInfo::startup_t KStartupInfo::checkStartup(WId w_P, KStartupInfoId &id_O, KStartupInfoData &data_O)
|
|
{
|
|
return d->check_startup_internal(w_P, &id_O, &data_O);
|
|
}
|
|
|
|
KStartupInfo::startup_t KStartupInfo::checkStartup(WId w_P, KStartupInfoId &id_O)
|
|
{
|
|
return d->check_startup_internal(w_P, &id_O, nullptr);
|
|
}
|
|
|
|
KStartupInfo::startup_t KStartupInfo::checkStartup(WId w_P, KStartupInfoData &data_O)
|
|
{
|
|
return d->check_startup_internal(w_P, nullptr, &data_O);
|
|
}
|
|
|
|
KStartupInfo::startup_t KStartupInfo::checkStartup(WId w_P)
|
|
{
|
|
return d->check_startup_internal(w_P, nullptr, nullptr);
|
|
}
|
|
|
|
KStartupInfo::startup_t KStartupInfo::Private::check_startup_internal(WId w_P, KStartupInfoId *id_O, KStartupInfoData *data_O)
|
|
{
|
|
if (startups.isEmpty()) {
|
|
return NoMatch; // no startups
|
|
}
|
|
// Strategy:
|
|
//
|
|
// Is this a compliant app ?
|
|
// - Yes - test for match
|
|
// - No - Is this a NET_WM compliant app ?
|
|
// - Yes - test for pid match
|
|
// - No - test for WM_CLASS match
|
|
qCDebug(LOG_KWINDOWSYSTEM) << "check_startup";
|
|
QByteArray id = windowStartupId(w_P);
|
|
if (!id.isNull()) {
|
|
if (id.isEmpty() || id == "0") { // means ignore this window
|
|
qCDebug(LOG_KWINDOWSYSTEM) << "ignore";
|
|
return NoMatch;
|
|
}
|
|
return find_id(id, id_O, data_O) ? Match : NoMatch;
|
|
}
|
|
if (!QX11Info::isPlatformX11()) {
|
|
qCDebug(LOG_KWINDOWSYSTEM) << "check_startup:cantdetect";
|
|
return CantDetect;
|
|
}
|
|
NETWinInfo info(QX11Info::connection(),
|
|
w_P,
|
|
QX11Info::appRootWindow(),
|
|
NET::WMWindowType | NET::WMPid | NET::WMState,
|
|
NET::WM2WindowClass | NET::WM2ClientMachine | NET::WM2TransientFor);
|
|
pid_t pid = info.pid();
|
|
if (pid > 0) {
|
|
QByteArray hostname = info.clientMachine();
|
|
if (!hostname.isEmpty() && find_pid(pid, hostname, id_O, data_O)) {
|
|
return Match;
|
|
}
|
|
// try XClass matching , this PID stuff sucks :(
|
|
}
|
|
if (find_wclass(info.windowClassName(), info.windowClassClass(), id_O, data_O)) {
|
|
return Match;
|
|
}
|
|
// ignore NET::Tool and other special window types, if they can't be matched
|
|
NET::WindowType type = info.windowType(NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask
|
|
| NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask);
|
|
if (type != NET::Normal && type != NET::Override && type != NET::Unknown && type != NET::Dialog && type != NET::Utility)
|
|
// && type != NET::Dock ) why did I put this here?
|
|
{
|
|
return NoMatch;
|
|
}
|
|
// lets see if this is a transient
|
|
xcb_window_t transient_for = info.transientFor();
|
|
if (transient_for != QX11Info::appRootWindow() && transient_for != XCB_WINDOW_NONE) {
|
|
return NoMatch;
|
|
}
|
|
qCDebug(LOG_KWINDOWSYSTEM) << "check_startup:cantdetect";
|
|
return CantDetect;
|
|
}
|
|
|
|
bool KStartupInfo::Private::find_id(const QByteArray &id_P, KStartupInfoId *id_O, KStartupInfoData *data_O)
|
|
{
|
|
// qCDebug(LOG_KWINDOWSYSTEM) << "find_id:" << id_P;
|
|
KStartupInfoId id;
|
|
id.initId(id_P);
|
|
if (startups.contains(id)) {
|
|
if (id_O != nullptr) {
|
|
*id_O = id;
|
|
}
|
|
if (data_O != nullptr) {
|
|
*data_O = startups[id];
|
|
}
|
|
// qCDebug(LOG_KWINDOWSYSTEM) << "check_startup_id:match";
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool KStartupInfo::Private::find_pid(pid_t pid_P, const QByteArray &hostname_P, KStartupInfoId *id_O, KStartupInfoData *data_O)
|
|
{
|
|
// qCDebug(LOG_KWINDOWSYSTEM) << "find_pid:" << pid_P;
|
|
for (QMap<KStartupInfoId, KStartupInfo::Data>::Iterator it = startups.begin(); it != startups.end(); ++it) {
|
|
if ((*it).is_pid(pid_P) && (*it).hostname() == hostname_P) {
|
|
// Found it !
|
|
if (id_O != nullptr) {
|
|
*id_O = it.key();
|
|
}
|
|
if (data_O != nullptr) {
|
|
*data_O = *it;
|
|
}
|
|
// non-compliant, remove on first match
|
|
removeStartupInfoInternal(it);
|
|
// qCDebug(LOG_KWINDOWSYSTEM) << "check_startup_pid:match";
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool KStartupInfo::Private::find_wclass(const QByteArray &_res_name, const QByteArray &_res_class, KStartupInfoId *id_O, KStartupInfoData *data_O)
|
|
{
|
|
QByteArray res_name = _res_name.toLower();
|
|
QByteArray res_class = _res_class.toLower();
|
|
// qCDebug(LOG_KWINDOWSYSTEM) << "find_wclass:" << res_name << ":" << res_class;
|
|
for (QMap<KStartupInfoId, Data>::Iterator it = startups.begin(); it != startups.end(); ++it) {
|
|
const QByteArray wmclass = (*it).findWMClass();
|
|
if (wmclass.toLower() == res_name || wmclass.toLower() == res_class) {
|
|
// Found it !
|
|
if (id_O != nullptr) {
|
|
*id_O = it.key();
|
|
}
|
|
if (data_O != nullptr) {
|
|
*data_O = *it;
|
|
}
|
|
// non-compliant, remove on first match
|
|
removeStartupInfoInternal(it);
|
|
// qCDebug(LOG_KWINDOWSYSTEM) << "check_startup_wclass:match";
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
QByteArray KStartupInfo::windowStartupId(WId w_P)
|
|
{
|
|
if (!QX11Info::isPlatformX11()) {
|
|
return QByteArray();
|
|
}
|
|
NETWinInfo info(QX11Info::connection(), w_P, QX11Info::appRootWindow(), NET::Properties(), NET::WM2StartupId | NET::WM2GroupLeader);
|
|
QByteArray ret = info.startupId();
|
|
if (ret.isEmpty() && info.groupLeader() != XCB_WINDOW_NONE) {
|
|
// retry with window group leader, as the spec says
|
|
NETWinInfo groupLeaderInfo(QX11Info::connection(), info.groupLeader(), QX11Info::appRootWindow(), NET::Properties(), NET::Properties2());
|
|
ret = groupLeaderInfo.startupId();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void KStartupInfo::Private::setWindowStartupId(WId w_P, const QByteArray &id_P)
|
|
{
|
|
if (!QX11Info::isPlatformX11()) {
|
|
return;
|
|
}
|
|
if (id_P.isNull()) {
|
|
return;
|
|
}
|
|
NETWinInfo info(QX11Info::connection(), w_P, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2());
|
|
info.setStartupId(id_P.constData());
|
|
}
|
|
|
|
void KStartupInfo::setTimeout(unsigned int secs_P)
|
|
{
|
|
d->timeout = secs_P;
|
|
// schedule removing entries that are older than the new timeout
|
|
QTimer::singleShot(0, this, SLOT(startups_cleanup_no_age()));
|
|
}
|
|
|
|
void KStartupInfo::Private::startups_cleanup_no_age()
|
|
{
|
|
startups_cleanup_internal(false);
|
|
}
|
|
|
|
void KStartupInfo::Private::startups_cleanup()
|
|
{
|
|
if (startups.isEmpty() && silent_startups.isEmpty() && uninited_startups.isEmpty()) {
|
|
cleanup->stop();
|
|
return;
|
|
}
|
|
startups_cleanup_internal(true);
|
|
}
|
|
|
|
void KStartupInfo::Private::startups_cleanup_internal(bool age_P)
|
|
{
|
|
auto checkCleanup = [this, age_P](QMap<KStartupInfoId, KStartupInfo::Data> &s, bool doEmit) {
|
|
auto it = s.begin();
|
|
while (it != s.end()) {
|
|
if (age_P) {
|
|
(*it).age++;
|
|
}
|
|
unsigned int tout = timeout;
|
|
if ((*it).silent() == KStartupInfo::Data::Yes) {
|
|
// give kdesu time to get a password
|
|
tout *= 20;
|
|
}
|
|
const QByteArray timeoutEnvVariable = qgetenv("KSTARTUPINFO_TIMEOUT");
|
|
if (!timeoutEnvVariable.isNull()) {
|
|
tout = timeoutEnvVariable.toUInt();
|
|
}
|
|
if ((*it).age >= tout) {
|
|
if (doEmit) {
|
|
Q_EMIT q->gotRemoveStartup(it.key(), it.value());
|
|
}
|
|
it = s.erase(it);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
};
|
|
checkCleanup(startups, true);
|
|
checkCleanup(silent_startups, false);
|
|
checkCleanup(uninited_startups, false);
|
|
}
|
|
|
|
void KStartupInfo::Private::clean_all_noncompliant()
|
|
{
|
|
for (QMap<KStartupInfoId, KStartupInfo::Data>::Iterator it = startups.begin(); it != startups.end();) {
|
|
if ((*it).WMClass() != "0") {
|
|
++it;
|
|
continue;
|
|
}
|
|
it = removeStartupInfoInternal(it);
|
|
}
|
|
}
|
|
|
|
QByteArray KStartupInfo::createNewStartupId()
|
|
{
|
|
quint32 timestamp = 0;
|
|
if (QX11Info::isPlatformX11()) {
|
|
timestamp = QX11Info::getTimestamp();
|
|
}
|
|
return KStartupInfo::createNewStartupIdForTimestamp(timestamp);
|
|
}
|
|
|
|
QByteArray KStartupInfo::createNewStartupIdForTimestamp(quint32 timestamp)
|
|
{
|
|
// Assign a unique id, use hostname+time+pid, that should be 200% unique.
|
|
// Also append the user timestamp (for focus stealing prevention).
|
|
struct timeval tm;
|
|
gettimeofday(&tm, nullptr);
|
|
char hostname[256];
|
|
hostname[0] = '\0';
|
|
if (!gethostname(hostname, 255)) {
|
|
hostname[sizeof(hostname) - 1] = '\0';
|
|
}
|
|
QByteArray id = QStringLiteral("%1;%2;%3;%4_TIME%5").arg(hostname).arg(tm.tv_sec).arg(tm.tv_usec).arg(getpid()).arg(timestamp).toUtf8();
|
|
// qCDebug(LOG_KWINDOWSYSTEM) << "creating: " << id << ":" << (qApp ? qAppName() : QString("unnamed app") /* e.g. kdeinit */);
|
|
return id;
|
|
}
|
|
|
|
const QByteArray &KStartupInfoId::id() const
|
|
{
|
|
return d->id;
|
|
}
|
|
|
|
QString KStartupInfoId::Private::to_text() const
|
|
{
|
|
return QStringLiteral(" ID=\"%1\" ").arg(escape_str(id));
|
|
}
|
|
|
|
KStartupInfoId::KStartupInfoId(const QString &txt_P)
|
|
: d(new Private)
|
|
{
|
|
const QStringList items = get_fields(txt_P);
|
|
for (QStringList::ConstIterator it = items.begin(); it != items.end(); ++it) {
|
|
if ((*it).startsWith(QLatin1String("ID="))) {
|
|
d->id = get_cstr(*it);
|
|
}
|
|
}
|
|
}
|
|
|
|
void KStartupInfoId::initId(const QByteArray &id_P)
|
|
{
|
|
if (!id_P.isEmpty()) {
|
|
d->id = id_P;
|
|
#ifdef KSTARTUPINFO_ALL_DEBUG
|
|
qCDebug(LOG_KWINDOWSYSTEM) << "using: " << d->id;
|
|
#endif
|
|
return;
|
|
}
|
|
const QByteArray startup_env = qgetenv(NET_STARTUP_ENV);
|
|
if (!startup_env.isEmpty()) {
|
|
// already has id
|
|
d->id = startup_env;
|
|
#ifdef KSTARTUPINFO_ALL_DEBUG
|
|
qCDebug(LOG_KWINDOWSYSTEM) << "reusing: " << d->id;
|
|
#endif
|
|
return;
|
|
}
|
|
d->id = KStartupInfo::createNewStartupId();
|
|
}
|
|
|
|
bool KStartupInfoId::setupStartupEnv() const
|
|
{
|
|
if (isNull()) {
|
|
qunsetenv(NET_STARTUP_ENV);
|
|
return false;
|
|
}
|
|
return !qputenv(NET_STARTUP_ENV, id()) == 0;
|
|
}
|
|
|
|
void KStartupInfo::resetStartupEnv()
|
|
{
|
|
qunsetenv(NET_STARTUP_ENV);
|
|
}
|
|
|
|
KStartupInfoId::KStartupInfoId()
|
|
: d(new Private)
|
|
{
|
|
}
|
|
|
|
KStartupInfoId::~KStartupInfoId()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
KStartupInfoId::KStartupInfoId(const KStartupInfoId &id_P)
|
|
: d(new Private(*id_P.d))
|
|
{
|
|
}
|
|
|
|
KStartupInfoId &KStartupInfoId::operator=(const KStartupInfoId &id_P)
|
|
{
|
|
if (&id_P == this) {
|
|
return *this;
|
|
}
|
|
*d = *id_P.d;
|
|
return *this;
|
|
}
|
|
|
|
bool KStartupInfoId::operator==(const KStartupInfoId &id_P) const
|
|
{
|
|
return id() == id_P.id();
|
|
}
|
|
|
|
bool KStartupInfoId::operator!=(const KStartupInfoId &id_P) const
|
|
{
|
|
return !(*this == id_P);
|
|
}
|
|
|
|
// needed for QMap
|
|
bool KStartupInfoId::operator<(const KStartupInfoId &id_P) const
|
|
{
|
|
return id() < id_P.id();
|
|
}
|
|
|
|
bool KStartupInfoId::isNull() const
|
|
{
|
|
return d->id.isEmpty() || d->id == "0";
|
|
}
|
|
|
|
unsigned long KStartupInfoId::timestamp() const
|
|
{
|
|
if (isNull()) {
|
|
return 0;
|
|
}
|
|
// As per the spec, the ID must contain the _TIME followed by the timestamp
|
|
int pos = d->id.lastIndexOf("_TIME");
|
|
if (pos >= 0) {
|
|
bool ok;
|
|
unsigned long time = QString(d->id.mid(pos + 5)).toULong(&ok);
|
|
if (!ok && d->id[pos + 5] == '-') { // try if it's as a negative signed number perhaps
|
|
time = QString(d->id.mid(pos + 5)).toLong(&ok);
|
|
}
|
|
if (ok) {
|
|
return time;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
QString KStartupInfoData::Private::to_text() const
|
|
{
|
|
QString ret;
|
|
// prepare some space which should be always enough.
|
|
// No need to squeze at the end, as the result is only used as intermediate string
|
|
ret.reserve(256);
|
|
if (!bin.isEmpty()) {
|
|
ret += QStringLiteral(" BIN=\"%1\"").arg(escape_str(bin));
|
|
}
|
|
if (!name.isEmpty()) {
|
|
ret += QStringLiteral(" NAME=\"%1\"").arg(escape_str(name));
|
|
}
|
|
if (!description.isEmpty()) {
|
|
ret += QStringLiteral(" DESCRIPTION=\"%1\"").arg(escape_str(description));
|
|
}
|
|
if (!icon.isEmpty()) {
|
|
ret += QStringLiteral(" ICON=\"%1\"").arg(icon);
|
|
}
|
|
if (desktop != 0) {
|
|
ret += QStringLiteral(" DESKTOP=%1").arg(desktop == NET::OnAllDesktops ? NET::OnAllDesktops : desktop - 1); // spec counts from 0
|
|
}
|
|
if (!wmclass.isEmpty()) {
|
|
ret += QStringLiteral(" WMCLASS=\"%1\"").arg(QString(wmclass));
|
|
}
|
|
if (!hostname.isEmpty()) {
|
|
ret += QStringLiteral(" HOSTNAME=%1").arg(QString(hostname));
|
|
}
|
|
for (QList<pid_t>::ConstIterator it = pids.begin(); it != pids.end(); ++it) {
|
|
ret += QStringLiteral(" PID=%1").arg(*it);
|
|
}
|
|
if (silent != KStartupInfoData::Unknown) {
|
|
ret += QStringLiteral(" SILENT=%1").arg(silent == KStartupInfoData::Yes ? 1 : 0);
|
|
}
|
|
if (screen != -1) {
|
|
ret += QStringLiteral(" SCREEN=%1").arg(screen);
|
|
}
|
|
if (xinerama != -1) {
|
|
ret += QStringLiteral(" XINERAMA=%1").arg(xinerama);
|
|
}
|
|
if (!application_id.isEmpty()) {
|
|
ret += QStringLiteral(" APPLICATION_ID=\"%1\"").arg(application_id);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
KStartupInfoData::KStartupInfoData(const QString &txt_P)
|
|
: d(new Private)
|
|
{
|
|
const QStringList items = get_fields(txt_P);
|
|
for (QStringList::ConstIterator it = items.begin(); it != items.end(); ++it) {
|
|
if ((*it).startsWith(QLatin1String("BIN="))) {
|
|
d->bin = get_str(*it);
|
|
} else if ((*it).startsWith(QLatin1String("NAME="))) {
|
|
d->name = get_str(*it);
|
|
} else if ((*it).startsWith(QLatin1String("DESCRIPTION="))) {
|
|
d->description = get_str(*it);
|
|
} else if ((*it).startsWith(QLatin1String("ICON="))) {
|
|
d->icon = get_str(*it);
|
|
} else if ((*it).startsWith(QLatin1String("DESKTOP="))) {
|
|
d->desktop = get_num(*it);
|
|
if (d->desktop != NET::OnAllDesktops) {
|
|
++d->desktop; // spec counts from 0
|
|
}
|
|
} else if ((*it).startsWith(QLatin1String("WMCLASS="))) {
|
|
d->wmclass = get_cstr(*it);
|
|
} else if ((*it).startsWith(QLatin1String("HOSTNAME="))) { // added to version 1 (2014)
|
|
d->hostname = get_cstr(*it);
|
|
} else if ((*it).startsWith(QLatin1String("PID="))) { // added to version 1 (2014)
|
|
addPid(get_num(*it));
|
|
} else if ((*it).startsWith(QLatin1String("SILENT="))) {
|
|
d->silent = get_num(*it) != 0 ? Yes : No;
|
|
} else if ((*it).startsWith(QLatin1String("SCREEN="))) {
|
|
d->screen = get_num(*it);
|
|
} else if ((*it).startsWith(QLatin1String("XINERAMA="))) {
|
|
d->xinerama = get_num(*it);
|
|
} else if ((*it).startsWith(QLatin1String("APPLICATION_ID="))) {
|
|
d->application_id = get_str(*it);
|
|
}
|
|
}
|
|
}
|
|
|
|
KStartupInfoData::KStartupInfoData(const KStartupInfoData &data)
|
|
: d(new Private(*data.d))
|
|
{
|
|
}
|
|
|
|
KStartupInfoData &KStartupInfoData::operator=(const KStartupInfoData &data)
|
|
{
|
|
if (&data == this) {
|
|
return *this;
|
|
}
|
|
*d = *data.d;
|
|
return *this;
|
|
}
|
|
|
|
void KStartupInfoData::update(const KStartupInfoData &data_P)
|
|
{
|
|
if (!data_P.bin().isEmpty()) {
|
|
d->bin = data_P.bin();
|
|
}
|
|
if (!data_P.name().isEmpty() && name().isEmpty()) { // don't overwrite
|
|
d->name = data_P.name();
|
|
}
|
|
if (!data_P.description().isEmpty() && description().isEmpty()) { // don't overwrite
|
|
d->description = data_P.description();
|
|
}
|
|
if (!data_P.icon().isEmpty() && icon().isEmpty()) { // don't overwrite
|
|
d->icon = data_P.icon();
|
|
}
|
|
if (data_P.desktop() != 0 && desktop() == 0) { // don't overwrite
|
|
d->desktop = data_P.desktop();
|
|
}
|
|
if (!data_P.d->wmclass.isEmpty()) {
|
|
d->wmclass = data_P.d->wmclass;
|
|
}
|
|
if (!data_P.d->hostname.isEmpty()) {
|
|
d->hostname = data_P.d->hostname;
|
|
}
|
|
for (QList<pid_t>::ConstIterator it = data_P.d->pids.constBegin(); it != data_P.d->pids.constEnd(); ++it) {
|
|
addPid(*it);
|
|
}
|
|
if (data_P.silent() != Unknown) {
|
|
d->silent = data_P.silent();
|
|
}
|
|
if (data_P.screen() != -1) {
|
|
d->screen = data_P.screen();
|
|
}
|
|
if (data_P.xinerama() != -1 && xinerama() != -1) { // don't overwrite
|
|
d->xinerama = data_P.xinerama();
|
|
}
|
|
if (!data_P.applicationId().isEmpty() && applicationId().isEmpty()) { // don't overwrite
|
|
d->application_id = data_P.applicationId();
|
|
}
|
|
}
|
|
|
|
KStartupInfoData::KStartupInfoData()
|
|
: d(new Private)
|
|
{
|
|
}
|
|
|
|
KStartupInfoData::~KStartupInfoData()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
void KStartupInfoData::setBin(const QString &bin_P)
|
|
{
|
|
d->bin = bin_P;
|
|
}
|
|
|
|
const QString &KStartupInfoData::bin() const
|
|
{
|
|
return d->bin;
|
|
}
|
|
|
|
void KStartupInfoData::setName(const QString &name_P)
|
|
{
|
|
d->name = name_P;
|
|
}
|
|
|
|
const QString &KStartupInfoData::name() const
|
|
{
|
|
return d->name;
|
|
}
|
|
|
|
const QString &KStartupInfoData::findName() const
|
|
{
|
|
if (!name().isEmpty()) {
|
|
return name();
|
|
}
|
|
return bin();
|
|
}
|
|
|
|
void KStartupInfoData::setDescription(const QString &desc_P)
|
|
{
|
|
d->description = desc_P;
|
|
}
|
|
|
|
const QString &KStartupInfoData::description() const
|
|
{
|
|
return d->description;
|
|
}
|
|
|
|
const QString &KStartupInfoData::findDescription() const
|
|
{
|
|
if (!description().isEmpty()) {
|
|
return description();
|
|
}
|
|
return name();
|
|
}
|
|
|
|
void KStartupInfoData::setIcon(const QString &icon_P)
|
|
{
|
|
d->icon = icon_P;
|
|
}
|
|
|
|
const QString &KStartupInfoData::findIcon() const
|
|
{
|
|
if (!icon().isEmpty()) {
|
|
return icon();
|
|
}
|
|
return bin();
|
|
}
|
|
|
|
const QString &KStartupInfoData::icon() const
|
|
{
|
|
return d->icon;
|
|
}
|
|
|
|
void KStartupInfoData::setDesktop(int desktop_P)
|
|
{
|
|
d->desktop = desktop_P;
|
|
}
|
|
|
|
int KStartupInfoData::desktop() const
|
|
{
|
|
return d->desktop;
|
|
}
|
|
|
|
void KStartupInfoData::setWMClass(const QByteArray &wmclass_P)
|
|
{
|
|
d->wmclass = wmclass_P;
|
|
}
|
|
|
|
const QByteArray KStartupInfoData::findWMClass() const
|
|
{
|
|
if (!WMClass().isEmpty() && WMClass() != "0") {
|
|
return WMClass();
|
|
}
|
|
return bin().toUtf8();
|
|
}
|
|
|
|
QByteArray KStartupInfoData::WMClass() const
|
|
{
|
|
return d->wmclass;
|
|
}
|
|
|
|
void KStartupInfoData::setHostname(const QByteArray &hostname_P)
|
|
{
|
|
if (!hostname_P.isNull()) {
|
|
d->hostname = hostname_P;
|
|
} else {
|
|
char tmp[256];
|
|
tmp[0] = '\0';
|
|
if (!gethostname(tmp, 255)) {
|
|
tmp[sizeof(tmp) - 1] = '\0';
|
|
}
|
|
d->hostname = tmp;
|
|
}
|
|
}
|
|
|
|
QByteArray KStartupInfoData::hostname() const
|
|
{
|
|
return d->hostname;
|
|
}
|
|
|
|
void KStartupInfoData::addPid(pid_t pid_P)
|
|
{
|
|
if (!d->pids.contains(pid_P)) {
|
|
d->pids.append(pid_P);
|
|
}
|
|
}
|
|
|
|
void KStartupInfoData::Private::remove_pid(pid_t pid_P)
|
|
{
|
|
pids.removeAll(pid_P);
|
|
}
|
|
|
|
QList<pid_t> KStartupInfoData::pids() const
|
|
{
|
|
return d->pids;
|
|
}
|
|
|
|
bool KStartupInfoData::is_pid(pid_t pid_P) const
|
|
{
|
|
return d->pids.contains(pid_P);
|
|
}
|
|
|
|
void KStartupInfoData::setSilent(TriState state_P)
|
|
{
|
|
d->silent = state_P;
|
|
}
|
|
|
|
KStartupInfoData::TriState KStartupInfoData::silent() const
|
|
{
|
|
return d->silent;
|
|
}
|
|
|
|
void KStartupInfoData::setScreen(int _screen)
|
|
{
|
|
d->screen = _screen;
|
|
}
|
|
|
|
int KStartupInfoData::screen() const
|
|
{
|
|
return d->screen;
|
|
}
|
|
|
|
void KStartupInfoData::setXinerama(int xinerama)
|
|
{
|
|
d->xinerama = xinerama;
|
|
}
|
|
|
|
int KStartupInfoData::xinerama() const
|
|
{
|
|
return d->xinerama;
|
|
}
|
|
|
|
void KStartupInfoData::setApplicationId(const QString &desktop)
|
|
{
|
|
if (desktop.startsWith(QLatin1Char('/'))) {
|
|
d->application_id = desktop;
|
|
return;
|
|
}
|
|
// the spec requires this is always a full path, in order for everyone to be able to find it
|
|
QString desk = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, desktop);
|
|
if (desk.isEmpty()) {
|
|
return;
|
|
}
|
|
d->application_id = desk;
|
|
}
|
|
|
|
QString KStartupInfoData::applicationId() const
|
|
{
|
|
return d->application_id;
|
|
}
|
|
|
|
static long get_num(const QString &item_P)
|
|
{
|
|
unsigned int pos = item_P.indexOf(QLatin1Char('='));
|
|
return item_P.mid(pos + 1).toLong();
|
|
}
|
|
|
|
static QString get_str(const QString &item_P)
|
|
{
|
|
int pos = item_P.indexOf(QLatin1Char('='));
|
|
return item_P.mid(pos + 1);
|
|
}
|
|
|
|
static QByteArray get_cstr(const QString &item_P)
|
|
{
|
|
return get_str(item_P).toUtf8();
|
|
}
|
|
|
|
static QStringList get_fields(const QString &txt_P)
|
|
{
|
|
QString txt = txt_P.simplified();
|
|
QStringList ret;
|
|
QString item;
|
|
bool in = false;
|
|
bool escape = false;
|
|
for (int pos = 0; pos < txt.length(); ++pos) {
|
|
if (escape) {
|
|
item += txt[pos];
|
|
escape = false;
|
|
} else if (txt[pos] == QLatin1Char('\\')) {
|
|
escape = true;
|
|
} else if (txt[pos] == QLatin1Char('\"')) {
|
|
in = !in;
|
|
} else if (txt[pos] == QLatin1Char(' ') && !in) {
|
|
ret.append(item);
|
|
item = QString();
|
|
} else {
|
|
item += txt[pos];
|
|
}
|
|
}
|
|
ret.append(item);
|
|
return ret;
|
|
}
|
|
|
|
static QString escape_str(const QString &str_P)
|
|
{
|
|
QString ret;
|
|
// prepare some space which should be always enough.
|
|
// No need to squeze at the end, as the result is only used as intermediate string
|
|
ret.reserve(str_P.size() * 2);
|
|
for (int pos = 0; pos < str_P.length(); ++pos) {
|
|
if (str_P[pos] == QLatin1Char('\\') || str_P[pos] == QLatin1Char('"')) {
|
|
ret += QLatin1Char('\\');
|
|
}
|
|
ret += str_P[pos];
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
#include "moc_kstartupinfo.cpp"
|