Application.cpp 15.6 KB
Newer Older
1
/*
2
    Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

    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.
*/

20
21
22
// Own
#include "Application.h"

23
24
25
26
// std
#include <iostream>

// Qt
27
28
29
#include <QtCore/QHashIterator>
#include <QtCore/QFileInfo>
#include <QtCore/QDir>
30

31
32
// KDE
#include <KAction>
33
#include <KActionCollection>
34
#include <KCmdLineArgs>
35
#include <KUrl>
Jordi Polo's avatar
   
Jordi Polo committed
36
#include <KDebug>
Robert Knight's avatar
Robert Knight committed
37
#include <KWindowSystem>
38
39
40

// Konsole
#include "ColorScheme.h"
41
#include "ProfileList.h"
42
#include "SessionManager.h"
43
#include "KeyboardTranslator.h"
44
#include "MainWindow.h"
45
#include "Session.h"
46
#include "TerminalDisplay.h"
47
48
#include "ViewManager.h"

49
using namespace Konsole;
50

51
Application::Application() : KUniqueApplication()
52
{
53
54
55
56
57
58
59
    init();
}

void Application::init()
{
    _backgroundInstance = 0;

60
    // check for compositing functionality
Kurt Hindenburg's avatar
Kurt Hindenburg committed
61
    TerminalDisplay::setTransparencyEnabled(KWindowSystem::compositingActive());
62

63
64
65
66
#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);
67
68
69
70
71

    // 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);
72
#endif
Stephan Binner's avatar
Stephan Binner committed
73
}
74

75
MainWindow* Application::newMainWindow()
76
{
77
    MainWindow* window = new MainWindow();
Jekyll Wu's avatar
Jekyll Wu committed
78
    window->setProfileList(new ProfileList(true, window));
79

80
    connect(window,
81
82
            SIGNAL(newSessionRequest(Profile::Ptr,QString,ViewManager*)),
            this, SLOT(createSession(Profile::Ptr,QString,ViewManager*)));
83
    connect(window,
84
85
86
87
            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)));
88
89
    connect(window->viewManager(), SIGNAL(viewDetached(Session*)),
            this, SLOT(detachView(Session*)));
90
91
92
93

    return window;
}

94
95
96
97
void Application::listAvailableProfiles()
{
    QList<QString> paths = SessionManager::instance()->availableProfilePaths();

98
99
    foreach ( const QString& path, paths) {
        QFileInfo info(path);
100
        std::cout << info.completeBaseName().toLocal8Bit().data() << std::endl;
101
    }
102

103
    quit();
104
105
}

106
107
108
109
void Application::listProfilePropertyInfo()
{
    Profile::Ptr tempProfile = SessionManager::instance()->defaultProfile();
    const QStringList names = tempProfile->propertiesInfoList();
Jekyll Wu's avatar
Jekyll Wu committed
110
111
112
113
114

    foreach ( const QString& name, names) {
        std::cout << name.toLocal8Bit().data() << std::endl;
    }

115
116
117
    quit();
}

118
int Application::newInstance()
119
{
Robert Knight's avatar
   
Robert Knight committed
120
    static bool firstInstance = true;
121

122
123
    KCmdLineArgs* args = KCmdLineArgs::parsedArgs();

Robert Knight's avatar
   
Robert Knight committed
124
    // handle session management
Kurt Hindenburg's avatar
Kurt Hindenburg committed
125
    if ((args->count() != 0) || !firstInstance || !isSessionRestored()) {
126
127
        // check for arguments to print help or other information to the
        // terminal, quit if such an argument was found
Kurt Hindenburg's avatar
Kurt Hindenburg committed
128
        if (processHelpArgs(args))
Robert Knight's avatar
   
Robert Knight committed
129
130
131
132
133
            return 0;

        // create a new window or use an existing one
        MainWindow* window = processWindowArgs(args);

134
135
136
137
138
139
        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);
Robert Knight's avatar
   
Robert Knight committed
140

141
142
143
            // process various command-line options which cause a property of the
            // selected profile to be changed
            Profile::Ptr newProfile = processProfileChangeArgs(args, baseProfile);
Robert Knight's avatar
   
Robert Knight committed
144

145
            // create new session
146
            Session* session = createSession(newProfile,
147
148
                                             QString(),
                                             window->viewManager());
Kurt Hindenburg's avatar
Kurt Hindenburg committed
149
            if (!args->isSet("close")) {
150
151
152
                session->setAutoClose(false);
            }
        }
