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 | |
40 | using namespace Konsole; |
41 | |
42 | Application::Application() : KUniqueApplication() |
43 | { |
44 | init(); |
45 | } |
46 | |
47 | void 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 | |
63 | Application::~Application() |
64 | { |
65 | SessionManager::instance()->closeAllSessions(); |
66 | ProfileManager::instance()->saveSettings(); |
67 | } |
68 | |
69 | MainWindow* 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 | |
81 | void Application::createWindow(Profile::Ptr profile, const QString& directory) |
82 | { |
83 | MainWindow* window = newMainWindow(); |
84 | window->createSession(profile, directory); |
85 | window->show(); |
86 | } |
87 | |
88 | void 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 | |
98 | int 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: |
172 | title: This is the title;; command: ssh jupiter |
173 | title: Top this!;; command: top |
174 | #this line is comment |
175 | command: ssh earth |
176 | profile: Zsh |
177 | */ |
178 | void 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 | |
221 | void 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 | |
287 | MainWindow* 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 | |
329 | Profile::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 | |
347 | bool 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 | |
359 | void 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 | |
371 | void 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 | |
383 | Profile::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 | |
439 | void 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 | |
456 | void 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 | |