File indexing completed on 2024-04-28 05:50:44

0001 /*
0002     This source file is part of Konsole, a terminal emulator.
0003 
0004     SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
0005     SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 // Own
0011 #include "Profile.h"
0012 
0013 // Qt
0014 #include <QDir>
0015 #include <QFileInfo>
0016 #include <QTextCodec>
0017 
0018 // KDE
0019 #include <KLocalizedString>
0020 #include <QFontDatabase>
0021 
0022 // Konsole
0023 #include "Enumeration.h"
0024 #include "ProfileGroup.h"
0025 #include "config-konsole.h"
0026 
0027 #ifndef Q_OS_WIN
0028 #ifdef HAVE_GETPWUID
0029 #include <pwd.h>
0030 #include <sys/types.h>
0031 #include <unistd.h>
0032 #endif // HAVE_GETPWUID
0033 #endif // Q_OS_WIN
0034 
0035 #include <KSandbox>
0036 
0037 using namespace Konsole;
0038 
0039 #if defined(Q_OS_WIN)
0040 #define DEFAULT_ENCODING QStringLiteral("utf8")
0041 #else
0042 #define DEFAULT_ENCODING QString()
0043 #endif
0044 
0045 // mappings between property enum values and names
0046 //
0047 // multiple names are defined for some property values,
0048 // in these cases, the "proper" string name comes first,
0049 // as that is used when reading/writing profiles from/to disk
0050 //
0051 // the other names are usually shorter versions for convenience
0052 // when parsing konsoleprofile commands
0053 static const char GENERAL_GROUP[] = "General";
0054 static const char KEYBOARD_GROUP[] = "Keyboard";
0055 static const char APPEARANCE_GROUP[] = "Appearance";
0056 static const char SCROLLING_GROUP[] = "Scrolling";
0057 static const char TERMINAL_GROUP[] = "Terminal Features";
0058 static const char CURSOR_GROUP[] = "Cursor Options";
0059 static const char INTERACTION_GROUP[] = "Interaction Options";
0060 static const char ENCODING_GROUP[] = "Encoding Options";
0061 
0062 const std::vector<Profile::PropertyInfo> Profile::DefaultProperties = {
0063     // General
0064     {Path, "Path", nullptr, QString()},
0065     {Name, "Name", GENERAL_GROUP, QString()},
0066     {UntranslatedName, "UntranslatedName", nullptr, QString()},
0067     {Icon, "Icon", GENERAL_GROUP, QLatin1String("utilities-terminal")},
0068     {Command, "Command", nullptr, QString()},
0069     {Arguments, "Arguments", nullptr, QStringList()},
0070     {MenuIndex, "MenuIndex", nullptr, QLatin1String("0")},
0071     {Environment, "Environment", GENERAL_GROUP, QStringList{QLatin1String("TERM=xterm-256color"), QLatin1String("COLORTERM=truecolor")}},
0072     {Directory, "Directory", GENERAL_GROUP, QString()},
0073     {LocalTabTitleFormat, "LocalTabTitleFormat", GENERAL_GROUP, QLatin1String("%d : %n")},
0074     {LocalTabTitleFormat, "tabtitle", nullptr, QLatin1String("%d : %n")},
0075     {RemoteTabTitleFormat, "RemoteTabTitleFormat", GENERAL_GROUP, QLatin1String("(%u) %H")},
0076     {SemanticHints, "SemanticHints", GENERAL_GROUP, 1},
0077     {SemanticUpDown, "SemanticUpDown", GENERAL_GROUP, false},
0078     {SemanticInputClick, "SemanticInputClick", GENERAL_GROUP, false},
0079     {ShowTerminalSizeHint, "ShowTerminalSizeHint", GENERAL_GROUP, true},
0080     {StartInCurrentSessionDir, "StartInCurrentSessionDir", GENERAL_GROUP, true},
0081     {SilenceSeconds, "SilenceSeconds", GENERAL_GROUP, 10},
0082     {TerminalColumns, "TerminalColumns", GENERAL_GROUP, 110},
0083     {TerminalRows, "TerminalRows", GENERAL_GROUP, 28},
0084     {TerminalMargin, "TerminalMargin", GENERAL_GROUP, 1},
0085     {TerminalCenter, "TerminalCenter", GENERAL_GROUP, false},
0086     {ErrorBars, "ErrorBars", GENERAL_GROUP, 2},
0087     {ErrorBackground, "ErrorBackground", GENERAL_GROUP, 1},
0088     {AlternatingBars, "AlternatingBars", GENERAL_GROUP, 2},
0089     {AlternatingBackground, "AlternatingBackground", GENERAL_GROUP, 1},
0090 
0091     // Appearance
0092     {Font, "Font", APPEARANCE_GROUP, QFont()},
0093     {ColorScheme, "ColorScheme", APPEARANCE_GROUP, QLatin1String("Breeze")},
0094     {ColorScheme, "colors", nullptr, QLatin1String("Breeze")},
0095     {AntiAliasFonts, "AntiAliasFonts", APPEARANCE_GROUP, true},
0096     {BoldIntense, "BoldIntense", APPEARANCE_GROUP, true},
0097     {UseFontLineCharacters, "UseFontLineChararacters", APPEARANCE_GROUP, false},
0098     {LineSpacing, "LineSpacing", APPEARANCE_GROUP, 0},
0099     {TabColor, "TabColor", APPEARANCE_GROUP, QColor(QColor::Invalid)},
0100     {DimValue, "DimmValue", APPEARANCE_GROUP, 128},
0101     {DimWhenInactive, "DimWhenInactive", GENERAL_GROUP, false},
0102     {BorderWhenActive, "BorderWhenActive", APPEARANCE_GROUP, false},
0103     {FocusBorderColor, "FocusBorderColor", APPEARANCE_GROUP, QColor(Qt::gray)},
0104     {InvertSelectionColors, "InvertSelectionColors", GENERAL_GROUP, false},
0105     {EmojiFont, "EmojiFont", APPEARANCE_GROUP, QFont()},
0106     {WordMode, "WordMode", APPEARANCE_GROUP, true},
0107     {WordModeAttr, "WordModeAttr", APPEARANCE_GROUP, false},
0108     {WordModeAscii, "WordModeAscii", APPEARANCE_GROUP, true},
0109     {WordModeBrahmic, "WordModeBrahmic", APPEARANCE_GROUP, false},
0110     {IgnoreWcWidth, "IgnoreWcWidth", APPEARANCE_GROUP, false},
0111 
0112 // Keyboard
0113 #ifdef Q_OS_MACOS
0114     {KeyBindings, "KeyBindings", KEYBOARD_GROUP, QLatin1String("macos")},
0115 #else
0116     {KeyBindings, "KeyBindings", KEYBOARD_GROUP, QLatin1String("default")},
0117 #endif
0118 
0119     // Scrolling
0120     {HistoryMode, "HistoryMode", SCROLLING_GROUP, Enum::FixedSizeHistory},
0121     {HistorySize, "HistorySize", SCROLLING_GROUP, 1000},
0122     {ScrollBarPosition, "ScrollBarPosition", SCROLLING_GROUP, Enum::ScrollBarRight},
0123     {ScrollFullPage, "ScrollFullPage", SCROLLING_GROUP, false},
0124     {HighlightScrolledLines, "HighlightScrolledLines", SCROLLING_GROUP, true},
0125     {ReflowLines, "ReflowLines", SCROLLING_GROUP, true},
0126 
0127     // Terminal Features
0128     {UrlHintsModifiers, "UrlHintsModifiers", TERMINAL_GROUP, 0},
0129     {ReverseUrlHints, "ReverseUrlHints", TERMINAL_GROUP, false},
0130     {BlinkingTextEnabled, "BlinkingTextEnabled", TERMINAL_GROUP, true},
0131     {FlowControlEnabled, "FlowControlEnabled", TERMINAL_GROUP, true},
0132     {BidiRenderingEnabled, "BidiRenderingEnabled", TERMINAL_GROUP, true},
0133     {BidiLineLTR, "BidiLineLTR", TERMINAL_GROUP, true},
0134     {BidiTableDirOverride, "BidiTableDirOverride", TERMINAL_GROUP, true},
0135     {BlinkingCursorEnabled, "BlinkingCursorEnabled", TERMINAL_GROUP, false},
0136     {BellMode, "BellMode", TERMINAL_GROUP, Enum::NotifyBell},
0137     {VerticalLine, "VerticalLine", TERMINAL_GROUP, false},
0138     {VerticalLineAtChar, "VerticalLineAtChar", TERMINAL_GROUP, 80},
0139     {PeekPrimaryKeySequence, "PeekPrimaryKeySequence", TERMINAL_GROUP, QString()},
0140     {LineNumbers, "LineNumbers", TERMINAL_GROUP, 0},
0141 
0142     // Cursor
0143     {UseCustomCursorColor, "UseCustomCursorColor", CURSOR_GROUP, false},
0144     {CursorShape, "CursorShape", CURSOR_GROUP, Enum::BlockCursor},
0145     {CustomCursorColor, "CustomCursorColor", CURSOR_GROUP, QColor(Qt::white)},
0146     {CustomCursorTextColor, "CustomCursorTextColor", CURSOR_GROUP, QColor(Qt::black)},
0147 
0148     // Interaction
0149     {WordCharacters, "WordCharacters", INTERACTION_GROUP, QLatin1String(":@-./_~?&=%+#")},
0150     {TripleClickMode, "TripleClickMode", INTERACTION_GROUP, Enum::SelectWholeLine},
0151     {UnderlineLinksEnabled, "UnderlineLinksEnabled", INTERACTION_GROUP, true},
0152     {UnderlineFilesEnabled, "UnderlineFilesEnabled", INTERACTION_GROUP, false},
0153     {OpenLinksByDirectClickEnabled, "OpenLinksByDirectClickEnabled", INTERACTION_GROUP, false},
0154     {TextEditorCmd, "TextEditorCmd", INTERACTION_GROUP, Enum::Kate},
0155     {TextEditorCmdCustom, "TextEditorCmdCustom", INTERACTION_GROUP, QLatin1String("kate PATH:LINE:COLUMN")},
0156     {CtrlRequiredForDrag, "CtrlRequiredForDrag", INTERACTION_GROUP, true},
0157     {DropUrlsAsText, "DropUrlsAsText", INTERACTION_GROUP, true},
0158     {AutoCopySelectedText, "AutoCopySelectedText", INTERACTION_GROUP, false},
0159     {CopyTextAsHTML, "CopyTextAsHTML", INTERACTION_GROUP, true},
0160     {TrimLeadingSpacesInSelectedText, "TrimLeadingSpacesInSelectedText", INTERACTION_GROUP, false},
0161     {TrimTrailingSpacesInSelectedText, "TrimTrailingSpacesInSelectedText", INTERACTION_GROUP, false},
0162     {PasteFromSelectionEnabled, "PasteFromSelectionEnabled", INTERACTION_GROUP, true},
0163     {PasteFromClipboardEnabled, "PasteFromClipboardEnabled", INTERACTION_GROUP, false},
0164     {MiddleClickPasteMode, "MiddleClickPasteMode", INTERACTION_GROUP, Enum::PasteFromX11Selection},
0165     {MouseWheelZoomEnabled, "MouseWheelZoomEnabled", INTERACTION_GROUP, true},
0166     {AllowMouseTracking, "AllowMouseTracking", INTERACTION_GROUP, true},
0167     {AlternateScrolling, "AlternateScrolling", INTERACTION_GROUP, true},
0168     {AllowEscapedLinks, "AllowEscapedLinks", INTERACTION_GROUP, false},
0169     {EscapedLinksSchema, "EscapedLinksSchema", INTERACTION_GROUP, QLatin1String("http://;https://;file://")},
0170     {ColorFilterEnabled, "ColorFilterEnabled", INTERACTION_GROUP, true},
0171 
0172     // Encoding
0173     {DefaultEncoding, "DefaultEncoding", ENCODING_GROUP, DEFAULT_ENCODING},
0174 };
0175 
0176 QHash<QString, Profile::PropertyInfo> Profile::PropertyInfoByName;
0177 
0178 // Magic path for the built-in profile which is not a valid file name,
0179 // thus it can not interfere with regular profiles.
0180 // For backward compatibility with existing profiles, it should never change.
0181 static const QString BUILTIN_MAGIC_PATH = QStringLiteral("FALLBACK/");
0182 
0183 #ifdef Q_OS_WIN
0184 static QString checkFile(const QStringList &dirList, const QString &filePath)
0185 {
0186     for (const QString &root : dirList) {
0187         QFileInfo info(root, filePath);
0188         if (info.exists()) {
0189             return QDir::toNativeSeparators(info.filePath());
0190         }
0191     }
0192     return QString();
0193 }
0194 
0195 static QString GetWindowPowerShell()
0196 {
0197     QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
0198     QStringList dirList;
0199     dirList << env.value(QStringLiteral("windir"), QStringLiteral("C:\\Windows"));
0200     return checkFile(dirList, QStringLiteral("System32\\WindowsPowerShell\\v1.0\\powershell.exe"));
0201 }
0202 
0203 static QString GetWindowsShell()
0204 {
0205     QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
0206     QString windir = env.value(QStringLiteral("windir"), QStringLiteral("C:\\Windows"));
0207     QFileInfo info(windir, QStringLiteral("System32\\cmd.exe"));
0208     return QDir::toNativeSeparators(info.filePath());
0209 }
0210 #endif
0211 
0212 static QString defaultShell()
0213 {
0214 #ifndef Q_OS_WIN
0215     if (!KSandbox::isFlatpak()) {
0216         return QString::fromUtf8(qgetenv("SHELL"));
0217     }
0218 
0219 #ifdef HAVE_GETPWUID
0220     auto pw = getpwuid(getuid());
0221     // pw: Do not pass the returned pointer to free.
0222     if (pw != nullptr) {
0223         QProcess proc;
0224         proc.setProgram(QStringLiteral("getent"));
0225         proc.setArguments({QStringLiteral("passwd"), QString::number(pw->pw_uid)});
0226         KSandbox::startHostProcess(proc);
0227         proc.waitForFinished();
0228         const auto output = proc.readAllStandardOutput().simplified();
0229         if (auto parts = QString::fromUtf8(output).split(QLatin1Char(':')); output.size() >= 7) {
0230             return parts.at(6);
0231         }
0232     }
0233     return {};
0234 #endif // HAVE_GETPWUID
0235 
0236 #else // Q_OS_WIN
0237     auto shell = GetWindowPowerShell();
0238     if (shell.isEmpty()) {
0239         shell = GetWindowsShell();
0240     }
0241     return shell;
0242 #endif // Q_OS_WIN
0243 }
0244 
0245 void Profile::fillTableWithDefaultNames()
0246 {
0247     static bool filledDefaults = false;
0248 
0249     if (filledDefaults) {
0250         return;
0251     }
0252 
0253     std::for_each(DefaultProperties.cbegin(), DefaultProperties.cend(), Profile::registerProperty);
0254 
0255     filledDefaults = true;
0256 }
0257 
0258 void Profile::useBuiltin()
0259 {
0260     for (const PropertyInfo &propInfo : DefaultProperties) {
0261         setProperty(propInfo.property, propInfo.defaultValue);
0262     }
0263     setProperty(Name, i18nc("Name of the built-in profile", "Built-in"));
0264     setProperty(UntranslatedName, QStringLiteral("Built-in"));
0265     setProperty(Path, BUILTIN_MAGIC_PATH);
0266     setProperty(Command, defaultShell());
0267     // See Pty.cpp on why Arguments is populated
0268     setProperty(Arguments, QStringList{defaultShell()});
0269     setProperty(Font, QFontDatabase::systemFont(QFontDatabase::FixedFont));
0270 #if defined(Q_OS_WIN)
0271     setProperty(DefaultEncoding, QLatin1String(QTextCodec::codecForName("utf8")->name()));
0272 #else
0273     setProperty(DefaultEncoding, QLatin1String(QTextCodec::codecForLocale()->name()));
0274 #endif
0275     // Built-in profile should not be shown in menus
0276     setHidden(true);
0277 }
0278 
0279 Profile::Profile(const Profile::Ptr &parent)
0280     : _propertyValues(PropertyMap())
0281     , _parent(parent)
0282     , _hidden(false)
0283 {
0284 }
0285 
0286 void Profile::clone(Profile::Ptr profile, bool differentOnly)
0287 {
0288     for (const PropertyInfo &info : DefaultProperties) {
0289         Property current = info.property;
0290         QVariant otherValue = profile->property<QVariant>(current);
0291         if (current == Name || current == Path) { // These are unique per Profile
0292             continue;
0293         }
0294         if (!differentOnly || property<QVariant>(current) != otherValue) {
0295             setProperty(current, otherValue);
0296         }
0297     }
0298 }
0299 
0300 Profile::~Profile() = default;
0301 
0302 bool Profile::isBuiltin() const
0303 {
0304     return path() == BUILTIN_MAGIC_PATH;
0305 }
0306 
0307 bool Profile::isEditable() const
0308 {
0309     // Read-only profiles (i.e. with non-user-writable .profile location)
0310     // aren't editable. This includes the built-in profile, which is hardcoded.
0311     return !isBuiltin() && QFileInfo(path()).isWritable();
0312 }
0313 
0314 bool Profile::isDeletable() const
0315 {
0316     // To delete a file, parent dir must be writable
0317     const QFileInfo fileInfo(path());
0318     return !isBuiltin() && fileInfo.exists() && QFileInfo(fileInfo.path()).isWritable();
0319 }
0320 
0321 bool Profile::isHidden() const
0322 {
0323     return _hidden;
0324 }
0325 void Profile::setHidden(bool hidden)
0326 {
0327     _hidden = hidden;
0328 }
0329 
0330 void Profile::setParent(const Profile::Ptr &parent)
0331 {
0332     _parent = parent;
0333 }
0334 const Profile::Ptr Profile::parent() const
0335 {
0336     return _parent;
0337 }
0338 
0339 bool Profile::isEmpty() const
0340 {
0341     return _propertyValues.empty();
0342 }
0343 
0344 const Profile::PropertyMap &Profile::properties() const
0345 {
0346     return _propertyValues;
0347 }
0348 
0349 void Profile::setProperty(Property p, const QVariant &value)
0350 {
0351     _propertyValues.insert_or_assign(p, value);
0352 }
0353 
0354 void Profile::setProperty(Property p, QVariant &&value)
0355 {
0356     _propertyValues.insert_or_assign(p, value);
0357 }
0358 
0359 void Profile::assignProperties(const PropertyMap &map)
0360 {
0361     for (const auto &[p, value] : map) {
0362         setProperty(p, value);
0363     }
0364 }
0365 
0366 void Profile::assignProperties(PropertyMap &&map)
0367 {
0368     // If a key exists in both maps, we want to use the associated
0369     // value from 'map'
0370     map.merge(_propertyValues);
0371     _propertyValues.swap(map);
0372 }
0373 
0374 bool Profile::isPropertySet(Property p) const
0375 {
0376     return _propertyValues.find(p) != _propertyValues.cend();
0377 }
0378 
0379 Profile::Property Profile::lookupByName(const QString &name)
0380 {
0381     // insert default names into table the first time this is called
0382     fillTableWithDefaultNames();
0383 
0384     return PropertyInfoByName[name.toLower()].property;
0385 }
0386 
0387 void Profile::registerProperty(const PropertyInfo &info)
0388 {
0389     QString name = QLatin1String(info.name);
0390     PropertyInfoByName.insert(name.toLower(), info);
0391 }
0392 
0393 // static
0394 const std::vector<std::string> &Profile::propertiesInfoList()
0395 {
0396     static std::vector<std::string> list;
0397     if (!list.empty()) {
0398         return list;
0399     }
0400 
0401     list.reserve(DefaultProperties.size());
0402     for (const PropertyInfo &info : DefaultProperties) {
0403         list.push_back(std::string(info.name) + " : " + info.defaultValue.typeName());
0404     }
0405     return list;
0406 }
0407 
0408 const Profile::GroupPtr Profile::asGroup() const
0409 {
0410     const Profile::GroupPtr ptr(dynamic_cast<ProfileGroup *>(const_cast<Profile *>(this)));
0411     return ptr;
0412 }
0413 
0414 Profile::GroupPtr Profile::asGroup()
0415 {
0416     return Profile::GroupPtr(dynamic_cast<ProfileGroup *>(this));
0417 }
0418 
0419 QString Profile::textEditorCmd() const
0420 {
0421     auto current = property<int>(Profile::TextEditorCmd);
0422 
0423     QString editorCmd;
0424     switch (current) {
0425     case Enum::Kate:
0426         editorCmd = QStringLiteral("kate PATH:LINE:COLUMN");
0427         break;
0428     case Enum::KWrite:
0429         editorCmd = QStringLiteral("kwrite PATH:LINE:COLUMN");
0430         break;
0431     case Enum::KDevelop:
0432         editorCmd = QStringLiteral("kdevelop PATH:LINE:COLUMN");
0433         break;
0434     case Enum::QtCreator:
0435         editorCmd = QStringLiteral("qtcreator PATH:LINE:COLUMN");
0436         break;
0437     case Enum::Gedit:
0438         editorCmd = QStringLiteral("gedit +LINE:COLUMN PATH");
0439         break;
0440     case Enum::gVim:
0441         editorCmd = QStringLiteral("gvim +LINE PATH");
0442         break;
0443     case Enum::CustomTextEditor:
0444         editorCmd = customTextEditorCmd();
0445         break;
0446     default:
0447         break;
0448     }
0449 
0450     return editorCmd;
0451 }
0452 
0453 #include "moc_Profile.cpp"