Robert Knight's avatar
   
Robert Knight committed
153

154
155
156
        // 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")) {
Robert Knight's avatar
   
Robert Knight committed
157
            startBackgroundMode(window);
158
159
160
161
        } 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.
Robert Knight's avatar
   
Robert Knight committed
162
            //
163
164
165
            // 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().
Robert Knight's avatar
   
Robert Knight committed
166
            //
167
168
169
170
            // 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)
Robert Knight's avatar
   
Robert Knight committed
171
172
173
174
175
            if (!window->testAttribute(Qt::WA_Resized))
                window->resize(window->sizeHint());

            window->show();
        }
176
    }
177

Robert Knight's avatar
   
Robert Knight committed
178
179
    firstInstance = false;
    args->clear();
180
181
182
    return 0;
}

183
184
185
186
/* 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)
187
 * Note that the title is static and the tab will close when the
188
189
190
191
192
193
194
 * 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
*/
195
void Application::processTabsFromFileArgs(KCmdLineArgs* args,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
196
        MainWindow* window)
197
198
199
200
{
    // Open tab configuration file
    const QString tabsFileName(args->getOption("tabs-from-file"));
    QFile tabsFile(tabsFileName);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
201
    if (!tabsFile.open(QFile::ReadOnly)) {
202
203
204
205
        kWarning() << "ERROR: Cannot open tabs file "
                   << tabsFileName.toLocal8Bit().data();
        quit();
    }
206

207
208
    unsigned int sessions = 0;
    while (!tabsFile.atEnd()) {
209
        QString lineString(tabsFile.readLine().trimmed());
210
211
212
213
214
215
216
217
        if ((lineString.isEmpty()) || (lineString[0] == '#'))
            continue;

        QHash<QString, QString> 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();
218
            QString value = lineParts.at(i).section(':', 1, -1).trimmed();
219
220
            lineTokens[key] = value;
        }
221
222
        // should contain at least one of 'command' and 'profile'
        if (lineTokens.contains("command") || lineTokens.contains("profile") ) {
223
224
225
            createTabFromArgs(args, window, lineTokens);
            sessions++;
        } else {
226
            kWarning() << "Each line should contain at least one of 'commad' and 'profile'.";
227
228
229
230
231
232
233
        }
    }
    tabsFile.close();

    if (sessions < 1) {
        kWarning() << "No valid lines found in "
                   << tabsFileName.toLocal8Bit().data();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
234
        quit();
235
236
237
    }
}

238
239
void Application::createTabFromArgs(KCmdLineArgs* args, MainWindow* window,
                                    const QHash<QString, QString>& tokens)
240
{
Jekyll Wu's avatar
Jekyll Wu committed
241
242
    const QString& title = tokens["title"];
    const QString& command = tokens["command"];
243
    const QString& profile = tokens["profile"];
244

245
246
247
248
249
250
251
252
    Profile::Ptr baseProfile;
    if (!profile.isEmpty()) {
        baseProfile = SessionManager::instance()->loadProfile(profile);
    }
    if (!baseProfile) {
        // fallback to default profile
        baseProfile = SessionManager::instance()->defaultProfile();
    }
253

254
    Profile::Ptr newProfile = Profile::Ptr(new Profile(baseProfile));
255
    newProfile->setHidden(true);
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272

    // 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;
    }

Kurt Hindenburg's avatar
Kurt Hindenburg committed
273
274
    if (args->isSet("workdir")) {
        newProfile->setProperty(Profile::Directory, args->getOption("workdir"));
275
        shouldUseNewProfile = true;
276
    }
277

278
    // Create the new session
