/* This source file is part of Konsole, a terminal emulator. Copyright (C) 2006-7 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 "SessionManager.h" // Qt #include #include #include #include // KDE #include #include #include #include #include #include // Konsole #include "ColorScheme.h" #include "Session.h" #include "History.h" #include "ShellCommand.h" using namespace Konsole; SessionManager* SessionManager::_instance = 0; #if 0 bool Profile::isAvailable() const { //TODO: Is it necessary to cache the result of the search? QString binary = KRun::binaryName( command(true) , false ); binary = KShell::tildeExpand(binary); QString fullBinaryPath = KGlobal::dirs()->findExe(binary); if ( fullBinaryPath.isEmpty() ) return false; else return true; } #endif SessionManager::SessionManager() : _loadedAllProfiles(false) { //map finished() signals from sessions _sessionMapper = new QSignalMapper(this); connect( _sessionMapper , SIGNAL(mapped(QObject*)) , this , SLOT(sessionTerminated(QObject*)) ); //load fallback profile addProfile( new FallbackProfile ); //locate and load default profile KSharedConfigPtr appConfig = KGlobal::config(); const KConfigGroup group = appConfig->group( "Desktop Entry" ); QString defaultSessionFilename = group.readEntry("DefaultProfile","Shell.profile"); QString path = KGlobal::dirs()->findResource("data","konsole/"+defaultSessionFilename); if (!path.isEmpty()) { const QString& key = loadProfile(path); if ( !key.isEmpty() ) _defaultProfile = key; } Q_ASSERT( _types.count() > 0 ); Q_ASSERT( !_defaultProfile.isEmpty() ); // get shortcuts and paths of profiles associated with // them - this doesn't load the shortcuts themselves, // that is done on-demand. loadShortcuts(); } QString SessionManager::loadProfile(const QString& path) { // check that we have not already loaded this profile QHashIterator iter(_types); while ( iter.hasNext() ) { iter.next(); if ( iter.value()->path() == path ) return iter.key(); } // load the profile ProfileReader* reader = 0; if ( path.endsWith(".desktop") ) reader = 0; //new KDE3ProfileReader; else reader = new KDE4ProfileReader; if (!reader) return QString(); Profile* newProfile = new Profile(defaultProfile()); newProfile->setProperty(Profile::Path,path); bool result = reader->readProfile(path,newProfile); delete reader; if (!result) { delete newProfile; return QString(); } else return addProfile(newProfile); } void SessionManager::loadAllProfiles() { if ( _loadedAllProfiles ) return; qDebug() << "Loading all profiles"; KDE3ProfileReader kde3Reader; KDE4ProfileReader kde4Reader; QStringList profiles; profiles += kde3Reader.findProfiles(); profiles += kde4Reader.findProfiles(); QListIterator iter(profiles); while (iter.hasNext()) loadProfile(iter.next()); _loadedAllProfiles = true; } SessionManager::~SessionManager() { // save default profile setDefaultProfile( _defaultProfile ); // save shortcuts saveShortcuts(); // free profiles QListIterator infoIter(_types.values()); while (infoIter.hasNext()) delete infoIter.next(); } const QList SessionManager::sessions() { return _sessions; } void SessionManager::updateSession(Session* session) { Profile* info = profile(session->profileKey()); Q_ASSERT( info ); applyProfile(session,info,false); // FIXME - This may update a lot more than just the session // of interest. emit sessionUpdated(session); } Session* SessionManager::createSession(const QString& key ) { Session* session = 0; const Profile* info = 0; if ( key.isEmpty() ) info = defaultProfile(); else info = _types[key]; //configuration information found, create a new session based on this session = new Session(); session->setProfileKey(key); applyProfile(session,info,false); connect( session , SIGNAL(profileChanged(const QString&)) , this , SLOT(sessionProfileChanged()) ); //ask for notification when session dies _sessionMapper->setMapping(session,session); connect( session , SIGNAL(finished()) , _sessionMapper , SLOT(map()) ); //add session to active list _sessions << session; Q_ASSERT( session ); return session; } void SessionManager::sessionTerminated(QObject* sessionObject) { Session* session = qobject_cast(sessionObject); qDebug() << "Session finished: " << session->title(Session::NameRole); Q_ASSERT( session ); _sessions.removeAll(session); session->deleteLater(); } QList SessionManager::availableProfiles() const { return _types.keys(); } Profile* SessionManager::profile(const QString& key) const { if ( key.isEmpty() ) return defaultProfile(); if ( _types.contains(key) ) return _types[key]; else return 0; } Profile* SessionManager::defaultProfile() const { return _types[_defaultProfile]; } QString SessionManager::defaultProfileKey() const { return _defaultProfile; } void SessionManager::saveProfile(const QString& path , Profile* info) { ProfileWriter* writer = new KDE4ProfileWriter; QString newPath = path; if ( newPath.isEmpty() ) newPath = writer->getPath(info); writer->writeProfile(newPath,info); delete writer; } void SessionManager::changeProfile(const QString& key , QHash propertyMap, bool persistant) { Profile* info = profile(key); if (!info || key.isEmpty()) { qWarning() << "Profile for key" << key << "not found."; return; } qDebug() << "Profile about to change: " << info->name(); // insert the changes into the existing Profile instance QListIterator iter(propertyMap.keys()); while ( iter.hasNext() ) { const Profile::Property property = iter.next(); info->setProperty(property,propertyMap[property]); } qDebug() << "Profile changed: " << info->name(); // apply the changes to existing sessions applyProfile(key,true); // notify the world about the change emit profileChanged(key); if ( persistant ) { // save the changes to disk // the path may be empty here, in which case it is up // to the profile writer to generate or request a path name if ( info->isPropertySet(Profile::Path) ) { qDebug() << "Profile saved to existing path: " << info->path(); saveProfile(info->path(),info); } else { qDebug() << "Profile saved to new path."; saveProfile(QString(),info); } } } void SessionManager::applyProfile(const QString& key , bool modifiedPropertiesOnly) { Profile* info = profile(key); QListIterator iter(_sessions); while ( iter.hasNext() ) { Session* next = iter.next(); if ( next->profileKey() == key ) applyProfile(next,info,modifiedPropertiesOnly); } } void SessionManager::applyProfile(Session* session, const Profile* info , bool modifiedPropertiesOnly) { QString key = _types.key((Profile*)info); if ( session->profileKey() != key ) session->setProfileKey(key); // Basic session settings if ( !modifiedPropertiesOnly || info->isPropertySet(Profile::Name) ) session->setTitle(Session::NameRole,info->name()); if ( !modifiedPropertiesOnly || info->isPropertySet(Profile::Command) ) session->setProgram(info->command()); if ( !modifiedPropertiesOnly || info->isPropertySet(Profile::Arguments) ) session->setArguments(info->arguments()); if ( !modifiedPropertiesOnly || info->isPropertySet(Profile::Directory) ) session->setInitialWorkingDirectory(info->defaultWorkingDirectory()); if ( !modifiedPropertiesOnly || info->isPropertySet(Profile::Icon) ) session->setIconName(info->icon()); // Key bindings if ( !modifiedPropertiesOnly || info->isPropertySet(Profile::KeyBindings) ) session->setKeyBindings(info->property(Profile::KeyBindings).value()); // Tab formats if ( !modifiedPropertiesOnly || info->isPropertySet(Profile::LocalTabTitleFormat) ) session->setTabTitleFormat( Session::LocalTabTitle , info->property(Profile::LocalTabTitleFormat).value()); if ( !modifiedPropertiesOnly || info->isPropertySet(Profile::RemoteTabTitleFormat) ) session->setTabTitleFormat( Session::RemoteTabTitle , info->property(Profile::RemoteTabTitleFormat).value()); // Scrollback / history if ( !modifiedPropertiesOnly || info->isPropertySet(Profile::HistoryMode) || info->isPropertySet(Profile::HistorySize) ) { int mode = info->property(Profile::HistoryMode).value(); switch ((Profile::HistoryModeEnum)mode) { case Profile::DisableHistory: session->setHistoryType( HistoryTypeNone() ); break; case Profile::FixedSizeHistory: { int lines = info->property(Profile::HistorySize).value(); session->setHistoryType( HistoryTypeBuffer(lines) ); } break; case Profile::UnlimitedHistory: session->setHistoryType( HistoryTypeFile() ); break; } } // Terminal features if ( !modifiedPropertiesOnly || info->isPropertySet(Profile::FlowControlEnabled) ) session->setFlowControlEnabled( info->property(Profile::FlowControlEnabled) .value() ); } QString SessionManager::addProfile(Profile* type) { QString key; for ( int counter = 0;;counter++ ) { if ( !_types.contains(type->path() + QString::number(counter)) ) { key = type->path() + QString::number(counter); break; } } if ( _types.isEmpty() ) _defaultProfile = key; _types.insert(key,type); emit profileAdded(key); return key; } void SessionManager::deleteProfile(const QString& key) { Profile* type = profile(key); setFavorite(key,false); bool wasDefault = ( type == defaultProfile() ); if ( type ) { // try to delete the config file if ( type->isPropertySet(Profile::Path) && QFile::exists(type->path()) ) { if (!QFile::remove(type->path())) { qWarning() << "Could not delete config file: " << type->path() << "The file is most likely in a directory which is read-only."; qWarning() << "TODO: Hide this file instead."; } } _types.remove(key); delete type; } // if we just deleted the default session type, // replace it with the first type in the list if ( wasDefault ) { setDefaultProfile( _types.keys().first() ); } emit profileRemoved(key); } void SessionManager::setDefaultProfile(const QString& key) { Q_ASSERT ( _types.contains(key) ); _defaultProfile = key; Profile* info = profile(key); QString path = info->path(); if ( path.isEmpty() ) path = KDE4ProfileWriter().getPath(info); QFileInfo fileInfo(path); qDebug() << "setting default session type to " << fileInfo.fileName(); KConfigGroup group = KGlobal::config()->group("Desktop Entry"); group.writeEntry("DefaultProfile",fileInfo.fileName()); } QSet SessionManager::findFavorites() { if (_favorites.isEmpty()) loadFavorites(); return _favorites; } void SessionManager::setFavorite(const QString& key , bool favorite) { Q_ASSERT( _types.contains(key) ); if ( favorite && !_favorites.contains(key) ) { qDebug() << "adding favorite - " << key; _favorites.insert(key); emit favoriteStatusChanged(key,favorite); saveFavorites(); } else if ( !favorite && _favorites.contains(key) ) { qDebug() << "removing favorite - " << key; _favorites.remove(key); emit favoriteStatusChanged(key,favorite); saveFavorites(); } } void SessionManager::loadShortcuts() { KSharedConfigPtr appConfig = KGlobal::config(); KConfigGroup shortcutGroup = appConfig->group("Profile Shortcuts"); QMap entries = shortcutGroup.entryMap(); QMapIterator iter(entries); while ( iter.hasNext() ) { iter.next(); QKeySequence shortcut = QKeySequence::fromString(iter.key()); QString profilePath = iter.value(); ShortcutData data; data.profilePath = profilePath; _shortcuts.insert(shortcut,data); } } void SessionManager::saveShortcuts() { KSharedConfigPtr appConfig = KGlobal::config(); KConfigGroup shortcutGroup = appConfig->group("Profile Shortcuts"); shortcutGroup.deleteGroup(); QMapIterator iter(_shortcuts); while ( iter.hasNext() ) { iter.next(); QString shortcutString = iter.key().toString(); shortcutGroup.writeEntry(shortcutString, iter.value().profilePath); } } void SessionManager::setShortcut(const QString& profileKey , const QKeySequence& keySequence ) { QKeySequence existingShortcut = shortcut(profileKey); _shortcuts.remove(existingShortcut); ShortcutData data; data.profileKey = profileKey; data.profilePath = profile(profileKey)->path(); // TODO - This won't work if the profile doesn't // have a path yet _shortcuts.insert(keySequence,data); } void SessionManager::loadFavorites() { KSharedConfigPtr appConfig = KGlobal::config(); KConfigGroup favoriteGroup = appConfig->group("Favorite Profiles"); qDebug() << "loading favorites"; if ( favoriteGroup.hasKey("Favorites") ) { qDebug() << "found favorites key"; QStringList list = favoriteGroup.readEntry("Favorites",QStringList()); qDebug() << "found " << list.count() << "entries"; QSet favoriteSet = QSet::fromList(list); // look for favorites amongst those already loaded QHashIterator iter(_types); while ( iter.hasNext() ) { iter.next(); const QString& path = iter.value()->path(); if ( favoriteSet.contains( path ) ) { _favorites.insert( iter.key() ); favoriteSet.remove(path); } } // load any remaining favorites QSetIterator unloadedFavoriteIter(favoriteSet); while ( unloadedFavoriteIter.hasNext() ) { const QString& key = loadProfile(unloadedFavoriteIter.next()); if (!key.isEmpty()) _favorites.insert(key); } } } void SessionManager::saveFavorites() { KSharedConfigPtr appConfig = KGlobal::config(); KConfigGroup favoriteGroup = appConfig->group("Favorite Profiles"); QStringList paths; QSetIterator keyIter(_favorites); while ( keyIter.hasNext() ) { const QString& key = keyIter.next(); Q_ASSERT( _types.contains(key) && profile(key) != 0 ); paths << profile(key)->path(); } favoriteGroup.writeEntry("Favorites",paths); } QList SessionManager::shortcuts() { return _shortcuts.keys(); } QString SessionManager::findByShortcut(const QKeySequence& shortcut) { Q_ASSERT( _shortcuts.contains(shortcut) ); if ( _shortcuts[shortcut].profileKey.isEmpty() ) { QString key = loadProfile(_shortcuts[shortcut].profilePath); _shortcuts[shortcut].profileKey = key; } return _shortcuts[shortcut].profileKey; } void SessionManager::sessionProfileChanged() { Session* session = qobject_cast(sender()); Q_ASSERT( session ); updateSession(session); } QKeySequence SessionManager::shortcut(const QString& profileKey) const { Profile* info = profile(profileKey); QMapIterator iter(_shortcuts); while (iter.hasNext()) { iter.next(); if ( iter.value().profileKey == profileKey || iter.value().profilePath == info->path() ) return iter.key(); } return QKeySequence(); } void SessionManager::setInstance(SessionManager* instance) { _instance = instance; } SessionManager* SessionManager::instance() { return _instance; } #include "SessionManager.moc"