-
-
Notifications
You must be signed in to change notification settings - Fork 455
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for opening links in incognito mode on Linux & BSD (#4745)
Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
- Loading branch information
Showing
18 changed files
with
739 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,112 +1,109 @@ | ||
#include "util/IncognitoBrowser.hpp" | ||
#ifdef USEWINSDK | ||
# include "util/WindowsHelper.hpp" | ||
#elif defined(Q_OS_UNIX) and !defined(Q_OS_DARWIN) | ||
# include "util/XDGHelper.hpp" | ||
#endif | ||
|
||
#include <QProcess> | ||
#include <QRegularExpression> | ||
#include <QVariant> | ||
|
||
namespace { | ||
|
||
using namespace chatterino; | ||
|
||
#ifdef USEWINSDK | ||
QString injectPrivateSwitch(QString command) | ||
QString getPrivateSwitch(const QString &browserExecutable) | ||
{ | ||
// list of command line switches to turn on private browsing in browsers | ||
static auto switches = std::vector<std::pair<QString, QString>>{ | ||
{"firefox", "-private-window"}, {"librewolf", "-private-window"}, | ||
{"waterfox", "-private-window"}, {"icecat", "-private-window"}, | ||
{"chrome", "-incognito"}, {"vivaldi", "-incognito"}, | ||
{"opera", "-newprivatetab"}, {"opera\\\\launcher", "--private"}, | ||
{"iexplore", "-private"}, {"msedge", "-inprivate"}, | ||
{"firefox", "-private-window"}, {"librewolf", "-private-window"}, | ||
{"waterfox", "-private-window"}, {"icecat", "-private-window"}, | ||
{"chrome", "-incognito"}, {"vivaldi", "-incognito"}, | ||
{"opera", "-newprivatetab"}, {"opera\\launcher", "--private"}, | ||
{"iexplore", "-private"}, {"msedge", "-inprivate"}, | ||
{"firefox-esr", "-private-window"}, {"chromium", "-incognito"}, | ||
}; | ||
|
||
// transform into regex and replacement string | ||
std::vector<std::pair<QRegularExpression, QString>> replacers; | ||
for (const auto &switch_ : switches) | ||
// compare case-insensitively | ||
auto lowercasedBrowserExecutable = browserExecutable.toLower(); | ||
|
||
#ifdef Q_OS_WINDOWS | ||
if (lowercasedBrowserExecutable.endsWith(".exe")) | ||
{ | ||
replacers.emplace_back( | ||
QRegularExpression("(" + switch_.first + "\\.exe\"?).*", | ||
QRegularExpression::CaseInsensitiveOption), | ||
"\\1 " + switch_.second); | ||
lowercasedBrowserExecutable.chop(4); | ||
} | ||
#endif | ||
|
||
// try to find matching regex and apply it | ||
for (const auto &replacement : replacers) | ||
for (const auto &switch_ : switches) | ||
{ | ||
if (replacement.first.match(command).hasMatch()) | ||
if (lowercasedBrowserExecutable.endsWith(switch_.first)) | ||
{ | ||
command.replace(replacement.first, replacement.second); | ||
return command; | ||
return switch_.second; | ||
} | ||
} | ||
|
||
// couldn't match any browser -> unknown browser | ||
return QString(); | ||
return {}; | ||
} | ||
|
||
QString getCommand() | ||
QString getDefaultBrowserExecutable() | ||
{ | ||
#ifdef USEWINSDK | ||
// get default browser start command, by protocol if possible, falling back to extension if not | ||
QString command = | ||
getAssociatedCommand(AssociationQueryType::Protocol, L"http"); | ||
getAssociatedExecutable(AssociationQueryType::Protocol, L"http"); | ||
|
||
if (command.isNull()) | ||
{ | ||
// failed to fetch default browser by protocol, try by file extension instead | ||
command = | ||
getAssociatedCommand(AssociationQueryType::FileExtension, L".html"); | ||
command = getAssociatedExecutable(AssociationQueryType::FileExtension, | ||
L".html"); | ||
} | ||
|
||
if (command.isNull()) | ||
{ | ||
// also try the equivalent .htm extension | ||
command = | ||
getAssociatedCommand(AssociationQueryType::FileExtension, L".htm"); | ||
} | ||
|
||
if (command.isNull()) | ||
{ | ||
// failed to find browser command | ||
return QString(); | ||
} | ||
|
||
// inject switch to enable private browsing | ||
command = injectPrivateSwitch(command); | ||
if (command.isNull()) | ||
{ | ||
return QString(); | ||
command = getAssociatedExecutable(AssociationQueryType::FileExtension, | ||
L".htm"); | ||
} | ||
|
||
return command; | ||
} | ||
#elif defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) | ||
static QString defaultBrowser = []() -> QString { | ||
auto desktopFile = getDefaultBrowserDesktopFile(); | ||
if (desktopFile.has_value()) | ||
{ | ||
auto entry = desktopFile->getEntries("Desktop Entry"); | ||
auto exec = entry.find("Exec"); | ||
if (exec != entry.end()) | ||
{ | ||
return parseDesktopExecProgram(exec->second.trimmed()); | ||
} | ||
} | ||
return {}; | ||
}(); | ||
|
||
return defaultBrowser; | ||
#else | ||
return {}; | ||
#endif | ||
} | ||
|
||
} // namespace | ||
|
||
namespace chatterino { | ||
|
||
bool supportsIncognitoLinks() | ||
{ | ||
#ifdef USEWINSDK | ||
return !getCommand().isNull(); | ||
#else | ||
return false; | ||
#endif | ||
auto browserExe = getDefaultBrowserExecutable(); | ||
return !browserExe.isNull() && !getPrivateSwitch(browserExe).isNull(); | ||
} | ||
|
||
bool openLinkIncognito(const QString &link) | ||
{ | ||
#ifdef USEWINSDK | ||
auto command = getCommand(); | ||
|
||
// TODO: split command into program path and incognito argument | ||
return QProcess::startDetached(command, {link}); | ||
#else | ||
return false; | ||
#endif | ||
auto browserExe = getDefaultBrowserExecutable(); | ||
return QProcess::startDetached(browserExe, | ||
{getPrivateSwitch(browserExe), link}); | ||
} | ||
|
||
} // namespace chatterino |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
#include "util/XDGDesktopFile.hpp" | ||
|
||
#include "util/XDGDirectory.hpp" | ||
|
||
#include <QDir> | ||
#include <QFile> | ||
|
||
#include <functional> | ||
|
||
#if defined(Q_OS_UNIX) and !defined(Q_OS_DARWIN) | ||
|
||
namespace chatterino { | ||
|
||
XDGDesktopFile::XDGDesktopFile(const QString &filename) | ||
{ | ||
QFile file(filename); | ||
if (!file.open(QIODevice::ReadOnly)) | ||
{ | ||
return; | ||
} | ||
this->valid = true; | ||
|
||
std::optional<std::reference_wrapper<XDGEntries>> entries; | ||
|
||
while (!file.atEnd()) | ||
{ | ||
auto lineBytes = file.readLine().trimmed(); | ||
|
||
// Ignore comments & empty lines | ||
if (lineBytes.startsWith('#') || lineBytes.size() == 0) | ||
{ | ||
continue; | ||
} | ||
|
||
auto line = QString::fromUtf8(lineBytes); | ||
|
||
if (line.startsWith('[')) | ||
{ | ||
// group header | ||
auto end = line.indexOf(']', 1); | ||
if (end == -1 || end == 1) | ||
{ | ||
// malformed header - either empty or no closing bracket | ||
continue; | ||
} | ||
auto groupName = line.mid(1, end - 1); | ||
|
||
// it is against spec for the group name to already exist, but the | ||
// parsing behavior for that case is not specified. operator[] will | ||
// result in duplicate groups being merged, which makes the most | ||
// sense for a read-only parser | ||
entries = this->groups[groupName]; | ||
|
||
continue; | ||
} | ||
|
||
// group entry | ||
if (!entries.has_value()) | ||
{ | ||
// no group header yet, entry before a group header is against spec | ||
// and should be ignored | ||
continue; | ||
} | ||
|
||
auto delimiter = line.indexOf('='); | ||
if (delimiter == -1) | ||
{ | ||
// line is not a group header or a key value pair, ignore it | ||
continue; | ||
} | ||
|
||
auto key = QStringView(line).left(delimiter).trimmed().toString(); | ||
// QStringView.mid() does not do bounds checking before qt 5.15, so | ||
// we have to do it ourselves | ||
auto valueStart = delimiter + 1; | ||
QString value; | ||
if (valueStart < line.size()) | ||
{ | ||
value = QStringView(line).mid(valueStart).trimmed().toString(); | ||
} | ||
|
||
// existing keys are against spec, so we can overwrite them with | ||
// wild abandon | ||
entries->get().emplace(key, value); | ||
} | ||
} | ||
|
||
XDGEntries XDGDesktopFile::getEntries(const QString &groupHeader) const | ||
{ | ||
auto group = this->groups.find(groupHeader); | ||
if (group != this->groups.end()) | ||
{ | ||
return group->second; | ||
} | ||
|
||
return {}; | ||
} | ||
|
||
std::optional<XDGDesktopFile> XDGDesktopFile::findDesktopFile( | ||
const QString &desktopFileID) | ||
{ | ||
for (const auto &dataDir : getXDGDirectories(XDGDirectoryType::Data)) | ||
{ | ||
auto fileName = | ||
QDir::cleanPath(dataDir + QDir::separator() + "applications" + | ||
QDir::separator() + desktopFileID); | ||
XDGDesktopFile desktopFile(fileName); | ||
if (desktopFile.isValid()) | ||
{ | ||
return desktopFile; | ||
} | ||
} | ||
return {}; | ||
} | ||
|
||
} // namespace chatterino | ||
|
||
#endif |
Oops, something went wrong.