279
280
    Profile::Ptr theProfile = shouldUseNewProfile ? newProfile :  baseProfile ;
    Session* session = createSession(theProfile, QString(), window->viewManager());
281

Kurt Hindenburg's avatar
Kurt Hindenburg committed
282
    if (!args->isSet("close")) {
283
284
        session->setAutoClose(false);
    }
285

286
287
288
289
    if (!window->testAttribute(Qt::WA_Resized)) {
        window->resize(window->sizeHint());
    }

290
291
292
    // FIXME: this ugly hack here is to make the session start running, so that
    // its tab title is displayed as expected.
    //
Jekyll Wu's avatar
Jekyll Wu committed
293
    // This is another side effect of the commit fixing BKO 176902.
294
295
296
    window->show();
    window->hide();

297
298
}

299
300
MainWindow* Application::processWindowArgs(KCmdLineArgs* args)
{
301
    MainWindow* window = 0;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
302
    if (args->isSet("new-tab")) {
303
304
        QListIterator<QWidget*> iter(topLevelWidgets());
        iter.toBack();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
305
        while (iter.hasPrevious()) {
306
            window = qobject_cast<MainWindow*>(iter.previous());
Kurt Hindenburg's avatar
Kurt Hindenburg committed
307
            if (window != 0)
308
                break;
309
        }
310
    }
311

Kurt Hindenburg's avatar
Kurt Hindenburg committed
312
    if (window == 0) {
313
314
        window = newMainWindow();
    }
315
316
    return window;
}
317

318
Profile::Ptr Application::processProfileSelectArgs(KCmdLineArgs* args)
319
{
320
321
    Profile::Ptr defaultProfile = SessionManager::instance()->defaultProfile();

Kurt Hindenburg's avatar
Kurt Hindenburg committed
322
    if (args->isSet("profile")) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
323
324
        Profile::Ptr profile = SessionManager::instance()->loadProfile(
                                   args->getOption("profile"));
325
326
        if (profile)
            return profile;
327
    }
328
329

    return defaultProfile;
330
331
332
333
}

bool Application::processHelpArgs(KCmdLineArgs* args)
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
334
    if (args->isSet("list-profiles")) {
335
336
        listAvailableProfiles();
        return true;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
337
    } else if (args->isSet("list-profile-properties")) {
338
339
340
        listProfilePropertyInfo();
        return true;
    }
341
    return false;
342
}
343
344

Profile::Ptr Application::processProfileChangeArgs(KCmdLineArgs* args, Profile::Ptr baseProfile)
345
{
Jekyll Wu's avatar
Jekyll Wu committed
346
347
    bool shouldUseNewProfile = false;

348
    Profile::Ptr newProfile = Profile::Ptr(new Profile(baseProfile));
Jordi Polo's avatar
   
Jordi Polo committed
349
    newProfile->setHidden(true);
350

351
    // change the initial working directory
Kurt Hindenburg's avatar
Kurt Hindenburg committed
352
353
    if (args->isSet("workdir")) {
        newProfile->setProperty(Profile::Directory, args->getOption("workdir"));
Jekyll Wu's avatar
Jekyll Wu committed
354
        shouldUseNewProfile = true;
355
    }
356
357

    // temporary changes to profile options specified on the command line
Kurt Hindenburg's avatar
Kurt Hindenburg committed
358
    foreach(const QString & value , args->getOptionList("p")) {
359
        ProfileCommandParser parser;
360

Kurt Hindenburg's avatar
Kurt Hindenburg committed
361
362
        QHashIterator<Profile::Property, QVariant> iter(parser.parse(value));
        while (iter.hasNext()) {
363
            iter.next();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
364
            newProfile->setProperty(iter.key(), iter.value());
365
        }
Jekyll Wu's avatar
Jekyll Wu committed
366
367

        shouldUseNewProfile = true;
368
    }
369

370
371
372
373
374
375
376
377
378
379
380
381
382
    // 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);
Jekyll Wu's avatar
Jekyll Wu committed
383
384

        shouldUseNewProfile = true;
385
386
    }

