/* Copyright 2006-2008 by Robert Knight 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" // std #include // Qt #include #include #include // KDE #include #include #include #include #include #include // Konsole #include "ColorScheme.h" #include "ProfileList.h" #include "SessionManager.h" #include "KeyboardTranslator.h" #include "MainWindow.h" #include "Session.h" #include "TerminalDisplay.h" #include "ViewManager.h" using namespace Konsole; Application::Application() : KUniqueApplication() { init(); } void Application::init() { _backgroundInstance = 0; // check for compositing functionality TerminalDisplay::setTransparencyEnabled(KWindowSystem::compositingActive()); #if defined(Q_WS_MAC) && QT_VERSION >= 0x040600 // this ensures that Ctrl and Meta are not swapped, so CTRL-C and friends // will work correctly in the terminal setAttribute(Qt::AA_MacDontSwapCtrlAndMeta); // KDE's menuBar()->isTopLevel() hasn't worked in a while. // For now, put menus inside Konsole window; this also make // the keyboard shortcut to show menus look reasonable. setAttribute(Qt::AA_DontUseNativeMenuBar); #endif } MainWindow* Application::newMainWindow() { MainWindow* window = new MainWindow(); window->setProfileList(new ProfileList(true, window)); connect(window, SIGNAL(newSessionRequest(Profile::Ptr,QString,ViewManager*)), this, SLOT(createSession(Profile::Ptr,QString,ViewManager*))); connect(window, SIGNAL(newSSHSessionRequest(Profile::Ptr,KUrl,ViewManager*)), this, SLOT(createSSHSession(Profile::Ptr,KUrl,ViewManager*))); connect(window, SIGNAL(newWindowRequest(Profile::Ptr,QString)), this, SLOT(createWindow(Profile::Ptr,QString))); connect(window->viewManager(), SIGNAL(viewDetached(Session*)), this, SLOT(detachView(Session*))); return window; } void Application::listAvailableProfiles() { QList paths = SessionManager::instance()->availableProfilePaths(); foreach ( const QString& path, paths) { QFileInfo info(path); std::cout << info.completeBaseName().toLocal8Bit().data() << std::endl; } quit(); } void Application::listProfilePropertyInfo() { Profile::Ptr tempProfile = SessionManager::instance()->defaultProfile(); const QStringList names = tempProfile->propertiesInfoList(); foreach ( const QString& name, names) { std::cout << name.toLocal8Bit().data() << std::endl; } quit(); } int Application::newInstance() { static bool firstInstance = true; KCmdLineArgs* args = KCmdLineArgs::parsedArgs(); // handle session management if ((args->count() != 0) || !firstInstance || !isSessionRestored()) { // check for arguments to print help or other information to the // terminal, quit if such an argument was found if (processHelpArgs(args)) return 0; // create a new window or use an existing one MainWindow* window = processWindowArgs(args); if (args->isSet("tabs-from-file")) { // create new session(s) as described in file processTabsFromFileArgs(args, window); } else { // select profile to use Profile::Ptr baseProfile = processProfileSelectArgs(args); // process various command-line options which cause a property of the // selected profile to be changed Profile::Ptr newProfile = processProfileChangeArgs(args, baseProfile); // create new session Session* session = createSession(newProfile, QString(), window->viewManager()); if (!args->isSet("close")) { session->setAutoClose(false); } } // if the background-mode argument is supplied, start the background // session ( or bring to the front if it already exists ) if (args->isSet("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 (!window->testAttribute(Qt::WA_Resized)) window->resize(window->sizeHint()); window->show(); } } firstInstance = false; args->clear(); return 0; } /* Documentation for tab file: * ;; is the token separator * # at the beginning of line results in line being ignored * tokens are title:, command:, profile: (not used currently) * 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 #title: This is commented out;; command: ssh jupiter command: ssh earth */ void Application::processTabsFromFileArgs(KCmdLineArgs* args, MainWindow* window) { // Open tab configuration file const QString tabsFileName(args->getOption("tabs-from-file")); QFile tabsFile(tabsFileName); if (!tabsFile.open(QFile::ReadOnly)) { kWarning() << "ERROR: Cannot open tabs file " << tabsFileName.toLocal8Bit().data(); quit(); } unsigned int sessions = 0; while (!tabsFile.atEnd()) { QString lineString(tabsFile.readLine().trimmed()); if ((lineString.isEmpty()) || (lineString[0] == '#')) continue; QHash lineTokens; QStringList lineParts = lineString.split(";;", 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("command") || lineTokens.contains("profile") ) { createTabFromArgs(args, window, lineTokens); sessions++; } else { kWarning() << "Each line should contain at least one of 'commad' and 'profile'."; } } tabsFile.close(); if (sessions < 1) { kWarning() << "No valid lines found in " << tabsFileName.toLocal8Bit().data(); quit(); } } void Application::createTabFromArgs(KCmdLineArgs* args, MainWindow* window, const QHash& tokens) { const QString& title = tokens["title"]; const QString& command = tokens["command"]; const QString& profile = tokens["profile"]; Profile::Ptr baseProfile; if (!profile.isEmpty()) { baseProfile = SessionManager::instance()->loadProfile(profile); } if (!baseProfile) { // fallback to default profile baseProfile = SessionManager::instance()->defaultProfile(); } Profile::Ptr newProfile = Profile::Ptr(new Profile(baseProfile)); newProfile->setHidden(true); // FIXME: the method of determining whethter 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 (args->isSet("workdir")) { newProfile->setProperty(Profile::Directory, args->getOption("workdir")); shouldUseNewProfile = true; } // Create the new session Profile::Ptr theProfile = shouldUseNewProfile ? newProfile : baseProfile ; Session* session = createSession(theProfile, QString(), window->viewManager()); if (!args->isSet("close")) { 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(); } MainWindow* Application::processWindowArgs(KCmdLineArgs* args) { MainWindow* window = 0; if (args->isSet("new-tab")) { QListIterator iter(topLevelWidgets()); iter.toBack(); while (iter.hasPrevious()) { window = qobject_cast(iter.previous()); if (window != 0) break; } } if (window == 0) { window = newMainWindow(); } return window; } Profile::Ptr Application::processProfileSelectArgs(KCmdLineArgs* args) { Profile::Ptr defaultProfile = SessionManager::instance()->defaultProfile(); if (args->isSet("profile")) { Profile::Ptr profile = SessionManager::instance()->loadProfile( args->getOption("profile")); if (profile) return profile; } return defaultProfile; } bool Application::processHelpArgs(KCmdLineArgs* args) { if (args->isSet("list-profiles")) { listAvailableProfiles(); return true; } else if (args->isSet("list-profile-properties")) { listProfilePropertyInfo(); return true; } return false; } Profile::Ptr Application::processProfileChangeArgs(KCmdLineArgs* args, Profile::Ptr baseProfile) { bool shouldUseNewProfile = false; Profile::Ptr newProfile = Profile::Ptr(new Profile(baseProfile)); newProfile->setHidden(true); // change the initial working directory if (args->isSet("workdir")) { newProfile->setProperty(Profile::Directory, args->getOption("workdir")); shouldUseNewProfile = true; } // temporary changes to profile options specified on the command line foreach(const QString & value , args->getOptionList("p")) { ProfileCommandParser parser; QHashIterator iter(parser.parse(value)); while (iter.hasNext()) { iter.next(); newProfile->setProperty(iter.key(), iter.value()); } shouldUseNewProfile = true; } // run a custom command if ( args->isSet("e") ) { QStringList arguments; arguments << args->getOption("e"); for ( int i = 0 ; i < args->count() ; i++ ) arguments << args->arg(i); QString exec = args->getOption("e"); if (exec.startsWith(QLatin1String("./"))) exec = QDir::currentPath() + exec.mid(1); newProfile->setProperty(Profile::Command, exec); newProfile->setProperty(Profile::Arguments, arguments); shouldUseNewProfile = true; } if ( shouldUseNewProfile ) { return newProfile; } else { return baseProfile; } } void Application::startBackgroundMode(MainWindow* window) { if (_backgroundInstance) { return; } KAction* action = window->actionCollection()->addAction("toggle-background-window"); action->setObjectName(QLatin1String("Konsole Background Mode")); action->setText(i18n("Toggle Background Window")); action->setGlobalShortcut(KShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_F12))); connect(action, SIGNAL(triggered()), this, SLOT(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->viewManager()->activeView()->setFocus(); } else { _backgroundInstance->hide(); } } Application::~Application() { SessionManager::instance()->closeAllSessions(); SessionManager::instance()->saveSettings(); } void Application::detachView(Session* session) { MainWindow* window = newMainWindow(); window->viewManager()->createView(session); window->show(); } void Application::createWindow(Profile::Ptr profile, const QString& directory) { MainWindow* window = newMainWindow(); createSession(profile, directory, window->viewManager()); window->show(); } Session* Application::createSession(Profile::Ptr profile, const QString& directory, ViewManager* viewManager) { if (!profile) profile = SessionManager::instance()->defaultProfile(); Session* session = SessionManager::instance()->createSession(profile); if (!directory.isEmpty() && profile->property(Profile::StartInCurrentSessionDir)) session->setInitialWorkingDirectory(directory); // create view before starting the session process so that the session // doesn't suffer a change in terminal size right after the session // starts. Some applications such as GNU Screen and Midnight Commander // don't like this happening viewManager->createView(session); return session; } Session* Application::createSSHSession(Profile::Ptr profile, const KUrl& url, ViewManager* viewManager) { if (!profile) profile = SessionManager::instance()->defaultProfile(); Session* session = SessionManager::instance()->createSession(profile); session->sendText("ssh "); if (url.port() > -1) session->sendText("-p " + QString::number(url.port()) + ' '); if (url.hasUser()) session->sendText(url.user() + '@'); if (url.hasHost()) session->sendText(url.host() + '\r'); // create view before starting the session process so that the session // doesn't suffer a change in terminal size right after the session // starts. some applications such as GNU Screen and Midnight Commander // don't like this happening viewManager->createView(session); return session; } #include "Application.moc"