1/*
2 Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301 USA.
18*/
19
20// Own
21#include "Application.h"
22
23// Qt
24#include <QtCore/QHashIterator>
25#include <QtCore/QFileInfo>
26#include <QtCore/QDir>
27
28// KDE
29#include <KAction>
30#include <KActionCollection>
31#include <KCmdLineArgs>
32#include <KDebug>
33
34// Konsole
35#include "SessionManager.h"
36#include "ProfileManager.h"
37#include "MainWindow.h"
38#include "Session.h"
39
40using namespace Konsole;
41
42Application::Application() : KUniqueApplication()
43{
44 init();
45}
46
47void Application::init()
48{
49 _backgroundInstance = 0;
50
51#if defined(Q_WS_MAC)
52 // this ensures that Ctrl and Meta are not swapped, so CTRL-C and friends
53 // will work correctly in the terminal
54 setAttribute(Qt::AA_MacDontSwapCtrlAndMeta);
55
56 // KDE's menuBar()->isTopLevel() hasn't worked in a while.
57 // For now, put menus inside Konsole window; this also make
58 // the keyboard shortcut to show menus look reasonable.
59 setAttribute(Qt::AA_DontUseNativeMenuBar);
60#endif
61}
62
63Application::~Application()
64{
65 SessionManager::instance()->closeAllSessions();
66 ProfileManager::instance()->saveSettings();
67}
68
69MainWindow* Application::newMainWindow()
70{
71 MainWindow* window = new MainWindow();
72
73 connect(window, SIGNAL(newWindowRequest(Profile::Ptr,QString)),
74 this, SLOT(createWindow(Profile::Ptr,QString)));
75 connect(window, SIGNAL(viewDetached(Session*)),
76 this, SLOT(detachView(Session*)));
77
78 return window;
79}
80
81void Application::createWindow(Profile::Ptr profile, const QString& directory)
82{
83 MainWindow* window = newMainWindow();
84 window->createSession(profile, directory);
85 window->show();
86}
87
88void Application::detachView(Session* session)
89{
90 MainWindow* window = newMainWindow();
91 window->createView(session);
92 // Since user is dragging and dropping, move dnd window to where
93 // the user has the cursor (correct multiple monitor setups).
94 window->move(QCursor::pos());
95 window->show();
96}
97
98int Application::newInstance()
99{
100 static bool firstInstance = true;
101
102 KCmdLineArgs* args = KCmdLineArgs::parsedArgs();
103
104 // handle session management
105 if ((args->count() != 0) || !firstInstance || !isSessionRestored()) {
106 // check for arguments to print help or other information to the
107 // terminal, quit if such an argument was found
108 if (processHelpArgs(args))
109 return 0;
110
111 // create a new window or use an existing one
112 MainWindow* window = processWindowArgs(args);
113
114 if (args->isSet("tabs-from-file")) {
115 // create new session(s) as described in file
116 processTabsFromFileArgs(args, window);
117 } else {
118 // select profile to use
119 Profile::Ptr baseProfile = processProfileSelectArgs(args);
120
121 // process various command-line options which cause a property of the
122 // selected profile to be changed
123 Profile::Ptr newProfile = processProfileChangeArgs(args, baseProfile);
124
125 // create new session
126 Session* session = window->createSession(newProfile, QString());
127
128 if (!args->isSet("close")) {
129 session->setAutoClose(false);
130 }
131 }
132
133 // if the background-mode argument is supplied, start the background
134 // session ( or bring to the front if it already exists )
135 if (args->isSet("background-mode")) {
136 startBackgroundMode(window);
137 } else {
138 // Qt constrains top-level windows which have not been manually
139 // resized (via QWidget::resize()) to a maximum of 2/3rds of the
140 // screen size.
141 //
142 // This means that the terminal display might not get the width/
143 // height it asks for. To work around this, the widget must be
144 // manually resized to its sizeHint().
145 //
146 // This problem only affects the first time the application is run.
147 // run. After that KMainWindow will have manually resized the
148 // window to its saved size at this point (so the Qt::WA_Resized
149 // attribute will be set)
150 if (!window->testAttribute(Qt::WA_Resized))
151 window->resize(window->sizeHint());
152
153 window->show();
154 }
155 }
156
157 firstInstance = false;
158 args->clear();
159 return 0;
160}
161
162/* Documentation for tab file:
163 *
164 * ;; is the token separator
165 * # at the beginning of line results in line being ignored.
166 * supported tokens are title, command and profile.
167 *
168 * Note that the title is static and the tab will close when the
169 * command is complete (do not use --noclose). You can start new tabs.
170 *
171 * Examples:
172title: This is the title;; command: ssh jupiter
173title: Top this!;; command: top
174#this line is comment
175command: ssh earth
176profile: Zsh
177*/
178void Application::processTabsFromFileArgs(KCmdLineArgs* args,
179 MainWindow* window)
180{
181 // Open tab configuration file
182 const QString tabsFileName(args->getOption("tabs-from-file"));
183 QFile tabsFile(tabsFileName);
184 if (!tabsFile.open(QFile::ReadOnly)) {
185 kWarning() << "ERROR: Cannot open tabs file "
186 << tabsFileName.toLocal8Bit().data();
187 quit();
188 }
189
190 unsigned int sessions = 0;
191 while (!tabsFile.atEnd()) {
192 QString lineString(tabsFile.readLine().trimmed());
193 if ((lineString.isEmpty()) || (lineString[0] == '#'))
194 continue;
195
196 QHash<QString, QString> lineTokens;
197 QStringList lineParts = lineString.split(";;", QString::SkipEmptyParts);
198
199 for (int i = 0; i < lineParts.size(); ++i) {
200 QString key = lineParts.at(i).section(':', 0, 0).trimmed().toLower();
201 QString value = lineParts.at(i).section(':', 1, -1).trimmed();
202 lineTokens[key] = value;
203 }
204 // should contain at least one of 'command' and 'profile'
205 if (lineTokens.contains("command") || lineTokens.contains("profile")) {
206 createTabFromArgs(args, window, lineTokens);
207 sessions++;
208 } else {
209 kWarning() << "Each line should contain at least one of 'command' and 'profile'.";
210 }
211 }
212 tabsFile.close();
213
214 if (sessions < 1) {
215 kWarning() << "No valid lines found in "
216 << tabsFileName.toLocal8Bit().data();
217 quit();
218 }
219}
220
221void Application::createTabFromArgs(KCmdLineArgs* args, MainWindow* window,
222 const QHash<QString, QString>& tokens)
223{
224 const QString& title = tokens["title"];
225 const QString& command = tokens["command"];
226 const QString& profile = tokens["profile"];
227 const QString& workdir = tokens["workdir"];
228
229 Profile::Ptr baseProfile;
230 if (!profile.isEmpty()) {
231 baseProfile = ProfileManager::instance()->loadProfile(profile);
232 }
233 if (!baseProfile) {
234 // fallback to default profile
235 baseProfile = ProfileManager::instance()->defaultProfile();
236 }
237
238 Profile::Ptr newProfile = Profile::Ptr(new Profile(baseProfile));
239 newProfile->setHidden(true);
240
241 // FIXME: the method of determining whether to use newProfile does not
242 // scale well when we support more fields in the future
243 bool shouldUseNewProfile = false;
244
245 if (!command.isEmpty()) {
246 newProfile->setProperty(Profile::Command, command);
247 newProfile->setProperty(Profile::Arguments, command.split(' '));
248 shouldUseNewProfile = true;
249 }
250
251 if (!title.isEmpty()) {
252 newProfile->setProperty(Profile::LocalTabTitleFormat, title);
253 newProfile->setProperty(Profile::RemoteTabTitleFormat, title);
254 shouldUseNewProfile = true;
255 }
256
257 if (args->isSet("workdir")) {
258 newProfile->setProperty(Profile::Directory, args->getOption("workdir"));
259 shouldUseNewProfile = true;
260 }
261
262 if (!workdir.isEmpty()) {
263 newProfile->setProperty(Profile::Directory, workdir);
264 shouldUseNewProfile = true;
265 }
266
267 // Create the new session
268 Profile::Ptr theProfile = shouldUseNewProfile ? newProfile : baseProfile;
269 Session* session = window->createSession(theProfile, QString());
270
271 if (!args->isSet("close")) {
272 session->setAutoClose(false);
273 }
274
275 if (!window->testAttribute(Qt::WA_Resized)) {
276 window->resize(window->sizeHint());
277 }
278
279 // FIXME: this ugly hack here is to make the session start running, so that
280 // its tab title is displayed as expected.
281 //
282 // This is another side effect of the commit fixing BKO 176902.
283 window->show();
284 window->hide();
285}
286
287MainWindow* Application::processWindowArgs(KCmdLineArgs* args)
288{
289 MainWindow* window = 0;
290 if (args->isSet("new-tab")) {
291 QListIterator<QWidget*> iter(topLevelWidgets());
292 iter.toBack();
293 while (iter.hasPrevious()) {
294 window = qobject_cast<MainWindow*>(iter.previous());
295 if (window != 0)
296 break;
297 }
298 }
299
300 if (window == 0) {
301 window = newMainWindow();
302
303 // override default menubar visibility
304 if (args->isSet("show-menubar")) {
305 window->setMenuBarInitialVisibility(true);
306 }
307 if (args->isSet("hide-menubar")) {
308 window->setMenuBarInitialVisibility(false);
309 }
310 if (args->isSet("fullscreen")) {
311 window->viewFullScreen(true);
312 }
313
314 // override default tabbbar visibility
315 // FIXME: remove those magic number
316 // see ViewContainer::NavigationVisibility
317 if (args->isSet("show-tabbar")) {
318 // always show
319 window->setNavigationVisibility(0);
320 }
321 if (args->isSet("hide-tabbar")) {
322 // never show
323 window->setNavigationVisibility(2);
324 }
325 }
326 return window;
327}
328
329Profile::Ptr Application::processProfileSelectArgs(KCmdLineArgs* args)
330{
331 Profile::Ptr defaultProfile = ProfileManager::instance()->defaultProfile();
332
333 if (args->isSet("profile")) {
334 Profile::Ptr profile = ProfileManager::instance()->loadProfile(
335 args->getOption("profile"));
336 if (profile)
337 return profile;
338 } else if (args->isSet("fallback-profile")) {
339 Profile::Ptr profile = ProfileManager::instance()->loadProfile("FALLBACK/");
340 if (profile)
341 return profile;
342 }
343
344 return defaultProfile;
345}
346
347bool Application::processHelpArgs(KCmdLineArgs* args)
348{
349 if (args->isSet("list-profiles")) {
350 listAvailableProfiles();
351 return true;
352 } else if (args->isSet("list-profile-properties")) {
353 listProfilePropertyInfo();
354 return true;
355 }
356 return false;
357}
358
359void Application::listAvailableProfiles()
360{
361 QStringList paths = ProfileManager::instance()->availableProfilePaths();
362
363 foreach(const QString& path, paths) {
364 QFileInfo info(path);
365 printf("%s\n", info.completeBaseName().toLocal8Bit().constData());
366 }
367
368 quit();
369}
370
371void Application::listProfilePropertyInfo()
372{
373 Profile::Ptr tempProfile = ProfileManager::instance()->defaultProfile();
374 const QStringList names = tempProfile->propertiesInfoList();
375
376 foreach(const QString& name, names) {
377 printf("%s\n", name.toLocal8Bit().constData());
378 }
379
380 quit();
381}
382
383Profile::Ptr Application::processProfileChangeArgs(KCmdLineArgs* args, Profile::Ptr baseProfile)
384{
385 bool shouldUseNewProfile = false;
386
387 Profile::Ptr newProfile = Profile::Ptr(new Profile(baseProfile));
388 newProfile->setHidden(true);
389
390 // change the initial working directory
391 if (args->isSet("workdir")) {
392 newProfile->setProperty(Profile::Directory, args->getOption("workdir"));
393 shouldUseNewProfile = true;
394 }
395
396 // temporary changes to profile options specified on the command line
397 foreach(const QString & value , args->getOptionList("p")) {
398 ProfileCommandParser parser;
399
400 QHashIterator<Profile::Property, QVariant> iter(parser.parse(value));
401 while (iter.hasNext()) {
402 iter.next();
403 newProfile->setProperty(iter.key(), iter.value());
404 }
405
406 shouldUseNewProfile = true;
407 }
408
409 // run a custom command
410 if (args->isSet("e")) {
411 QString commandExec = args->getOption("e");
412 QStringList commandArguments;
413
414 //commandArguments << args->getOption("e");
415 commandArguments << commandExec;
416
417 // Note: KCmdLineArgs::count() return the number of arguments
418 // that aren't options.
419 for ( int i = 0 ; i < args->count() ; i++ )
420 commandArguments << args->arg(i);
421
422
423 if (commandExec.startsWith(QLatin1String("./")))
424 commandExec = QDir::currentPath() + commandExec.mid(1);
425
426 newProfile->setProperty(Profile::Command, commandExec);
427 newProfile->setProperty(Profile::Arguments, commandArguments);
428
429 shouldUseNewProfile = true;
430 }
431
432 if (shouldUseNewProfile) {
433 return newProfile;
434 } else {
435 return baseProfile;
436 }
437}
438
439void Application::startBackgroundMode(MainWindow* window)
440{
441 if (_backgroundInstance) {
442 return;
443 }
444
445 KAction* action = window->actionCollection()->addAction("toggle-background-window");
446 action->setObjectName(QLatin1String("Konsole Background Mode"));
447 action->setText(i18n("Toggle Background Window"));
448 action->setGlobalShortcut(KShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_F12)));
449
450 connect(action, SIGNAL(triggered()),
451 this, SLOT(toggleBackgroundInstance()));
452
453 _backgroundInstance = window;
454}
455
456void Application::toggleBackgroundInstance()
457{
458 Q_ASSERT(_backgroundInstance);
459
460 if (!_backgroundInstance->isVisible()) {
461 _backgroundInstance->show();
462 // ensure that the active terminal display has the focus. Without
463 // this, an odd problem occurred where the focus widget would change
464 // each time the background instance was shown
465 _backgroundInstance->setFocus();
466 } else {
467 _backgroundInstance->hide();
468 }
469}
470
471#include "Application.moc"
472
473