kdelibs/kdecore/kernel/kcmdlineargs.cpp
Ivailo Monev 4b632fcae6 kdeui: implement status notifier for any KApplication instance
optional feature that requires "-tray" command line argument and replaces
what the `ksystraycmd` program was doing. requires "X-KDE-SysTray" entry
in the desktop file as indicator that the application supports "-tray"
argument, unlike `ksystraycmd` does not spawn extra process and even
session management will work properly for it (the argument is manually
added to the restart command)

the feature is very much tide to KMainWindow (and derived classes) but the
overhead is next to none when the "-tray" argument is not specified (the
status notifier is not created in such case) however if created an
expensive tooltip update is done whenever a window changes (may have to
look into optimizing it but then again - most of the code does nothing
unless the "-tray" argument is specified)

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2023-08-24 01:41:02 +03:00

1561 lines
42 KiB
C++

/*
Copyright (C) 1999 Waldo Bastian <bastian@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kcmdlineargs.h"
#include "kdebug.h"
#include "kglobalsettings.h"
#include "kaboutdata.h"
#include "klocale.h"
#include "kdeversion.h"
#include "kcomponentdata.h"
#include "kglobal.h"
#include "kurl.h"
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QHash>
#include <QtCore/QTextCodec>
#include <sys/param.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <locale.h>
#include <limits.h>
// -----------------------------------------------------------------------------
// Design notes:
//
// These classes deal with a lot of text, some of which needs to be
// marked for translation. Since at the time when these object and calls are
// made the translation catalogs are usually still not initialized, the
// translation has to be delayed. This is achieved by using KLocalizedString
// for translatable strings. KLocalizedStrings are produced by ki18n* calls,
// instead of the more usuall i18n* calls which produce QString by trying to
// translate immediately.
//
// All the non-translatable string arguments to methods are taken QByteArray,
// all the translatable are KLocalizedString. The getter methods always return
// proper QString: the non-translatable strings supplied by the code are
// treated with QString::fromUtf8(), those coming from the outside with
// QTextCodec::toUnicode(), and translatable strings are finalized to QStrings
// at the point of getter calls (i.e. delayed translation).
//
// The code below uses locally defined s->decodeInput(QByteArray) and
// s->encodeOutput(QString) calls to centralize the conversion of raw external
// bytes (instead of QString::to/fromLocal8Bit(), QFile::decodeName, etc.)
// -----------------------------------------------------------------------------
#ifdef Q_WS_X11
#define DISPLAY "DISPLAY"
#else
#define DISPLAY "NODISPLAY"
#endif
//
// Helper classes
//
class KCmdLineParsedOptions : public QHash<QByteArray,QByteArray>
{
public:
KCmdLineParsedOptions() { }
};
class KCmdLineParsedArgs : public QList<QByteArray>
{
public:
KCmdLineParsedArgs() { }
};
class KCmdLineArgsList: public QList<KCmdLineArgs*>
{
public:
KCmdLineArgsList() { }
~KCmdLineArgsList() {
while (count())
delete takeFirst();
}
};
//
// KCmdLineOptions
//
class KCmdLineOptionsPrivate {
public:
QList<QByteArray> names;
QList<KLocalizedString> descriptions;
QStringList defaults;
};
KCmdLineOptions::KCmdLineOptions ()
: d(new KCmdLineOptionsPrivate)
{}
KCmdLineOptions::~KCmdLineOptions ()
{
delete d;
}
KCmdLineOptions::KCmdLineOptions (const KCmdLineOptions &options)
: d(new KCmdLineOptionsPrivate(*(options.d)))
{
}
KCmdLineOptions& KCmdLineOptions::operator= (const KCmdLineOptions &options)
{
if (this != &options) {
*d = *(options.d);
}
return *this;
}
KCmdLineOptions &KCmdLineOptions::add (const QByteArray &name,
const KLocalizedString &description,
const QByteArray &defaultValue)
{
d->names.append(name);
d->descriptions.append(description);
d->defaults.append(QString::fromUtf8(defaultValue));
return *this;
}
KCmdLineOptions &KCmdLineOptions::add (const KCmdLineOptions &other)
{
d->names += other.d->names;
d->descriptions += other.d->descriptions;
d->defaults += other.d->defaults;
return *this;
}
//
// KCmdLineArgs static data and methods
//
class KCmdLineArgsStatic {
public:
KCmdLineArgsList *argsList; // All options.
const KAboutData *about;
int all_argc; // The original argc
char **all_argv; // The original argv
char *appName;
bool parsed : 1; // Whether we have parsed the arguments since calling init
bool ignoreUnknown : 1; // Ignore unknown options and arguments
QByteArray mCwd; // Current working directory. Important for KUnqiueApp!
KCmdLineArgs::StdCmdLineArgs mStdargs;
KCmdLineOptions qt_options;
KCmdLineOptions kde_options;
KCmdLineArgsStatic ();
~KCmdLineArgsStatic ();
QTextCodec *codec; // codec for converting raw input to QString
/**
* @internal
* Convertes raw command line argument data to proper QString.
*
* @param rawstr raw text
* @return properly decoded QString
*/
static QString decodeInput(const QByteArray &rawstr);
/**
* @internal
* Convertes QString to raw command line output.
*
* @param str string to be encoded
* @return raw text
*/
static QByteArray encodeOutput(const QString &str);
/**
* @internal
* Shell output with proper decoding.
*/
void printQ(const QString &msg);
/**
* @internal
* Try to match given option in the list of options.
* Returns match status.
*
* @return:
* 0 - option not found.
* 1 - option found // -fork
* 2 - inverse option found ('no') // -nofork
* 3 - option + arg found // -fork now
*
* +4 - no more options follow // !fork
*/
static int findOption(const KCmdLineOptions &options, QByteArray &opt,
QByteArray &opt_name, QString &def, bool &enabled);
/**
* @internal
*
* Checks what to do with a single option
*/
static void findOption(const QByteArray &optv, const QByteArray &_opt,
int &i, bool _enabled, bool &moreOptions);
/**
* @internal
*
* Parse all arguments, verify correct syntax and put all arguments
* where they belong.
*/
static void parseAllArgs();
/**
* @internal
*
* Remove named options.
*
* @param id The name of the options to be removed.
*/
static void removeArgs(const QByteArray &id);
};
K_GLOBAL_STATIC(KCmdLineArgsStatic, s)
KCmdLineArgsStatic::KCmdLineArgsStatic () {
// Global data
argsList = 0;
all_argc = 0;
all_argv = 0;
appName = 0;
mCwd.clear();
about = 0;
parsed = false;
ignoreUnknown = false;
mStdargs = 0;
// Text codec.
codec = QTextCodec::codecForLocale();
// Qt options
//FIXME: Check if other options are specific to Qt/X11
#ifdef Q_WS_X11
qt_options.add("display <displayname>", ki18n("Use the X-server display 'displayname'"));
#endif
qt_options.add("session <sessionId>", ki18n("Restore the application for the given 'sessionId'"));
qt_options.add("nograb", ki18n("tells Katie to never grab the mouse or the keyboard"));
qt_options.add("dograb", ki18n("running under a debugger can cause an implicit\n-nograb, use -dograb to override"));
qt_options.add("sync", ki18n("switches to synchronous mode for debugging"));
qt_options.add("title <title>", ki18n("sets the application title (caption)"));
qt_options.add("reverse", ki18n("mirrors the whole layout of widgets"));
qt_options.add("stylesheet <file.qss>", ki18n("applies the Katie stylesheet to the application widgets"));
qt_options.add("style <style>", ki18n("sets the application GUI style"));
// KDE options
kde_options.add("caption <caption>", ki18n("Use 'caption' as name in the titlebar"));
kde_options.add("icon <icon>", ki18n("Use 'icon' as the application icon"));
kde_options.add("config <filename>", ki18n("Use alternative configuration file"));
kde_options.add("nocrashhandler", ki18n("Disable crash handler, to get core dumps"));
#ifdef Q_WS_X11
kde_options.add("waitforwm", ki18n("Waits for a WM_NET compatible windowmanager"));
#endif
kde_options.add("tray", ki18n("Show tray icon"));
kde_options.add("geometry <geometry>", ki18n("sets the client geometry of the main widget - see man X for the argument format (usually WidthxHeight+XPos+YPos)"));
kde_options.add("smkey <sessionKey>"); // this option is obsolete and exists only to allow smooth upgrades from sessions
}
KCmdLineArgsStatic::~KCmdLineArgsStatic ()
{
delete argsList;
// KAboutData object is deleted by ~KCleanUpGlobalStatic.
//delete about;
}
//
// KCmdLineArgs private data and methods
//
class KCmdLineArgsPrivate
{
friend class KCmdLineArgsStatic;
public:
KCmdLineArgsPrivate(const KCmdLineOptions &_options, const KLocalizedString &_name, const QByteArray &_id)
: options(_options)
, name(_name)
, id(_id)
, parsedOptionList(0)
, parsedArgList(0)
, isQt(id == "qt")
{
}
~KCmdLineArgsPrivate()
{
delete parsedOptionList;
delete parsedArgList;
}
const KCmdLineOptions options;
const KLocalizedString name;
const QByteArray id;
KCmdLineParsedOptions *parsedOptionList;
KCmdLineParsedArgs *parsedArgList;
bool isQt;
/**
* @internal
*
* Set a boolean option
*/
void setOption(const QByteArray &option, bool enabled);
/**
* @internal
*
* Set a string option
*/
void setOption(const QByteArray &option, const QByteArray &value);
/**
* @internal
*
* Add an argument
*/
void addArgument(const QByteArray &argument);
/**
* @internal
*
* Save to a stream.
*/
void save( QDataStream &) const;
/**
* @internal
*
* Restore from a stream.
*/
void load( QDataStream &);
};
//
// Static functions
//
QString
KCmdLineArgsStatic::decodeInput(const QByteArray &rawstr)
{
return s->codec->toUnicode(rawstr);
}
QByteArray
KCmdLineArgsStatic::encodeOutput(const QString &str)
{
return s->codec->fromUnicode(str);
}
void
KCmdLineArgsStatic::printQ(const QString &msg)
{
fprintf(stdout, "%s", encodeOutput(msg).data());
}
void
KCmdLineArgs::init(int _argc, char **_argv,
const QByteArray &_appname,
const QByteArray &_catalog,
const KLocalizedString &_programName,
const QByteArray &_version,
const KLocalizedString &_description,
StdCmdLineArgs stdargs)
{
init(_argc, _argv,
new KAboutData(_appname, _catalog, _programName, _version, _description),
stdargs);
}
void
KCmdLineArgs::initIgnore(int _argc, char **_argv, const QByteArray &_appname )
{
init(_argc, _argv,
new KAboutData(_appname, 0, ki18n(_appname), "unknown", ki18n("KDE Application")));
s->ignoreUnknown = true;
}
void
KCmdLineArgs::init(const KAboutData* ab)
{
char **_argv = (char **) malloc(sizeof(char *));
_argv[0] = (char *) s->encodeOutput(ab->appName()).data();
init(1,_argv,ab, CmdLineArgNone);
}
void
KCmdLineArgs::init(int _argc, char **_argv, const KAboutData *_about, StdCmdLineArgs stdargs)
{
s->all_argc = _argc;
s->all_argv = _argv;
if (!s->all_argv)
{
fprintf(stderr, "\n\nFAILURE (KCmdLineArgs):\n");
fprintf(stderr, "Passing null-pointer to 'argv' is not allowed.\n\n");
assert( 0 );
exit(255);
}
// Strip path from argv[0]
if (s->all_argc) {
char *p = strrchr(s->all_argv[0], QDir::separator().toLatin1());
if (p)
s->appName = p+1;
else
s->appName = s->all_argv[0];
}
s->about = _about;
s->parsed = false;
s->mCwd = QDir::currentPath().toLocal8Bit(); //currentPath() uses fromLocal8Bit internally apparently
addStdCmdLineOptions(stdargs);
}
QString KCmdLineArgs::cwd()
{
return QString::fromLocal8Bit(s->mCwd);
}
QString KCmdLineArgs::appName()
{
if (!s->appName) return QString();
return s->decodeInput(s->appName);
}
/**
* Add Qt and KDE command line options to KCmdLineArgs.
*/
void KCmdLineArgs::addStdCmdLineOptions(StdCmdLineArgs stdargs) {
if (stdargs & KCmdLineArgs::CmdLineArgQt) {
KCmdLineArgs::addCmdLineOptions(s->qt_options, ki18n("Katie"), "qt");
}
if (stdargs & KCmdLineArgs::CmdLineArgKDE) {
KCmdLineArgs::addCmdLineOptions(s->kde_options, ki18n("KDE"), "kde");
}
s->mStdargs = stdargs;
}
void
KCmdLineArgs::addCmdLineOptions( const KCmdLineOptions &options, const KLocalizedString &name,
const QByteArray &id, const QByteArray &afterId)
{
if (!s->argsList)
s->argsList = new KCmdLineArgsList;
int pos = s->argsList->count();
// To make sure that the named options come before unnamed.
if (pos > 0 && !id.isEmpty() && s->argsList->last()->d->name.isEmpty())
pos--;
KCmdLineArgsList::Iterator args;
int i = 0;
for(args = s->argsList->begin(); args != s->argsList->end(); ++args, i++)
{
if (id == (*args)->d->id) {
return; // Options already present.
}
// Only check for afterId if it has been given non-empty, as the
// unnamed option group should come after all named groups.
if (!afterId.isEmpty() && afterId == (*args)->d->id)
pos = i+1;
}
Q_ASSERT( s->parsed == false ); // You must add _ALL_ cmd line options
// before accessing the arguments!
s->argsList->insert(pos, new KCmdLineArgs(options, name, id));
}
void
KCmdLineArgs::saveAppArgs( QDataStream &ds)
{
if (!s->parsed)
s->parseAllArgs();
// Remove Qt and KDE options.
s->removeArgs("qt");
s->removeArgs("kde");
ds << s->mCwd;
uint count = s->argsList ? s->argsList->count() : 0;
ds << count;
if (!count) return;
KCmdLineArgsList::Iterator args;
for(args = s->argsList->begin(); args != s->argsList->end(); ++args)
{
ds << (*args)->d->id;
(*args)->d->save(ds);
}
}
void
KCmdLineArgs::loadAppArgs( QDataStream &ds)
{
s->parsed = true; // don't reparse argc/argv!
// Remove Katie and KDE options.
s->removeArgs("qt");
s->removeArgs("kde");
KCmdLineArgsList::Iterator args;
if ( s->argsList ) {
for(args = s->argsList->begin(); args != s->argsList->end(); ++args)
{
(*args)->clear();
}
}
if (ds.atEnd())
return;
QByteArray qCwd;
ds >> qCwd;
s->mCwd = qCwd;
uint count;
ds >> count;
while(count--)
{
QByteArray id;
ds >> id;
Q_ASSERT( s->argsList );
bool found = false;
for(args = s->argsList->begin(); args != s->argsList->end(); ++args)
{
if ((*args)->d->id == id)
{
(*args)->d->load(ds);
found = true;
break;
}
}
if (!found) {
kWarning() << "Argument definitions for" << id << "not found!";
// The next ds >> id will do nonsensical things...
}
}
s->parsed = true;
}
KCmdLineArgs *KCmdLineArgs::parsedArgs(const QByteArray &id)
{
if (!s->argsList)
return 0;
KCmdLineArgsList::Iterator args = s->argsList->begin();
while(args != s->argsList->end())
{
if ((*args)->d->id == id)
{
if (!s->parsed)
s->parseAllArgs();
return *args;
}
++args;
}
return 0;
}
void KCmdLineArgsStatic::removeArgs(const QByteArray &id)
{
if (!s->argsList)
return;
KCmdLineArgsList::Iterator args = s->argsList->begin();
while(args != s->argsList->end())
{
if ((*args)->d->id == id)
{
if (!s->parsed)
s->parseAllArgs();
break;
}
++args;
}
if (args != s->argsList->end()) {
KCmdLineArgs *a = *args;
s->argsList->erase(args);
delete a;
}
}
int
KCmdLineArgsStatic::findOption(const KCmdLineOptions &options, QByteArray &opt,
QByteArray &opt_name, QString &def, bool &enabled)
{
for (int i = 0; i < options.d->names.size(); i++)
{
int result = 0;
bool inverse = false;
opt_name = options.d->names[i];
if (opt_name.startsWith(':') || opt_name.isEmpty())
{
continue;
}
if (opt_name.startsWith('!'))
{
opt_name = opt_name.mid(1);
result = 4;
}
if (opt_name.startsWith("no") && !opt_name.contains('<')) // krazy:exclude=strings
{
opt_name = opt_name.mid(2);
inverse = true;
}
int len = opt.length();
if (opt == opt_name.left(len))
{
opt_name = opt_name.mid(len);
if (opt_name.isEmpty())
{
if (inverse)
return result+2;
if (options.d->descriptions[i].isEmpty())
{
i++;
if (i >= options.d->names.size())
return result+0;
QByteArray nextOption = options.d->names[i];
int p = nextOption.indexOf(' ');
if (p > 0)
nextOption = nextOption.left(p);
if (nextOption.startsWith('!'))
nextOption = nextOption.mid(1);
if (nextOption.startsWith("no") && !nextOption.contains('<')) // krazy:exclude=strings
{
nextOption = nextOption.mid(2);
enabled = !enabled;
}
result = findOption(options, nextOption, opt_name, def, enabled);
Q_ASSERT(result);
opt = nextOption;
return result;
}
return 1;
}
if (opt_name.startsWith(' '))
{
opt_name = opt_name.mid(1);
def = options.d->defaults[i];
return result+3;
}
}
}
return 0;
}
void
KCmdLineArgsStatic::findOption(const QByteArray &optv, const QByteArray &_opt,
int &i, bool _enabled, bool &moreOptions)
{
KCmdLineArgsList::Iterator args = s->argsList->begin();
QByteArray opt = _opt;
QByteArray opt_name;
QString def;
QByteArray argument;
int j = opt.indexOf('=');
if (j != -1)
{
argument = opt.mid(j+1);
opt = opt.left(j);
}
bool enabled = true;
int result = 0;
while (args != s->argsList->end())
{
enabled = _enabled;
result = findOption((*args)->d->options, opt, opt_name, def, enabled);
if (result) break;
++args;
}
if ((args == s->argsList->end()) &&
(optv.startsWith('-') && !optv.startsWith("--"))) // krazy:exclude=strings
{
// Option not found check if it is a valid option
// in the style of -Pprinter1 or ps -aux
int p = 1;
while (true)
{
QByteArray singleCharOption = " "; // krazy:exclude=doublequote_chars
singleCharOption[0] = optv[p];
args = s->argsList->begin();
while (args != s->argsList->end())
{
enabled = _enabled;
result = findOption((*args)->d->options, singleCharOption,
opt_name, def, enabled);
if (result) break;
++args;
}
if (args == s->argsList->end())
break; // Unknown argument
p++;
if (result == 1) // Single option
{
(*args)->d->setOption(singleCharOption, enabled);
if (p < optv.length())
continue; // Next option
else
return; // Finished
}
else if (result == 3) // This option takes an argument
{
if (argument.isEmpty())
{
argument = optv.mid(p);
}
(*args)->d->setOption(singleCharOption, argument);
return;
}
break; // Unknown argument
}
args = s->argsList->end();
result = 0;
}
if (args == s->argsList->end() || !result)
{
if (s->ignoreUnknown)
return;
KCmdLineArgs::enable_i18n();
KCmdLineArgs::usageError( i18n("Unknown option '%1'.", QString::fromLocal8Bit(_opt)));
}
if ((result & 4) != 0)
{
result &= ~4;
moreOptions = false;
}
if (result == 3) // This option takes an argument
{
if (!enabled)
{
if (s->ignoreUnknown)
return;
KCmdLineArgs::enable_i18n();
KCmdLineArgs::usageError( i18n("Unknown option '%1'.", QString::fromLocal8Bit(_opt)));
}
if (argument.isEmpty())
{
i++;
if (i >= s->all_argc)
{
KCmdLineArgs::enable_i18n();
KCmdLineArgs::usageError( i18nc("@info:shell %1 is cmdoption name","'%1' missing.", QString::fromLocal8Bit(opt_name)));
}
argument = s->all_argv[i];
}
(*args)->d->setOption(opt, argument);
}
else
{
(*args)->d->setOption(opt, enabled);
}
}
void
KCmdLineArgsStatic::parseAllArgs()
{
bool allowArgs = false;
bool inOptions = true;
bool everythingAfterArgIsArgs = false;
KCmdLineArgs *appOptions = s->argsList->last();
if (appOptions->d->id.isEmpty())
{
foreach(const QByteArray& name, appOptions->d->options.d->names)
{
everythingAfterArgIsArgs = everythingAfterArgIsArgs || name.startsWith("!+");
allowArgs = allowArgs || name.startsWith('+') || everythingAfterArgIsArgs;
}
}
for(int i = 1; i < s->all_argc; i++)
{
if (!s->all_argv[i])
continue;
if ((s->all_argv[i][0] == '-') && s->all_argv[i][1] && inOptions)
{
bool enabled = true;
QByteArray orig = s->all_argv[i];
QByteArray option = orig.mid(1);
if (option.startsWith('-'))
{
option = option.mid(1);
if (option.isEmpty())
{
inOptions = false;
continue;
}
}
if (option == "help")
{
KCmdLineArgs::usage();
}
else if (option.startsWith("help-")) // krazy:exclude=strings
{
KCmdLineArgs::usage(option.mid(5));
}
else if ((option == "version") || (option == "v"))
{
KCmdLineArgs::enable_i18n();
s->printQ(i18nc("@info:shell message on appcmd --version; do not translate 'Development Platform'"
"%3 application name, other %n version strings",
"Katie: %1\n"
"Katana Development Platform: %2\n"
"%3: %4\n",
QString::fromLatin1(qVersion()),
QString::fromLatin1(KDE_VERSION_STRING),
s->about->programName(), s->about->version()));
exit(0);
} else if (option == "license")
{
KCmdLineArgs::enable_i18n();
s->printQ(s->about->license());
s->printQ(QString::fromLatin1("\n"));
exit(0);
} else if (option == "author") {
KCmdLineArgs::enable_i18n();
if ( s->about ) {
const QList<KAboutPerson> authors = s->about->authors();
if ( !authors.isEmpty() ) {
QString authorlist;
for (QList<KAboutPerson>::ConstIterator it = authors.begin(); it != authors.end(); ++it ) {
QString email;
if ( !(*it).emailAddress().isEmpty() )
email = QString::fromLatin1(" <") + (*it).emailAddress() + QLatin1String(">");
authorlist += QString::fromLatin1(" ") + (*it).name() + email + QLatin1Char('\n');
}
s->printQ( i18nc("the 2nd argument is a list of name+address, one on each line","%1 was written by\n%2", QString(s->about->programName()) , authorlist ) );
}
} else {
s->printQ( i18n("This application was written by somebody who wants to remain anonymous.") );
}
if (s->about)
{
if (s->about->bugAddress().isEmpty() || s->about->bugAddress() == QLatin1String(KDE_BUG_REPORT_EMAIL))
s->printQ( i18n( "Please use %1.\n", QLatin1String(KDE_BUG_REPORT_URL) ) );
}
exit(0);
} else {
if (option.startsWith("no")) // krazy:exclude=strings
{
bool noHasParameter=false;
foreach(const QByteArray& name, appOptions->d->options.d->names)
{
if (name.contains(option + QByteArray(" ")) && name.contains('<'))
{
noHasParameter=true;
break;
}
}
if (!noHasParameter)
{
option = option.mid(2);
enabled = false;
}
}
s->findOption(orig, option, i, enabled, inOptions);
}
}
else
{
// Check whether appOptions allows these arguments
if (!allowArgs)
{
if (s->ignoreUnknown)
continue;
KCmdLineArgs::enable_i18n();
KCmdLineArgs::usageError(i18n("Unexpected argument '%1'.", Qt::escape(s->decodeInput(s->all_argv[i]))));
}
else
{
appOptions->d->addArgument(s->all_argv[i]);
if (everythingAfterArgIsArgs)
inOptions = false;
}
}
}
s->parsed = true;
}
int & KCmdLineArgs::qtArgc()
{
if (!s->argsList)
addStdCmdLineOptions(CmdLineArgKDE|CmdLineArgQt); // Lazy bastards!
static int qt_argc = -1;
if( qt_argc != -1 )
return qt_argc;
if (!(s->mStdargs & KCmdLineArgs::CmdLineArgQt))
{
qt_argc = 2;
return qt_argc;
}
KCmdLineArgs *args = parsedArgs("qt");
Q_ASSERT(args); // No qt options have been added!
if (!s->all_argv)
{
fprintf(stderr, "\n\nFAILURE (KCmdLineArgs):\n");
fprintf(stderr, "Application has not called KCmdLineArgs::init(...).\n\n");
assert( 0 );
exit(255);
}
Q_ASSERT(s->all_argc >= (args->count()+1));
qt_argc = args->count() +1;
return qt_argc;
}
static char** s_qt_argv = nullptr;
char **
KCmdLineArgs::qtArgv()
{
if (!s->argsList)
addStdCmdLineOptions(CmdLineArgKDE|CmdLineArgQt); // Lazy bastards!
if( s_qt_argv != nullptr )
return s_qt_argv;
if (!(s->mStdargs & KCmdLineArgs::CmdLineArgQt))
{
s_qt_argv = new char*[2];
s_qt_argv[0] = qstrdup(s->all_argc?s->all_argv[0]:"");
s_qt_argv[1] = 0;
return s_qt_argv;
}
KCmdLineArgs *args = parsedArgs("qt");
if (!args)
{
fprintf(stderr, "\n\nFAILURE (KCmdLineArgs):\n");
fprintf(stderr, "The \"qt\" options have not be added to KCmdLineArgs!\n\n");
assert( 0 );
exit(255);
}
if (!s->all_argv)
{
fprintf(stderr, "\n\nFAILURE (KCmdLineArgs):\n");
fprintf(stderr, "Application has not called KCmdLineArgs::init(...).\n\n");
assert( 0 );
exit(255);
}
int count=args->count();
s_qt_argv = new char*[ count + 2 ];
s_qt_argv[0] = qstrdup(s->all_argc?s->all_argv[0]:"");
int i = 0;
for(; i < count; i++)
{
s_qt_argv[i+1] = qstrdup(args->d->parsedArgList->at(i));
}
s_qt_argv[i+1] = 0;
return s_qt_argv;
}
const KAboutData *
KCmdLineArgs::aboutData()
{
return s->about;
}
void
KCmdLineArgs::enable_i18n()
{
// called twice or too late
if (KGlobal::hasLocale())
return;
if (!KGlobal::hasMainComponent()) {
KComponentData mainComponentData(s->about);
mainComponentData.config();
// mainComponentData is now the main component and won't disappear until KGlobal deletes it
}
}
void
KCmdLineArgs::usageError(const QString &error)
{
Q_ASSERT(KGlobal::hasLocale());
QByteArray localError = s->encodeOutput(error);
if (localError.endsWith('\n'))
localError.chop(1);
fprintf(stderr, "%s: %s\n", s->appName, localError.data());
QString tmp = i18n("Use --help to get a list of available command line options.");
localError = s->encodeOutput(tmp);
fprintf(stderr, "%s: %s\n", s->appName, localError.data());
exit(254);
}
void
KCmdLineArgs::usage(const QByteArray &id)
{
enable_i18n();
Q_ASSERT(s->argsList != 0); // It's an error to call usage(...) without
// having done addCmdLineOptions first!
QString optionFormatString = QString::fromLatin1(" %1 %2\n");
QString optionFormatStringDef = QString::fromLatin1(" %1 %2 [%3]\n");
QString tmp;
QString usage;
KCmdLineArgsList::Iterator args = --(s->argsList->end());
if ((*args)->d->id.isEmpty() && ((*args)->d->options.d->names.size() > 0) &&
!(*args)->d->options.d->names[0].startsWith('+'))
{
usage = i18n("[options] ")+usage;
}
while(true)
{
if (!(*args)->d->name.isEmpty())
{
usage = i18n("[%1-options]", (*args)->d->name.toString())+QLatin1Char(' ')+usage;
}
if (args == s->argsList->begin())
break;
--args;
}
KCmdLineArgs *appOptions = s->argsList->last();
if (appOptions->d->id.isEmpty())
{
const KCmdLineOptions &option = appOptions->d->options;
for (int i = 0; i < option.d->names.size(); i++)
{
QByteArray opt_name = option.d->names[i];
if (opt_name.startsWith('+'))
usage += QString::fromLatin1(opt_name.mid(1)) + QLatin1Char(' ');
else if ( opt_name.startsWith("!+") )
usage += QString::fromLatin1(opt_name.mid(2)) + QLatin1Char(' ');
}
}
s->printQ(i18n("Usage: %1 %2\n", QString::fromLocal8Bit(s->appName), Qt::escape(usage)));
s->printQ(QLatin1Char('\n')+s->about->shortDescription()+QLatin1Char('\n'));
s->printQ(i18n("\nGeneric options:\n"));
s->printQ(optionFormatString.arg(QString::fromLatin1("--help"), -25)
.arg(i18n("Show help about options")));
args = s->argsList->begin();
while(args != s->argsList->end())
{
if (!(*args)->d->name.isEmpty() && !(*args)->d->id.isEmpty())
{
QString option = QString::fromLatin1("--help-%1").arg(QString::fromLatin1((*args)->d->id));
QString desc = i18n("Show %1 specific options", (*args)->d->name.toString());
s->printQ(optionFormatString.arg(option, -25).arg(desc));
}
++args;
}
s->printQ(optionFormatString.arg(QString::fromLatin1("--help-all"),-25).arg(i18n("Show all options")));
s->printQ(optionFormatString.arg(QString::fromLatin1("--author"),-25).arg(i18n("Show author information")));
s->printQ(optionFormatString.arg(QString::fromLatin1("-v, --version"),-25).arg(i18n("Show version information")));
s->printQ(optionFormatString.arg(QString::fromLatin1("--license"),-25).arg(i18n("Show license information")));
s->printQ(optionFormatString.arg(QString::fromLatin1("--"), -25).arg(i18n("End of options")));
args = s->argsList->begin(); // Sets current to 1st.
bool showAll = (id == "all");
if (!showAll)
{
while(args != s->argsList->end())
{
if (id == (*args)->d->id) break;
++args;
}
}
while(args != s->argsList->end())
{
bool hasArgs = false;
bool hasOptions = false;
QString optionsHeader;
if (!(*args)->d->name.isEmpty())
optionsHeader = i18n("\n%1 options:\n", (*args)->d->name.toString());
else
optionsHeader = i18n("\nOptions:\n");
while (args != s->argsList->end())
{
const KCmdLineOptions &option = (*args)->d->options;
QByteArray opt;
for (int i = 0; i < option.d->names.size(); i++)
{
QString description;
QStringList dl;
QString descriptionFull;
if (!option.d->descriptions[i].isEmpty()) {
descriptionFull = option.d->descriptions[i].toString();
}
// Option header
if (option.d->names[i].startsWith(':'))
{
if (!descriptionFull.isEmpty())
{
optionsHeader = QLatin1Char('\n')+descriptionFull;
if (!optionsHeader.endsWith(QLatin1Char('\n')))
optionsHeader.append(QLatin1Char('\n'));
hasOptions = false;
}
continue;
}
// Free-form comment
if (option.d->names[i].isEmpty())
{
if (!descriptionFull.isEmpty())
{
tmp = QLatin1Char('\n')+descriptionFull;
if (!tmp.endsWith(QLatin1Char('\n')))
tmp.append(QLatin1Char('\n'));
s->printQ(tmp);
}
continue;
}
// Options
if (!descriptionFull.isEmpty())
{
dl = descriptionFull.split(QLatin1Char('\n'), QString::KeepEmptyParts);
description = dl.first();
dl.erase( dl.begin() );
}
QByteArray name = option.d->names[i];
if (name.startsWith('!'))
name = name.mid(1);
if (name.startsWith('+'))
{
if (!hasArgs)
{
s->printQ(i18n("\nArguments:\n"));
hasArgs = true;
}
name = name.mid(1);
if (name.startsWith('[') && name.endsWith(']'))
name = name.mid(1, name.length()-2);
s->printQ(optionFormatString.arg(QString::fromLocal8Bit(name), -25).arg(description));
}
else
{
if (!hasOptions)
{
s->printQ(optionsHeader);
hasOptions = true;
}
if ((name.length() == 1) || (name[1] == ' '))
name = '-'+name;
else
name = "--"+name;
if (descriptionFull.isEmpty())
{
opt = name + ", ";
}
else
{
opt = opt + name;
if (option.d->defaults[i].isEmpty())
{
s->printQ(optionFormatString.arg(QString::fromLatin1(opt), -25).arg(description));
}
else
{
s->printQ(optionFormatStringDef.arg(QString::fromLatin1(opt), -25)
.arg(description).arg(option.d->defaults[i]));
}
opt.clear();
}
}
for(QStringList::Iterator it = dl.begin();
it != dl.end();
++it)
{
s->printQ(optionFormatString.arg(QString(), -25).arg(*it));
}
}
++args;
if (args == s->argsList->end() || !(*args)->d->name.isEmpty() || (*args)->d->id.isEmpty())
break;
}
if (!showAll) break;
}
exit(0);
}
//
// Member functions
//
/**
* Constructor.
*
* The given arguments are assumed to be constants.
*/
KCmdLineArgs::KCmdLineArgs( const KCmdLineOptions &_options,
const KLocalizedString &_name,
const QByteArray &_id)
: d(new KCmdLineArgsPrivate(_options, _name, _id))
{
}
/**
* Destructor.
*/
KCmdLineArgs::~KCmdLineArgs()
{
if (!s.isDestroyed() && s->argsList)
s->argsList->removeAll(this);
delete d;
}
void
KCmdLineArgs::setCwd( const QByteArray &cwd )
{
s->mCwd = cwd;
}
void
KCmdLineArgs::clear()
{
delete d->parsedArgList; d->parsedArgList = 0;
delete d->parsedOptionList; d->parsedOptionList = 0;
}
void
KCmdLineArgs::reset()
{
delete s->argsList; s->argsList = 0;
s->parsed = false;
}
void
KCmdLineArgsPrivate::save( QDataStream &ds) const
{
if (parsedOptionList)
ds << (*(parsedOptionList));
else
ds << quint32(0);
if (parsedArgList)
ds << (*(parsedArgList));
else
ds << quint32(0);
}
void
KCmdLineArgsPrivate::load( QDataStream &ds)
{
if (!parsedOptionList) parsedOptionList = new KCmdLineParsedOptions;
if (!parsedArgList) parsedArgList = new KCmdLineParsedArgs;
ds >> (*(parsedOptionList));
ds >> (*(parsedArgList));
if (parsedOptionList->count() == 0)
{
delete parsedOptionList; parsedOptionList = 0;
}
if (parsedArgList->count() == 0)
{
delete parsedArgList; parsedArgList = 0;
}
}
void
KCmdLineArgsPrivate::setOption(const QByteArray &opt, bool enabled)
{
if (isQt)
{
// Qt does it own parsing.
QByteArray argString = "-"; // krazy:exclude=doublequote_chars
if( !enabled )
argString += "no";
argString += opt;
addArgument(argString);
}
if (!parsedOptionList) {
parsedOptionList = new KCmdLineParsedOptions;
}
if (enabled)
parsedOptionList->insert( opt, "t" ); // krazy:exclude=doublequote_chars
else
parsedOptionList->insert( opt, "f" ); // krazy:exclude=doublequote_chars
}
void
KCmdLineArgsPrivate::setOption(const QByteArray &opt, const QByteArray &value)
{
if (isQt)
{
// Katie does it's own parsing.
QByteArray argString = "-"; // krazy:exclude=doublequote_chars
argString += opt;
addArgument(argString);
addArgument(value);
#if defined(Q_WS_X11)
// Hack coming up!
if (argString == "-display")
{
setenv(DISPLAY, value.data(), true);
}
#endif
}
if (!parsedOptionList) {
parsedOptionList = new KCmdLineParsedOptions;
}
parsedOptionList->insertMulti( opt, value );
}
QString
KCmdLineArgs::getOption(const QByteArray &_opt) const
{
QByteArray opt = _opt;
QByteArray value;
if (d->parsedOptionList)
{
value = d->parsedOptionList->value(opt);
}
if (!value.isEmpty())
return QString::fromLocal8Bit(value);
// Look up the default.
QByteArray opt_name;
QString def;
bool dummy = true;
int result = s->findOption( d->options, opt, opt_name, def, dummy) & ~4;
if (result != 3)
{
fprintf(stderr, "\n\nFAILURE (KCmdLineArgs):\n");
fprintf(stderr, "Application requests for getOption(\"%s\") but the \"%s\" option\n",
opt.data(), opt.data());
fprintf(stderr, "has never been specified via addCmdLineOptions( ... )\n\n");
Q_ASSERT( 0 );
exit(255);
}
return def;
}
QStringList
KCmdLineArgs::getOptionList(const QByteArray &opt) const
{
QStringList result;
if (!d->parsedOptionList)
return result;
while(true)
{
QByteArray value = d->parsedOptionList->take(opt);
if (value.isEmpty())
break;
result.prepend(QString::fromLocal8Bit(value));
}
// Reinsert items in dictionary
// WABA: This is rather silly, but I don't want to add restrictions
// to the API like "you can only call this function once".
// I can't access all items without taking them out of the list.
// So taking them out and then putting them back is the only way.
Q_FOREACH(const QString &str, result)
{
d->parsedOptionList->insertMulti(opt, str.toLocal8Bit());
}
return result;
}
bool
KCmdLineArgs::isSet(const QByteArray &_opt) const
{
// Look up the default.
QByteArray opt = _opt;
QByteArray opt_name;
QString def;
int result = 0;
KCmdLineArgsList::Iterator args = s->argsList->begin();
while (args != s->argsList->end())
{
bool dummy = true;
result = s->findOption((*args)->d->options, opt, opt_name, def, dummy) & ~4;
if (result) break;
++args;
}
if (result == 0)
{
fprintf(stderr, "\n\nFAILURE (KCmdLineArgs):\n");
fprintf(stderr, "Application requests for isSet(\"%s\") but the \"%s\" option\n",
opt.data(), opt.data());
fprintf(stderr, "has never been specified via addCmdLineOptions( ... )\n\n");
Q_ASSERT( 0 );
exit(255);
}
QByteArray value;
if (d->parsedOptionList)
{
value = d->parsedOptionList->value(opt);
}
if (!value.isEmpty())
{
if (result == 3)
return true;
else
return (value.at(0) == 't');
}
if (result == 3)
return false; // String option has 'false' as default.
// We return 'true' as default if the option was listed as '-nofork'
// We return 'false' as default if the option was listed as '-fork'
return (result == 2);
}
int
KCmdLineArgs::count() const
{
return d->parsedArgList?d->parsedArgList->count():0;
}
QString
KCmdLineArgs::arg(int n) const
{
if (!d->parsedArgList || (n >= (int) d->parsedArgList->count()))
{
fprintf(stderr, "\n\nFAILURE (KCmdLineArgs): Argument out of bounds\n");
fprintf(stderr, "Application requests for arg(%d) without checking count() first.\n",
n);
Q_ASSERT( 0 );
exit(255);
}
return QString::fromLocal8Bit(d->parsedArgList->at(n));
}
KUrl
KCmdLineArgs::url(int n) const
{
return makeURL( arg(n).toUtf8() );
}
KUrl KCmdLineArgs::makeURL(const QByteArray &_urlArg)
{
const QString urlArg = QString::fromUtf8(_urlArg);
QFileInfo fileInfo(urlArg);
if (!fileInfo.isRelative()) { // i.e. starts with '/', on unix
KUrl result;
result.setPath(QDir::fromNativeSeparators(urlArg));
return result; // Absolute path.
}
if ( KUrl::isRelativeUrl(urlArg) || fileInfo.exists() ) {
KUrl result;
result.setPath(cwd()+QLatin1Char('/')+urlArg);
result.cleanPath();
return result; // Relative path
}
return KUrl(urlArg); // Argument is a URL
}
void
KCmdLineArgsPrivate::addArgument(const QByteArray &argument)
{
if (!parsedArgList)
parsedArgList = new KCmdLineParsedArgs;
parsedArgList->append(argument);
}
void
KCmdLineArgs::addTempFileOption()
{
KCmdLineOptions tmpopt;
tmpopt.add( "tempfile", ki18n("The files/URLs opened by the application will be deleted after use") );
KCmdLineArgs::addCmdLineOptions( tmpopt, ki18n("KDE-tempfile"), "kde-tempfile" );
}
bool KCmdLineArgs::isTempFileSet()
{
KCmdLineArgs* args = KCmdLineArgs::parsedArgs( "kde-tempfile" );
return args && args->isSet( "tempfile" );
}
QStringList KCmdLineArgs::allArguments()
{
QStringList lst;
for(int i = 0; i < s->all_argc; i++) {
char* arg = s->all_argv[i];
if (!arg)
continue;
lst.append(QString::fromLocal8Bit(arg));
}
return lst;
}