/* This source file is part of Konsole, a terminal emulator. Copyright (C) 2006 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 // 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; QHash Profile::_propertyNames; FallbackProfile::FallbackProfile() : Profile(0) { // Fallback settings setProperty(Name,i18n("Shell")); setProperty(Command,getenv("SHELL")); setProperty(Icon,"konsole"); setProperty(Arguments,QStringList() << getenv("SHELL")); setProperty(LocalTabTitleFormat,"%d : %n"); setProperty(RemoteTabTitleFormat,"%H : %u"); setProperty(TabBarMode,AlwaysShowTabBar); setProperty(ShowMenuBar,true); setProperty(KeyBindings,"default"); setProperty(Font,QFont("Monospace")); setProperty(HistoryMode,FixedSizeHistory); setProperty(HistorySize,1000); setProperty(ScrollBarPosition,ScrollBarRight); setProperty(FlowControlEnabled,true); setProperty(AllowProgramsToResizeWindow,true); setProperty(BlinkingTextEnabled,true); setProperty(BlinkingCursorEnabled,false); setProperty(CursorShape,BlockCursor); setProperty(UseCustomCursorColor,false); setProperty(CustomCursorColor,Qt::black); // default taken from KDE 3 setProperty(WordCharacters,":@-./_~?&=%+#"); // Fallback should not be shown in menus setHidden(true); } Profile::Profile(Profile* parent) : _parent(parent) ,_hidden(false) { } bool Profile::isHidden() const { return _hidden; } void Profile::setHidden(bool hidden) { _hidden = hidden; } void Profile::setParent(Profile* parent) { _parent = parent; } const Profile* Profile::parent() const { return _parent; } bool Profile::isEmpty() const { return _propertyValues.isEmpty(); } QHash Profile::setProperties() const { return _propertyValues; } QVariant Profile::property(Property property) const { if ( _propertyValues.contains(property) ) return _propertyValues[property]; else if ( _parent ) return _parent->property(property); else return QVariant(); } void Profile::setProperty(Property property , const QVariant& value) { _propertyValues.insert(property,value); } bool Profile::isPropertySet(Property property) const { return _propertyValues.contains(property); } bool Profile::isNameRegistered(const QString& name) { return _propertyNames.contains(name); } Profile::Property Profile::lookupByName(const QString& name) { return _propertyNames[name]; } QList Profile::namesForProperty(Property property) { return _propertyNames.keys(property); } void Profile::registerName(Property property , const QString& name) { _propertyNames.insert(name,property); } QString KDE4ProfileWriter::getPath(const Profile* info) { QString newPath; if ( info->isPropertySet(Profile::Path) ) newPath=info->path(); // if the path is not specified, use the profile name + ".profile" if ( newPath.isEmpty() ) newPath = info->name() + ".profile"; QFileInfo fileInfo(newPath); if (!fileInfo.isAbsolute()) newPath = KGlobal::dirs()->saveLocation("data","konsole/") + newPath; qDebug() << "Saving profile under name: " << newPath; return newPath; } void KDE4ProfileWriter::writeStandardElement(KConfigGroup& group , char* name , const Profile* profile , Profile::Property attribute) { if ( profile->isPropertySet(attribute) ) group.writeEntry(name,profile->property(attribute)); } bool KDE4ProfileWriter::writeProfile(const QString& path , const Profile* profile) { KConfig config(path,KConfig::NoGlobals); // Basic Profile Settings KConfigGroup general = config.group("General"); if ( profile->isPropertySet(Profile::Name) ) general.writeEntry("Name",profile->name()); if ( profile->isPropertySet(Profile::Command) || profile->isPropertySet(Profile::Arguments) ) general.writeEntry("Command", ShellCommand(profile->command(),profile->arguments()).fullCommand()); if ( profile->isPropertySet(Profile::Directory) ) general.writeEntry("Directory",profile->defaultWorkingDirectory()); writeStandardElement( general , "Icon" , profile , Profile::Icon ); // Tab Titles writeStandardElement( general , "LocalTabTitleFormat" , profile , Profile::LocalTabTitleFormat ); writeStandardElement( general , "RemoteTabTitleFormat" , profile , Profile::RemoteTabTitleFormat ); // Menu and Tab Bar writeStandardElement( general , "TabBarMode" , profile , Profile::TabBarMode ); writeStandardElement( general , "ShowMenuBar" , profile , Profile::ShowMenuBar ); // Keyboard KConfigGroup keyboard = config.group("Keyboard"); writeStandardElement( keyboard , "KeyBindings" , profile , Profile::KeyBindings ); // Appearance KConfigGroup appearance = config.group("Appearance"); writeStandardElement( appearance , "ColorScheme" , profile , Profile::ColorScheme ); writeStandardElement( appearance , "Font" , profile , Profile::Font ); // Scrolling KConfigGroup scrolling = config.group("Scrolling"); writeStandardElement( scrolling , "HistoryMode" , profile , Profile::HistoryMode ); writeStandardElement( scrolling , "HistorySize" , profile , Profile::HistorySize ); writeStandardElement( scrolling , "ScrollBarPosition" , profile , Profile::ScrollBarPosition ); // Terminal Features KConfigGroup terminalFeatures = config.group("Terminal Features"); writeStandardElement( terminalFeatures , "FlowControl" , profile , Profile::FlowControlEnabled ); writeStandardElement( terminalFeatures , "BlinkingCursor" , profile , Profile::BlinkingCursorEnabled ); // Cursor KConfigGroup cursorOptions = config.group("Cursor Options"); writeStandardElement( cursorOptions , "UseCustomCursorColor" , profile , Profile::UseCustomCursorColor ); writeStandardElement( cursorOptions , "CustomCursorColor" , profile , Profile::CustomCursorColor ); writeStandardElement( cursorOptions , "CursorShape" , profile , Profile::CursorShape ); // Interaction KConfigGroup interactionOptions = config.group("Interaction Options"); writeStandardElement( interactionOptions , "WordCharacters" , profile , Profile::WordCharacters ); return true; } QStringList KDE4ProfileReader::findProfiles() { return KGlobal::dirs()->findAllResources("data","konsole/*.profile", KStandardDirs::NoDuplicates); } bool KDE4ProfileReader::readProfile(const QString& path , Profile* profile) { qDebug() << "KDE 4 Profile Reader:" << path; KConfig config(path,KConfig::NoGlobals); // general KConfigGroup general = config.group("General"); if ( general.hasKey("Name") ) profile->setProperty(Profile::Name,general.readEntry("Name")); else return false; if ( general.hasKey("Command") ) { ShellCommand shellCommand(general.readEntry("Command")); profile->setProperty(Profile::Command,shellCommand.command()); profile->setProperty(Profile::Arguments,shellCommand.arguments()); } readStandardElement(general,"Directory",profile,Profile::Directory); readStandardElement(general,"Icon",profile,Profile::Icon); readStandardElement(general,"LocalTabTitleFormat",profile,Profile::LocalTabTitleFormat); readStandardElement(general,"RemoteTabTitleFormat",profile,Profile::RemoteTabTitleFormat); readStandardElement(general,"TabBarMode",profile,Profile::TabBarMode); readStandardElement(general,"ShowMenuBar",profile,Profile::ShowMenuBar); // keyboard KConfigGroup keyboard = config.group("Keyboard"); readStandardElement(keyboard,"KeyBindings",profile,Profile::KeyBindings); // appearence KConfigGroup appearance = config.group("Appearance"); readStandardElement(appearance,"ColorScheme",profile,Profile::ColorScheme); readStandardElement(appearance,"Font",profile,Profile::Font); // scrolling KConfigGroup scrolling = config.group("Scrolling"); readStandardElement(scrolling,"HistoryMode",profile,Profile::HistoryMode); readStandardElement(scrolling,"HistorySize",profile,Profile::HistorySize); readStandardElement(scrolling,"ScrollBarPosition",profile,Profile::ScrollBarPosition); // terminal features KConfigGroup terminalFeatures = config.group("Terminal Features"); readStandardElement(terminalFeatures,"FlowControl",profile,Profile::FlowControlEnabled); readStandardElement(terminalFeatures,"BlinkingCursor",profile,Profile::BlinkingCursorEnabled); // cursor settings KConfigGroup cursorOptions = config.group("Cursor Options"); readStandardElement(cursorOptions,"UseCustomCursorColor",profile,Profile::UseCustomCursorColor); readStandardElement(cursorOptions,"CustomCursorColor",profile,Profile::CustomCursorColor); readStandardElement(cursorOptions,"CursorShape",profile,Profile::CursorShape); // interaction options KConfigGroup interactionOptions = config.group("Interaction Options"); readStandardElement(interactionOptions,"WordCharacters",profile,Profile::WordCharacters); return true; } template void KDE4ProfileReader::readStandardElement(const KConfigGroup& group , char* name , Profile* info , Profile::Property property) { if ( group.hasKey(name) ) info->setProperty(property,group.readEntry(name,T())); } QStringList KDE3ProfileReader::findProfiles() { return KGlobal::dirs()->findAllResources("data", "konsole/*.desktop", KStandardDirs::NoDuplicates); } bool KDE3ProfileReader::readProfile(const QString& path , Profile* profile) { if (!QFile::exists(path)) return false; KDesktopFile* desktopFile = new KDesktopFile(path); KConfigGroup* config = new KConfigGroup( desktopFile->desktopGroup() ); if ( config->hasKey("Name") ) profile->setProperty(Profile::Name,config->readEntry("Name")); qDebug() << "reading KDE 3 profile " << profile->name(); if ( config->hasKey("Icon") ) profile->setProperty(Profile::Icon,config->readEntry("Icon")); if ( config->hasKey("Exec") ) { const QString& fullCommand = config->readEntry("Exec"); ShellCommand shellCommand(fullCommand); profile->setProperty(Profile::Command,shellCommand.command()); profile->setProperty(Profile::Arguments,shellCommand.arguments()); } if ( config->hasKey("Schema") ) { profile->setProperty(Profile::ColorScheme,config->readEntry("Schema").replace (".schema",QString())); } if ( config->hasKey("defaultfont") ) { profile->setProperty(Profile::Font,config->readEntry("defaultfont")); } if ( config->hasKey("KeyTab") ) { profile->setProperty(Profile::KeyBindings,config->readEntry("KeyTab")); } if ( config->hasKey("Term") ) { profile->setProperty(Profile::Environment, QStringList() << "TERM="+config->readEntry("Term")); } if ( config->hasKey("Cwd") ) { profile->setProperty(Profile::Directory,config->readEntry("Cwd")); } delete desktopFile; delete config; return true; } #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) { //load fallback profile // addProfile( new FallbackProfile ); 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() ); // now that the session types have been loaded, // get the list of favorite sessions //loadFavorites(); } 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 QString(); } // 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 ); // free profiles QListIterator infoIter(_types.values()); while (infoIter.hasNext()) delete infoIter.next(); } const QList SessionManager::sessions() { return _sessions; } 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->setType(key); applyProfile(session,info,false); //ask for notification when session dies connect( session , SIGNAL(done(Session*)) , SLOT(sessionTerminated(Session*)) ); //add session to active list _sessions << session; Q_ASSERT( session ); return session; } void SessionManager::sessionTerminated(Session* 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->type() == key ) applyProfile(next,info,modifiedPropertiesOnly); } } void SessionManager::applyProfile(Session* session, const Profile* info , bool modifiedPropertiesOnly) { session->setType( _types.key((Profile*)info) ); // Basic session settings if ( !modifiedPropertiesOnly || info->isPropertySet(Profile::Name) ) session->setTitle(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->setKeymap(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->setHistory( HistoryTypeNone() ); break; case Profile::FixedSizeHistory: { int lines = info->property(Profile::HistorySize).value(); session->setHistory( HistoryTypeBuffer(lines) ); } break; case Profile::UnlimitedHistory: session->setHistory( HistoryTypeFile() ); break; } } // Terminal features if ( !modifiedPropertiesOnly || info->isPropertySet(Profile::FlowControlEnabled) ) session->setXonXoff( 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::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); } void SessionManager::setInstance(SessionManager* instance) { _instance = instance; } SessionManager* SessionManager::instance() { return _instance; } #include "SessionManager.moc"