2 This source file is part of Konsole, a terminal emulator.
4 Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 #include "SessionManager.h"
26 #include <QtCore/QDir>
27 #include <QtCore/QFileInfo>
28 #include <QtCore/QList>
29 #include <QtCore/QSignalMapper>
30 #include <QtCore/QString>
31 #include <QtCore/QTextCodec>
41 #include <kconfiggroup.h>
42 #include <kstandarddirs.h>
43 #include <kdesktopfile.h>
46 #include "ColorScheme.h"
49 #include "ShellCommand.h"
51 using namespace Konsole
;
55 bool Profile::isAvailable() const
57 //TODO: Is it necessary to cache the result of the search?
59 QString binary
= KRun::binaryName( command(true) , false );
60 binary
= KShell::tildeExpand(binary
);
62 QString fullBinaryPath
= KGlobal::dirs()->findExe(binary
);
64 if ( fullBinaryPath
.isEmpty() )
71 SessionManager::SessionManager()
72 : _loadedAllProfiles(false)
73 , _loadedFavorites(false)
75 //map finished() signals from sessions
76 _sessionMapper
= new QSignalMapper(this);
77 connect( _sessionMapper
, SIGNAL(mapped(QObject
*)) , this ,
78 SLOT(sessionTerminated(QObject
*)) );
80 //load fallback profile
81 _fallbackProfile
= Profile::Ptr(new FallbackProfile
);
82 addProfile(_fallbackProfile
);
84 //locate and load default profile
85 KSharedConfigPtr appConfig
= KGlobal::config();
86 const KConfigGroup group
= appConfig
->group( "Desktop Entry" );
87 QString defaultSessionFilename
= group
.readEntry("DefaultProfile","Shell.profile");
89 QString path
= KGlobal::dirs()->findResource("data","konsole/"+defaultSessionFilename
);
92 Profile::Ptr profile
= loadProfile(path
);
94 _defaultProfile
= profile
;
97 Q_ASSERT( _types
.count() > 0 );
98 Q_ASSERT( _defaultProfile
);
100 // get shortcuts and paths of profiles associated with
101 // them - this doesn't load the shortcuts themselves,
102 // that is done on-demand.
105 Profile::Ptr
SessionManager::loadProfile(const QString
& shortPath
)
107 // the fallback profile has a 'special' path name, "FALLBACK/"
108 if (shortPath
== _fallbackProfile
->property
<QString
>(Profile::Path
))
109 return _fallbackProfile
;
111 QString path
= shortPath
;
113 // add a suggested suffix and relative prefix if missing
114 QFileInfo
fileInfo(path
);
115 if ( fileInfo
.suffix().isEmpty() )
116 path
.append(".profile");
117 if ( fileInfo
.path().isEmpty() || fileInfo
.path() == "." )
118 path
.prepend(QString("konsole")+QDir::separator());
120 // if the file is not an absolute path, look it up
121 if ( !fileInfo
.isAbsolute() )
122 path
= KStandardDirs::locate("data",path
);
124 // check that we have not already loaded this profile
125 QSetIterator
<Profile::Ptr
> iter(_types
);
126 while ( iter
.hasNext() )
128 Profile::Ptr profile
= iter
.next();
129 if ( profile
->path() == path
)
133 // guard to prevent problems if a profile specifies itself as its parent
134 // or if there is recursion in the "inheritance" chain
135 // (eg. two profiles, A and B, specifying each other as their parents)
136 static QStack
<QString
> recursionGuard
;
137 PopStackOnExit
<QString
> popGuardOnExit(recursionGuard
);
139 if (recursionGuard
.contains(path
))
141 kWarning() << "Ignoring attempt to load profile recursively from" << path
;
142 return _fallbackProfile
;
145 recursionGuard
.push(path
);
148 ProfileReader
* reader
= 0;
149 if ( path
.endsWith(".desktop") )
150 reader
= 0; // new KDE3ProfileReader;
152 reader
= new KDE4ProfileReader
;
156 kWarning() << "Could not create loader to read profile from" << path
;
157 return Profile::Ptr();
160 Profile::Ptr newProfile
= Profile::Ptr(new Profile(defaultProfile()));
161 newProfile
->setProperty(Profile::Path
,path
);
163 QString parentProfilePath
;
164 bool result
= reader
->readProfile(path
,newProfile
,parentProfilePath
);
166 if ( !parentProfilePath
.isEmpty() )
168 Profile::Ptr parentProfile
= loadProfile(parentProfilePath
);
169 newProfile
->setParent(parentProfile
);
176 kWarning() << "Could not load profile from " << path
;
177 return Profile::Ptr();
181 addProfile(newProfile
);
185 QStringList
SessionManager::availableProfilePaths() const
187 KDE3ProfileReader kde3Reader
;
188 KDE4ProfileReader kde4Reader
;
190 QStringList profiles
;
191 profiles
+= kde3Reader
.findProfiles();
192 profiles
+= kde4Reader
.findProfiles();
197 void SessionManager::loadAllProfiles()
199 if ( _loadedAllProfiles
)
202 QStringList profiles
= availableProfilePaths();
204 QListIterator
<QString
> iter(profiles
);
205 while (iter
.hasNext())
206 loadProfile(iter
.next());
208 _loadedAllProfiles
= true;
210 void SessionManager::saveState()
212 // save default profile
213 setDefaultProfile( _defaultProfile
);
221 void SessionManager::closeAll()
223 // close remaining sessions
224 foreach( Session
* session
, _sessions
)
230 SessionManager::~SessionManager()
232 if (_sessions
.count() > 0)
234 kWarning() << "Konsole SessionManager destroyed with sessions still alive";
235 // ensure that the Session doesn't later try to call back and do things to the
237 foreach(Session
* session
, _sessions
)
238 disconnect(session
, 0 , this , 0);
242 const QList
<Session
*> SessionManager::sessions()
247 void SessionManager::updateSession(Session
* session
)
249 Profile::Ptr info
= _sessionProfiles
[session
];
253 applyProfile(session
,info
,false);
255 // FIXME - This may update a lot more than just the session
257 emit
sessionUpdated(session
);
260 Session
* SessionManager::createSession(Profile::Ptr info
)
262 Session
* session
= 0;
265 info
= defaultProfile();
267 if (!_types
.contains(info
))
270 //configuration information found, create a new session based on this
271 session
= new Session();
272 applyProfile(session
,info
,false);
274 connect( session
, SIGNAL(profileChangeCommandReceived(QString
)) , this ,
275 SLOT(sessionProfileCommandReceived(QString
)) );
277 //ask for notification when session dies
278 _sessionMapper
->setMapping(session
,session
);
279 connect( session
, SIGNAL(finished()) , _sessionMapper
,
282 //add session to active list
283 _sessions
<< session
;
284 _sessionProfiles
.insert(session
,info
);
291 void SessionManager::sessionTerminated(QObject
* sessionObject
)
293 Session
* session
= qobject_cast
<Session
*>(sessionObject
);
297 _sessions
.removeAll(session
);
298 session
->deleteLater();
301 QList
<Profile::Ptr
> SessionManager::loadedProfiles() const
303 return _types
.toList();
306 Profile::Ptr
SessionManager::defaultProfile() const
308 return _defaultProfile
;
310 Profile::Ptr
SessionManager::fallbackProfile() const
311 { return _fallbackProfile
; }
313 QString
SessionManager::saveProfile(Profile::Ptr info
)
315 ProfileWriter
* writer
= new KDE4ProfileWriter
;
317 QString newPath
= writer
->getPath(info
);
319 writer
->writeProfile(newPath
,info
);
326 void SessionManager::changeProfile(Profile::Ptr info
,
327 QHash
<Profile::Property
,QVariant
> propertyMap
, bool persistant
)
331 // insert the changes into the existing Profile instance
332 QListIterator
<Profile::Property
> iter(propertyMap
.keys());
333 while ( iter
.hasNext() )
335 const Profile::Property property
= iter
.next();
336 info
->setProperty(property
,propertyMap
[property
]);
339 // when changing a group, iterate through the profiles
340 // in the group and call changeProfile() on each of them
342 // this is so that each profile in the group, the profile is
343 // applied, a change notification is emitted and the profile
345 ProfileGroup::Ptr group
= info
->asGroup();
348 foreach(Profile::Ptr profile
, group
->profiles())
349 changeProfile(profile
,propertyMap
,persistant
);
353 // apply the changes to existing sessions
354 applyProfile(info
,true);
356 // notify the world about the change
357 emit
profileChanged(info
);
359 // save changes to disk, unless the profile is hidden, in which case
360 // it has no file on disk
361 if ( persistant
&& !info
->isHidden() )
363 info
->setProperty(Profile::Path
,saveProfile(info
));
366 void SessionManager::applyProfile(Profile::Ptr info
, bool modifiedPropertiesOnly
)
368 QListIterator
<Session
*> iter(_sessions
);
369 while ( iter
.hasNext() )
371 Session
* next
= iter
.next();
372 if ( _sessionProfiles
[next
] == info
)
373 applyProfile(next
,info
,modifiedPropertiesOnly
);
376 Profile::Ptr
SessionManager::sessionProfile(Session
* session
) const
378 return _sessionProfiles
[session
];
380 void SessionManager::setSessionProfile(Session
* session
, Profile::Ptr profile
)
382 _sessionProfiles
[session
] = profile
;
383 updateSession(session
);
385 void SessionManager::applyProfile(Session
* session
, const Profile::Ptr info
, bool modifiedPropertiesOnly
)
389 _sessionProfiles
[session
] = info
;
391 ShouldApplyProperty
apply(info
,modifiedPropertiesOnly
);
393 // Basic session settings
394 if ( apply
.shouldApply(Profile::Name
) )
395 session
->setTitle(Session::NameRole
,info
->name());
397 if ( apply
.shouldApply(Profile::Command
) )
398 session
->setProgram(info
->command());
400 if ( apply
.shouldApply(Profile::Arguments
) )
401 session
->setArguments(info
->arguments());
403 if ( apply
.shouldApply(Profile::Directory
) )
404 session
->setInitialWorkingDirectory(info
->defaultWorkingDirectory());
406 if ( apply
.shouldApply(Profile::Environment
) )
408 // add environment variable containing home directory of current profile
410 QStringList environment
= info
->property
<QStringList
>(Profile::Environment
);
411 environment
<< QString("PROFILEHOME=%1").arg(info
->defaultWorkingDirectory());
413 session
->setEnvironment(environment
);
416 if ( apply
.shouldApply(Profile::Icon
) )
417 session
->setIconName(info
->icon());
420 if ( apply
.shouldApply(Profile::KeyBindings
) )
421 session
->setKeyBindings(info
->property
<QString
>(Profile::KeyBindings
));
424 if ( apply
.shouldApply(Profile::LocalTabTitleFormat
) )
425 session
->setTabTitleFormat( Session::LocalTabTitle
,
426 info
->property
<QString
>(Profile::LocalTabTitleFormat
));
427 if ( apply
.shouldApply(Profile::RemoteTabTitleFormat
) )
428 session
->setTabTitleFormat( Session::RemoteTabTitle
,
429 info
->property
<QString
>(Profile::RemoteTabTitleFormat
));
431 // Scrollback / history
432 if ( apply
.shouldApply(Profile::HistoryMode
) || apply
.shouldApply(Profile::HistorySize
) )
434 int mode
= info
->property
<int>(Profile::HistoryMode
);
435 switch ((Profile::HistoryModeEnum
)mode
)
437 case Profile::DisableHistory
:
438 session
->setHistoryType( HistoryTypeNone() );
440 case Profile::FixedSizeHistory
:
442 int lines
= info
->property
<int>(Profile::HistorySize
);
443 session
->setHistoryType( HistoryTypeBuffer(lines
) );
446 case Profile::UnlimitedHistory
:
447 session
->setHistoryType( HistoryTypeFile() );
453 if ( apply
.shouldApply(Profile::FlowControlEnabled
) )
454 session
->setFlowControlEnabled( info
->property
<bool>(Profile::FlowControlEnabled
) );
457 if ( apply
.shouldApply(Profile::DefaultEncoding
) )
459 QByteArray name
= info
->property
<QString
>(Profile::DefaultEncoding
).toUtf8();
460 session
->setCodec( QTextCodec::codecForName(name
) );
464 void SessionManager::addProfile(Profile::Ptr type
)
466 if ( _types
.isEmpty() )
467 _defaultProfile
= type
;
471 emit
profileAdded(type
);
474 bool SessionManager::deleteProfile(Profile::Ptr type
)
476 bool wasDefault
= ( type
== defaultProfile() );
480 // try to delete the config file
481 if ( type
->isPropertySet(Profile::Path
) && QFile::exists(type
->path()) )
483 if (!QFile::remove(type
->path()))
485 kWarning() << "Could not delete profile: " << type
->path()
486 << "The file is most likely in a directory which is read-only.";
492 // remove from favorites, profile list, shortcut list etc.
493 setFavorite(type
,false);
494 setShortcut(type
,QKeySequence());
497 // mark the profile as hidden so that it does not show up in the
498 // Manage Profiles dialog and is not saved to disk
499 type
->setHidden(true);
502 // if we just deleted the default session type,
503 // replace it with a random type from the list
506 setDefaultProfile( _types
.toList().first() );
509 emit
profileRemoved(type
);
513 void SessionManager::setDefaultProfile(Profile::Ptr info
)
515 Q_ASSERT ( _types
.contains(info
) );
517 _defaultProfile
= info
;
519 QString path
= info
->path();
521 if ( path
.isEmpty() )
522 path
= KDE4ProfileWriter().getPath(info
);
524 QFileInfo
fileInfo(path
);
526 KSharedConfigPtr config
= KGlobal::config();
527 KConfigGroup group
= config
->group("Desktop Entry");
528 group
.writeEntry("DefaultProfile",fileInfo
.fileName());
530 QSet
<Profile::Ptr
> SessionManager::findFavorites()
532 if (!_loadedFavorites
)
537 void SessionManager::setFavorite(Profile::Ptr info
, bool favorite
)
539 if (!_types
.contains(info
))
542 if ( favorite
&& !_favorites
.contains(info
) )
544 _favorites
.insert(info
);
545 emit
favoriteStatusChanged(info
,favorite
);
547 else if ( !favorite
&& _favorites
.contains(info
) )
549 _favorites
.remove(info
);
550 emit
favoriteStatusChanged(info
,favorite
);
553 void SessionManager::loadShortcuts()
555 KSharedConfigPtr appConfig
= KGlobal::config();
556 KConfigGroup shortcutGroup
= appConfig
->group("Profile Shortcuts");
558 QMap
<QString
,QString
> entries
= shortcutGroup
.entryMap();
560 QMapIterator
<QString
,QString
> iter(entries
);
561 while ( iter
.hasNext() )
565 QKeySequence shortcut
= QKeySequence::fromString(iter
.key());
566 QString profilePath
= iter
.value();
569 data
.profilePath
= profilePath
;
571 _shortcuts
.insert(shortcut
,data
);
574 void SessionManager::saveShortcuts()
576 KSharedConfigPtr appConfig
= KGlobal::config();
577 KConfigGroup shortcutGroup
= appConfig
->group("Profile Shortcuts");
578 shortcutGroup
.deleteGroup();
580 QMapIterator
<QKeySequence
,ShortcutData
> iter(_shortcuts
);
581 while ( iter
.hasNext() )
585 QString shortcutString
= iter
.key().toString();
587 shortcutGroup
.writeEntry(shortcutString
,
588 iter
.value().profilePath
);
591 void SessionManager::setShortcut(Profile::Ptr info
,
592 const QKeySequence
& keySequence
)
594 QKeySequence existingShortcut
= shortcut(info
);
595 _shortcuts
.remove(existingShortcut
);
597 if (keySequence
.isEmpty())
601 data
.profileKey
= info
;
602 data
.profilePath
= info
->path();
603 // TODO - This won't work if the profile doesn't
605 _shortcuts
.insert(keySequence
,data
);
607 emit
shortcutChanged(info
,keySequence
);
609 void SessionManager::loadFavorites()
611 KSharedConfigPtr appConfig
= KGlobal::config();
612 KConfigGroup favoriteGroup
= appConfig
->group("Favorite Profiles");
614 QSet
<QString
> favoriteSet
;
616 if ( favoriteGroup
.hasKey("Favorites") )
618 QStringList list
= favoriteGroup
.readEntry("Favorites", QStringList());
619 favoriteSet
= QSet
<QString
>::fromList(list
);
623 // if there is no favorites key at all, mark the
624 // supplied 'Shell.profile' as the only favorite
625 favoriteSet
<< "Shell.profile";
628 // look for favorites amongst those already loaded
629 QSetIterator
<Profile::Ptr
> iter(_types
);
630 while ( iter
.hasNext() )
632 Profile::Ptr profile
= iter
.next();
633 const QString
& path
= profile
->path();
634 if ( favoriteSet
.contains( path
) )
636 _favorites
.insert( profile
);
637 favoriteSet
.remove(path
);
640 // load any remaining favorites
641 QSetIterator
<QString
> unloadedFavoriteIter(favoriteSet
);
642 while ( unloadedFavoriteIter
.hasNext() )
644 Profile::Ptr profile
= loadProfile(unloadedFavoriteIter
.next());
646 _favorites
.insert(profile
);
649 _loadedFavorites
= true;
651 void SessionManager::saveFavorites()
653 KSharedConfigPtr appConfig
= KGlobal::config();
654 KConfigGroup favoriteGroup
= appConfig
->group("Favorite Profiles");
657 QSetIterator
<Profile::Ptr
> keyIter(_favorites
);
658 while ( keyIter
.hasNext() )
660 Profile::Ptr profile
= keyIter
.next();
662 Q_ASSERT( _types
.contains(profile
) && profile
);
664 paths
<< profile
->path();
667 favoriteGroup
.writeEntry("Favorites",paths
);
670 QList
<QKeySequence
> SessionManager::shortcuts()
672 return _shortcuts
.keys();
675 Profile::Ptr
SessionManager::findByShortcut(const QKeySequence
& shortcut
)
677 Q_ASSERT( _shortcuts
.contains(shortcut
) );
679 if ( !_shortcuts
[shortcut
].profileKey
)
681 Profile::Ptr key
= loadProfile(_shortcuts
[shortcut
].profilePath
);
684 _shortcuts
.remove(shortcut
);
685 return Profile::Ptr();
687 _shortcuts
[shortcut
].profileKey
= key
;
690 return _shortcuts
[shortcut
].profileKey
;
693 void SessionManager::sessionProfileCommandReceived(const QString
& text
)
695 // FIXME: This is inefficient, it creates a new profile instance for
696 // each set of changes applied. Instead a new profile should be created
697 // only the first time changes are applied to a session
699 Session
* session
= qobject_cast
<Session
*>(sender());
702 ProfileCommandParser parser
;
703 QHash
<Profile::Property
,QVariant
> changes
= parser
.parse(text
);
705 Profile::Ptr newProfile
= Profile::Ptr(new Profile(_sessionProfiles
[session
]));
707 QHashIterator
<Profile::Property
,QVariant
> iter(changes
);
708 while ( iter
.hasNext() )
711 newProfile
->setProperty(iter
.key(),iter
.value());
714 _sessionProfiles
[session
] = newProfile
;
715 applyProfile(newProfile
,true);
716 emit
sessionUpdated(session
);
719 QKeySequence
SessionManager::shortcut(Profile::Ptr info
) const
721 QMapIterator
<QKeySequence
,ShortcutData
> iter(_shortcuts
);
722 while (iter
.hasNext())
725 if ( iter
.value().profileKey
== info
726 || iter
.value().profilePath
== info
->path() )
730 return QKeySequence();
733 void SessionManager::saveSessions(KConfig
* config
)
735 // The session IDs can't be restored.
736 // So we need to map the old ID to the future new ID.
738 _restoreMapping
.clear();
740 foreach(Session
* session
, _sessions
)
742 QString name
= QLatin1String("Session") + QString::number(n
);
743 KConfigGroup
group(config
, name
);
745 group
.writePathEntry("Profile",
746 _sessionProfiles
.value(session
)->path());
747 session
->saveSession(group
);
748 _restoreMapping
.insert(session
, n
);
752 KConfigGroup
group(config
, "Number");
753 group
.writeEntry("NumberOfSessions", _sessions
.count());
756 int SessionManager::getRestoreId(Session
* session
)
758 return _restoreMapping
.value(session
);
761 void SessionManager::restoreSessions(KConfig
* config
)
763 KConfigGroup
group(config
, "Number");
766 // Any sessions saved?
767 if ((sessions
= group
.readEntry("NumberOfSessions", 0)) > 0)
769 for (int n
= 1; n
<= sessions
; n
++)
771 QString name
= QLatin1String("Session") + QString::number(n
);
772 KConfigGroup
sessionGroup(config
, name
);
774 QString profile
= sessionGroup
.readPathEntry("Profile", QString());
775 Profile::Ptr ptr
= defaultProfile();
776 if (!profile
.isEmpty()) ptr
= loadProfile(profile
);
778 Session
* session
= createSession(ptr
);
779 session
->restoreSession(sessionGroup
);
784 Session
* SessionManager::idToSession(int id
)
787 foreach(Session
* session
, _sessions
)
788 if (session
->sessionId() == id
)
791 // this should not happen
796 K_GLOBAL_STATIC( SessionManager
, theSessionManager
)
797 SessionManager
* SessionManager::instance()
799 return theSessionManager
;
802 SessionListModel::SessionListModel(QObject
* parent
)
803 : QAbstractListModel(parent
)
807 void SessionListModel::setSessions(const QList
<Session
*>& sessions
)
809 _sessions
= sessions
;
811 foreach(Session
* session
, sessions
)
812 connect(session
,SIGNAL(finished()),this,SLOT(sessionFinished()));
816 QVariant
SessionListModel::data(const QModelIndex
& index
, int role
) const
818 Q_ASSERT(index
.isValid());
820 int row
= index
.row();
821 int column
= index
.column();
823 Q_ASSERT( row
>= 0 && row
< _sessions
.count() );
824 Q_ASSERT( column
>= 0 && column
< 2 );
828 case Qt::DisplayRole
:
830 return _sessions
[row
]->title(Session::DisplayedTitleRole
);
831 else if (column
== 0)
832 return _sessions
[row
]->sessionId();
834 case Qt::DecorationRole
:
836 return KIcon(_sessions
[row
]->iconName());
843 QVariant
SessionListModel::headerData(int section
, Qt::Orientation orientation
,
846 if (role
!= Qt::DisplayRole
)
849 if (orientation
== Qt::Vertical
)
856 return i18n("Number");
858 return i18n("Title");
865 int SessionListModel::columnCount(const QModelIndex
&) const
869 int SessionListModel::rowCount(const QModelIndex
&) const
871 return _sessions
.count();
873 QModelIndex
SessionListModel::parent(const QModelIndex
&) const
875 return QModelIndex();
877 void SessionListModel::sessionFinished()
879 Session
* session
= qobject_cast
<Session
*>(sender());
880 int row
= _sessions
.indexOf(session
);
884 beginRemoveRows(QModelIndex(),row
,row
);
885 sessionRemoved(session
);
886 _sessions
.removeAt(row
);
890 QModelIndex
SessionListModel::index(int row
, int column
, const QModelIndex
& parent
) const
892 if (hasIndex(row
,column
,parent
))
893 return createIndex(row
,column
,_sessions
[row
]);
895 return QModelIndex();
898 #include "SessionManager.moc"
903 c-file-style: "stroustrup"
904 indent-tabs-mode: nil