Jekyll Wu's avatar
Jekyll Wu committed
387
388
389
390
391
392
    if ( shouldUseNewProfile ) {
        return newProfile;
    }
    else {
        return baseProfile;
    }
393
394
}

395
396
void Application::startBackgroundMode(MainWindow* window)
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
397
    if (_backgroundInstance) {
Jekyll Wu's avatar
Jekyll Wu committed
398
399
        return;
    }
400

401
    KAction* action = window->actionCollection()->addAction("toggle-background-window");
Kurt Hindenburg's avatar
Kurt Hindenburg committed
402
    action->setObjectName(QLatin1String("Konsole Background Mode"));
403
    action->setText(i18n("Toggle Background Window"));
404
    action->setGlobalShortcut(KShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_F12)));
405

406
407
    connect(action, SIGNAL(triggered()),
            this, SLOT(toggleBackgroundInstance()));
Jekyll Wu's avatar
Jekyll Wu committed
408

409
    _backgroundInstance = window;
410
411
}

412
void Application::toggleBackgroundInstance()
413
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
414
    Q_ASSERT(_backgroundInstance);
415

Kurt Hindenburg's avatar
Kurt Hindenburg committed
416
    if (!_backgroundInstance->isVisible()) {
417
        _backgroundInstance->show();
418
419
        // ensure that the active terminal display has the focus. Without
        // this, an odd problem occurred where the focus widget would change
Jekyll Wu's avatar
Jekyll Wu committed
420
        // each time the background instance was shown
421
        _backgroundInstance->viewManager()->activeView()->setFocus();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
422
    } else {
423
424
        _backgroundInstance->hide();
    }
425
}
426

427
428
Application::~Application()
{
429
    SessionManager::instance()->closeAllSessions();
430
    SessionManager::instance()->saveSettings();
431
432
}

433
void Application::detachView(Session* session)
434
{
435
    MainWindow* window = newMainWindow();
436
437
438
439
    window->viewManager()->createView(session);
    window->show();
}

440
void Application::createWindow(Profile::Ptr profile, const QString& directory)
441
442
{
    MainWindow* window = newMainWindow();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
443
    createSession(profile, directory, window->viewManager());
444
445
446
    window->show();
}

447
448
449
Session* Application::createSession(Profile::Ptr profile,
                                    const QString& directory,
                                    ViewManager* viewManager)
450
{
Jordi Polo's avatar
   
Jordi Polo committed
451
452
    if (!profile)
        profile = SessionManager::instance()->defaultProfile();
453

454
    Session* session = SessionManager::instance()->createSession(profile);
455

456
457
    if (!directory.isEmpty()
            && profile->property<bool>(Profile::StartInCurrentSessionDir))
458
459
        session->setInitialWorkingDirectory(directory);

460
461
462
463
    // 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
Jekyll Wu's avatar
Jekyll Wu committed
464
    viewManager->createView(session);
465

Jordi Polo's avatar
   
Jordi Polo committed
466
    return session;
467
468
}

469
470
Session* Application::createSSHSession(Profile::Ptr profile, const KUrl& url,
                                       ViewManager* viewManager)
471
472
473
474
475
476
477
478
{
    if (!profile)
        profile = SessionManager::instance()->defaultProfile();

    Session* session = SessionManager::instance()->createSession(profile);

    session->sendText("ssh ");

Kurt Hindenburg's avatar
Kurt Hindenburg committed
479
480
481
    if (url.port() > -1)
        session->sendText("-p " + QString::number(url.port()) + ' ');
    if (url.hasUser())
482
        session->sendText(url.user() + '@');
Kurt Hindenburg's avatar
Kurt Hindenburg committed
483
    if (url.hasHost())
484
485
        session->sendText(url.host() + '\r');

486
487
488
489
    // 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
Jekyll Wu's avatar
Jekyll Wu committed
490
    viewManager->createView(session);
491
492
493
494

    return session;
}

495
#include "Application.moc"
Robert Knight's avatar
   
Robert Knight committed
496