You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

581 lines
22 KiB

/*
Copyright 2006-2008 by Robert Knight <robertknight@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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.
*/
// Own
#include "Application.h"
// Qt
#include <QtCore/QHashIterator>
#include <QtCore/QFileInfo>
#include <QtCore/QDir>
#include <QCommandLineParser>
#include <QStandardPaths>
#include <QDebug>
// KDE
#include <KActionCollection>
#include <KLocalizedString>
// Konsole
#include "SessionManager.h"
#include "ProfileManager.h"
#include "MainWindow.h"
#include "Session.h"
#include "ShellCommand.h"
#include "KonsoleSettings.h"
#include "ViewManager.h"
#include "SessionController.h"
#include "WindowSystemInfo.h"
using namespace Konsole;
Application::Application(QSharedPointer<QCommandLineParser> parser, const QStringList &customCommand) : m_parser(parser), m_customCommand(customCommand)
{
_backgroundInstance = 0;
}
void Application::populateCommandLineParser(QCommandLineParser *parser)
{
parser->addOption(QCommandLineOption(QStringList() << QStringLiteral("profile"),
i18nc("@info:shell", "Name of profile to use for new Konsole instance"),
QStringLiteral("name")));
parser->addOption(QCommandLineOption(QStringList(QStringLiteral("fallback-profile")),
i18nc("@info:shell", "Use the internal FALLBACK profile")));
parser->addOption(QCommandLineOption(QStringList() << QStringLiteral("workdir"),
i18nc("@info:shell", "Set the initial working directory of the new tab or"
" window to 'dir'"),
QStringLiteral("dir")));
parser->addOption(QCommandLineOption(QStringList() << QStringLiteral("hold") << QStringLiteral("noclose"),
i18nc("@info:shell", "Do not close the initial session automatically when it"
" ends.")));
parser->addOption(QCommandLineOption(QStringList() << QStringLiteral("new-tab"),
i18nc("@info:shell", "Create a new tab in an existing window rather than"
" creating a new window")));
parser->addOption(QCommandLineOption(QStringList() << QStringLiteral("tabs-from-file"),
i18nc("@info:shell", "Create tabs as specified in given tabs configuration"
" file"),
QStringLiteral("file")));
parser->addOption(QCommandLineOption(QStringList() << QStringLiteral("background-mode"),
i18nc("@info:shell", "Start Konsole in the background and bring to the front"
" when Ctrl+Shift+F12 (by default) is pressed")));
// --nofork is a compatibility alias for separate
parser->addOption(QCommandLineOption(QStringList() << QStringLiteral("separate") << QStringLiteral("nofork"), i18n("Run in a separate process")));
parser->addOption(QCommandLineOption(QStringList() << QStringLiteral("show-menubar"), i18nc("@info:shell", "Show the menubar, overriding the default setting")));
parser->addOption(QCommandLineOption(QStringList() << QStringLiteral("hide-menubar"), i18nc("@info:shell", "Hide the menubar, overriding the default setting")));
parser->addOption(QCommandLineOption(QStringList() << QStringLiteral("show-tabbar"), i18nc("@info:shell", "Show the tabbar, overriding the default setting")));
parser->addOption(QCommandLineOption(QStringList() << QStringLiteral("hide-tabbar"), i18nc("@info:shell", "Hide the tabbar, overriding the default setting")));
parser->addOption(QCommandLineOption(QStringList() << QStringLiteral("fullscreen"), i18nc("@info:shell", "Start Konsole in fullscreen mode")));
parser->addOption(QCommandLineOption(QStringList() << QStringLiteral("notransparency"),
i18nc("@info:shell", "Disable transparent backgrounds, even if the system"
" supports them.")));
parser->addOption(QCommandLineOption(QStringList() << QStringLiteral("list-profiles"),
i18nc("@info:shell", "List the available profiles")));
parser->addOption(QCommandLineOption(QStringList() << QStringLiteral("list-profile-properties"),
i18nc("@info:shell", "List all the profile properties names and their type"
" (for use with -p)")));
parser->addOption(QCommandLineOption(QStringList() << QStringLiteral("p"),
i18nc("@info:shell", "Change the value of a profile property."),
QStringLiteral("property=value")));
parser->addOption(QCommandLineOption(QStringList() << QStringLiteral("e"),
i18nc("@info:shell", "Command to execute. This option will catch all following"
" arguments, so use it as the last option."),
QStringLiteral("cmd")));
parser->addPositionalArgument(QStringLiteral("[args]"),
i18nc("@info:shell", "Arguments passed to command"));
// Add a no-op compatibility option to make Konsole compatible with
// Debian's policy on X terminal emulators.
// -T is technically meant to set a title, that is not really meaningful
// for Konsole as we have multiple user-facing options controlling
// the title and overriding whatever is set elsewhere.
// https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=532029
// https://www.debian.org/doc/debian-policy/ch-customized-programs.html#s11.8.3
auto titleOption = QCommandLineOption(QStringList() << QStringLiteral("T"),
QStringLiteral("Debian policy compatibility, not used"),
QStringLiteral("value"));
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
titleOption.setHidden(true);
#endif
parser->addOption(titleOption);
}
QStringList Application::getCustomCommand(QStringList &args)
{
int i = args.indexOf("-e");
QStringList customCommand;
if ((0 < i) && (i < (args.size() - 1))) {
// -e was specified with at least one extra argument
// if -e was specified without arguments, QCommandLineParser will deal
// with that
args.removeAt(i);
while (args.size() > i) {
customCommand << args.takeAt(i);
}
}
return customCommand;
}
Application::~Application()
{
SessionManager::instance()->closeAllSessions();
ProfileManager::instance()->saveSettings();
}
MainWindow* Application::newMainWindow()
{
WindowSystemInfo::HAVE_TRANSPARENCY = !m_parser->isSet(QStringLiteral("notransparency"));
MainWindow* window = new MainWindow();
connect(window, &Konsole::MainWindow::newWindowRequest, this, &Konsole::Application::createWindow);
connect(window, &Konsole::MainWindow::viewDetached, this, &Konsole::Application::detachView);
return window;
}
void Application::createWindow(Profile::Ptr profile, const QString& directory)
{
MainWindow* window = newMainWindow();
window->createSession(profile, directory);
finalizeNewMainWindow(window);
}
void Application::detachView(Session* session)
{
MainWindow* window = newMainWindow();
window->createView(session);
// When detaching a view, the size of the new window should equal the
// size of the source window
Session* newsession = window->viewManager()->activeViewController()->session();
newsession->setSize(session->size());
window->adjustSize();
// Since user is dragging and dropping, move dnd window to where
// the user has the cursor (correct multiple monitor setups).
window->move(QCursor::pos());
window->show();
}
int Application::newInstance()
{
// handle session management
// returns from processWindowArgs(args, createdNewMainWindow)
// if a new window was created
bool createdNewMainWindow = false;
// check for arguments to print help or other information to the
// terminal, quit if such an argument was found
if (processHelpArgs())
return 0;
// create a new window or use an existing one
MainWindow* window = processWindowArgs(createdNewMainWindow);
if (m_parser->isSet(QStringLiteral("tabs-from-file"))) {
// create new session(s) as described in file
if (!processTabsFromFileArgs(window)) {
return 0;
}
}
// select profile to use
Profile::Ptr baseProfile = processProfileSelectArgs();
// process various command-line options which cause a property of the
// selected profile to be changed
Profile::Ptr newProfile = processProfileChangeArgs(baseProfile);
// create new session
Session* session = window->createSession(newProfile, QString());
if (m_parser->isSet(QStringLiteral("noclose"))) {
session->setAutoClose(false);
}
// if the background-mode argument is supplied, start the background
// session ( or bring to the front if it already exists )
if (m_parser->isSet(QStringLiteral("background-mode"))) {
startBackgroundMode(window);
} else {
// Qt constrains top-level windows which have not been manually
// resized (via QWidget::resize()) to a maximum of 2/3rds of the
// screen size.
//
// This means that the terminal display might not get the width/
// height it asks for. To work around this, the widget must be
// manually resized to its sizeHint().
//
// This problem only affects the first time the application is run.
// run. After that KMainWindow will have manually resized the
// window to its saved size at this point (so the Qt::WA_Resized
// attribute will be set)
// If not restoring size from last time or only adding new tab,
// resize window to chosen profile size (see Bug:345403)
if (createdNewMainWindow){
finalizeNewMainWindow(window);
} else{
window->show();
}
}
return 1;
}
/* Documentation for tab file:
*
* ;; is the token separator
* # at the beginning of line results in line being ignored.
* supported tokens are title, command and profile.
*
* Note that the title is static and the tab will close when the
* command is complete (do not use --noclose). You can start new tabs.
*
* Examples:
title: This is the title;; command: ssh jupiter
title: Top this!;; command: top
#this line is comment
command: ssh earth
profile: Zsh
*/
bool Application::processTabsFromFileArgs(MainWindow* window)
{
// Open tab configuration file
const QString tabsFileName(m_parser->value(QStringLiteral("tabs-from-file")));
QFile tabsFile(tabsFileName);
if (!tabsFile.open(QFile::ReadOnly)) {
qWarning() << "ERROR: Cannot open tabs file "
<< tabsFileName.toLocal8Bit().data();
return false;
}
unsigned int sessions = 0;
while (!tabsFile.atEnd()) {
QString lineString(tabsFile.readLine().trimmed());
if ((lineString.isEmpty()) || (lineString[0] == '#'))
continue;
QHash<QString, QString> lineTokens;
QStringList lineParts = lineString.split(QStringLiteral(";;"), QString::SkipEmptyParts);
for (int i = 0; i < lineParts.size(); ++i) {
QString key = lineParts.at(i).section(':', 0, 0).trimmed().toLower();
QString value = lineParts.at(i).section(':', 1, -1).trimmed();
lineTokens[key] = value;
}
// should contain at least one of 'command' and 'profile'
if (lineTokens.contains(QStringLiteral("command")) || lineTokens.contains(QStringLiteral("profile"))) {
createTabFromArgs(window, lineTokens);
sessions++;
} else {
qWarning() << "Each line should contain at least one of 'command' and 'profile'.";
}
}
tabsFile.close();
if (sessions < 1) {
qWarning() << "No valid lines found in "
<< tabsFileName.toLocal8Bit().data();
return false;
}
return true;
}
void Application::createTabFromArgs(MainWindow* window,
const QHash<QString, QString>& tokens)
{
const QString& title = tokens[QStringLiteral("title")];
const QString& command = tokens[QStringLiteral("command")];
const QString& profile = tokens[QStringLiteral("profile")];
const QString& workdir = tokens[QStringLiteral("workdir")];
Profile::Ptr baseProfile;
if (!profile.isEmpty()) {
baseProfile = ProfileManager::instance()->loadProfile(profile);
}
if (!baseProfile) {
// fallback to default profile
baseProfile = ProfileManager::instance()->defaultProfile();
}
Profile::Ptr newProfile = Profile::Ptr(new Profile(baseProfile));
newProfile->setHidden(true);
// FIXME: the method of determining whether to use newProfile does not
// scale well when we support more fields in the future
bool shouldUseNewProfile = false;
if (!command.isEmpty()) {
newProfile->setProperty(Profile::Command, command);
newProfile->setProperty(Profile::Arguments, command.split(' '));
shouldUseNewProfile = true;
}
if (!title.isEmpty()) {
newProfile->setProperty(Profile::LocalTabTitleFormat, title);
newProfile->setProperty(Profile::RemoteTabTitleFormat, title);
shouldUseNewProfile = true;
}
if (m_parser->isSet(QStringLiteral("workdir"))) {
newProfile->setProperty(Profile::Directory, m_parser->value(QStringLiteral("workdir")));
shouldUseNewProfile = true;
}
if (!workdir.isEmpty()) {
newProfile->setProperty(Profile::Directory, workdir);
shouldUseNewProfile = true;
}
// Create the new session
Profile::Ptr theProfile = shouldUseNewProfile ? newProfile : baseProfile;
Session* session = window->createSession(theProfile, QString());
if (m_parser->isSet(QStringLiteral("noclose"))) {
session->setAutoClose(false);
}
if (!window->testAttribute(Qt::WA_Resized)) {
window->resize(window->sizeHint());
}
// FIXME: this ugly hack here is to make the session start running, so that
// its tab title is displayed as expected.
//
// This is another side effect of the commit fixing BKO 176902.
window->show();
window->hide();
}
// Creates a new Konsole window.
// If --new-tab is given, use existing window.
MainWindow* Application::processWindowArgs(bool &createdNewMainWindow)
{
MainWindow* window = 0;
if (m_parser->isSet(QStringLiteral("new-tab"))) {
QListIterator<QWidget*> iter(QApplication::topLevelWidgets());
iter.toBack();
while (iter.hasPrevious()) {
window = qobject_cast<MainWindow*>(iter.previous());
if (window != 0)
break;
}
}
if (window == 0) {
createdNewMainWindow = true;
window = newMainWindow();
// override default menubar visibility
if (m_parser->isSet(QStringLiteral("show-menubar"))) {
window->setMenuBarInitialVisibility(true);
}
if (m_parser->isSet(QStringLiteral("hide-menubar"))) {
window->setMenuBarInitialVisibility(false);
}
if (m_parser->isSet(QStringLiteral("fullscreen"))) {
window->viewFullScreen(true);
}
// override default tabbbar visibility
// FIXME: remove those magic number
// see ViewContainer::NavigationVisibility
if (m_parser->isSet(QStringLiteral("show-tabbar"))) {
// always show
window->setNavigationVisibility(0);
}
if (m_parser->isSet(QStringLiteral("hide-tabbar"))) {
// never show
window->setNavigationVisibility(2);
}
}
return window;
}
// Loads a profile.
// If --profile <name> is given, loads profile <name>.
// If --fallback-profile is given, loads profile FALLBACK/.
// Else loads the default profile.
Profile::Ptr Application::processProfileSelectArgs()
{
Profile::Ptr defaultProfile = ProfileManager::instance()->defaultProfile();
if (m_parser->isSet(QStringLiteral("profile"))) {
Profile::Ptr profile = ProfileManager::instance()->loadProfile(
m_parser->value(QStringLiteral("profile")));
if (profile)
return profile;
} else if (m_parser->isSet(QStringLiteral("fallback-profile"))) {
Profile::Ptr profile = ProfileManager::instance()->loadProfile(QStringLiteral("FALLBACK/"));
if (profile)
return profile;
}
return defaultProfile;
}
bool Application::processHelpArgs()
{
if (m_parser->isSet(QStringLiteral("list-profiles"))) {
listAvailableProfiles();
return true;
} else if (m_parser->isSet(QStringLiteral("list-profile-properties"))) {
listProfilePropertyInfo();
return true;
}
return false;
}
void Application::listAvailableProfiles()
{
QStringList paths = ProfileManager::instance()->availableProfilePaths();
foreach(const QString& path, paths) {
QFileInfo info(path);
printf("%s\n", info.completeBaseName().toLocal8Bit().constData());
}
return;
}
void Application::listProfilePropertyInfo()
{
Profile::Ptr tempProfile = ProfileManager::instance()->defaultProfile();
const QStringList names = tempProfile->propertiesInfoList();
foreach(const QString& name, names) {
printf("%s\n", name.toLocal8Bit().constData());
}
return;
}
Profile::Ptr Application::processProfileChangeArgs(Profile::Ptr baseProfile)
{
bool shouldUseNewProfile = false;
Profile::Ptr newProfile = Profile::Ptr(new Profile(baseProfile));
newProfile->setHidden(true);
// change the initial working directory
if (m_parser->isSet(QStringLiteral("workdir"))) {
newProfile->setProperty(Profile::Directory, m_parser->value(QStringLiteral("workdir")));
shouldUseNewProfile = true;
}
// temporary changes to profile options specified on the command line
foreach(const QString & value , m_parser->values("p")) {
ProfileCommandParser parser;
QHashIterator<Profile::Property, QVariant> iter(parser.parse(value));
while (iter.hasNext()) {
iter.next();
newProfile->setProperty(iter.key(), iter.value());
}
shouldUseNewProfile = true;
}
// run a custom command
if (!m_customCommand.isEmpty()) {
// Example: konsole -e man ls
QString commandExec = m_customCommand[0];
QStringList commandArguments(m_customCommand);
if ((m_customCommand.size() == 1) &&
(QStandardPaths::findExecutable(commandExec).isEmpty())) {
// Example: konsole -e "man ls"
ShellCommand shellCommand(commandExec);
commandExec = shellCommand.command();
commandArguments = shellCommand.arguments();
}
if (commandExec.startsWith(QLatin1String("./")))
commandExec = QDir::currentPath() + commandExec.mid(1);
newProfile->setProperty(Profile::Command, commandExec);
newProfile->setProperty(Profile::Arguments, commandArguments);
shouldUseNewProfile = true;
}
if (shouldUseNewProfile) {
return newProfile;
} else {
return baseProfile;
}
}
void Application::startBackgroundMode(MainWindow* window)
{
if (_backgroundInstance) {
return;
}
/* FIXME: This doesn't work ATM - leave in here so I dont' forget about it
KActionCollection* collection = window->actionCollection();
QAction * action = collection->addAction("toggle-background-window");
action->setObjectName(QLatin1String("Konsole Background Mode"));
action->setText(i18n("Toggle Background Window"));
action->setGlobalShortcut(QKeySequence(Konsole::ACCEL + Qt::SHIFT + Qt::Key_F12)));
connect(action, &QAction::triggered, this, &Application::toggleBackgroundInstance);
*/
_backgroundInstance = window;
}
void Application::toggleBackgroundInstance()
{
Q_ASSERT(_backgroundInstance);
if (!_backgroundInstance->isVisible()) {
_backgroundInstance->show();
// ensure that the active terminal display has the focus. Without
// this, an odd problem occurred where the focus widget would change
// each time the background instance was shown
_backgroundInstance->setFocus();
} else {
_backgroundInstance->hide();
}
}
void Application::slotActivateRequested (QStringList args, const QString & /*workingDir*/)
{
// QCommandLineParser expects the first argument to be the executable name
// In the current version it just strips it away
args.prepend(qApp->applicationFilePath());
m_customCommand = getCustomCommand(args);
// We can't re-use QCommandLineParser instances, it preserves earlier parsed values
QCommandLineParser *parser = new QCommandLineParser;
populateCommandLineParser(parser);
parser->parse(args);
m_parser.reset(parser);
newInstance();
}
void Application::finalizeNewMainWindow(MainWindow* window)
{
if (!KonsoleSettings::saveGeometryOnExit())
window->resize(window->sizeHint());
window->show();
}