Combine startUrls and sendPaths

This commit allows to handle multiple URLs of all types as positional arguments simultaneously:
* tg:// links
* tonsite:// links
* interpret:// file paths
* generic file paths (to share files)

This allows to Drag'n'Drop files to the Telegram shortcut/binary.
This commit is contained in:
Ilya Fedin
2025-10-15 18:36:54 +00:00
committed by John Preston
parent f787e0fa1d
commit 5fbf280e4a
9 changed files with 90 additions and 152 deletions

View File

@@ -693,33 +693,26 @@ bool Application::eventFilter(QObject *object, QEvent *e) {
case QEvent::FileOpen: {
if (object == QCoreApplication::instance()) {
const auto event = static_cast<QFileOpenEvent*>(e);
const auto flushQueued = [=] {
if (_filesToOpen.isEmpty()) {
InvokeQueued(this, [=] {
cSetSendPaths(_filesToOpen);
_filesToOpen.clear();
checkSendPaths();
});
}
};
if (const auto file = event->file(); !file.isEmpty()) {
flushQueued();
_filesToOpen.append(file);
} else if (event->url().scheme() == u"tg"_q
|| event->url().scheme() == u"tonsite"_q) {
const auto url = QString::fromUtf8(
event->url().toEncoded().trimmed());
cSetStartUrl(url.mid(0, 8192));
checkStartUrl();
if (_lastActivePrimaryWindow
&& StartUrlRequiresActivate(url)) {
_lastActivePrimaryWindow->activate();
}
} else if (event->url().scheme() == u"interpret"_q) {
flushQueued();
_filesToOpen.append(event->url().toString());
if (_urlsToOpen.isEmpty()) {
InvokeQueued(this, [=] {
const auto activateRequired = ranges::any_of(
ranges::views::all(
_urlsToOpen
) | ranges::views::transform([](const QUrl &url) {
return url.toString();
}),
StartUrlRequiresActivate);
cRefStartUrls() << base::take(_urlsToOpen);
checkStartUrls();
if (_lastActivePrimaryWindow && activateRequired) {
_lastActivePrimaryWindow->activate();
}
});
}
const auto event = static_cast<QFileOpenEvent*>(e);
_urlsToOpen << event->url().toString(QUrl::FullyEncoded).mid(
0,
8192);
}
} break;
@@ -1091,31 +1084,27 @@ bool Application::canApplyLangPackWithoutRestart() const {
return true;
}
void Application::checkSendPaths() {
if (!cSendPaths().isEmpty()
void Application::checkStartUrls() {
if (!Core::App().passcodeLocked()) {
cRefStartUrls() = ranges::views::all(
cRefStartUrls()
) | ranges::views::filter([&](const QUrl &url) {
if (url.scheme() == u"tonsite"_q) {
iv().showTonSite(url.toString(), {});
return false;
} else if (_lastActivePrimaryWindow) {
return !openLocalUrl(url.toString(), {});
}
return true;
}) | ranges::to<QList<QUrl>>;
}
if (!cRefStartUrls().isEmpty()
&& _lastActivePrimaryWindow
&& !_lastActivePrimaryWindow->locked()) {
_lastActivePrimaryWindow->widget()->sendPaths();
}
}
void Application::checkStartUrl() {
if (!cStartUrl().isEmpty()) {
const auto url = cStartUrl();
if (!Core::App().passcodeLocked()) {
if (url.startsWith("tonsite://", Qt::CaseInsensitive)) {
cSetStartUrl(QString());
iv().showTonSite(url, {});
} else if (_lastActivePrimaryWindow) {
cSetStartUrl(QString());
if (!openLocalUrl(url, {})) {
cSetStartUrl(url);
}
}
}
}
}
bool Application::openLocalUrl(const QString &url, QVariant context) {
return openCustomUrl("tg://", LocalUrlHandlers(), url, context);
}

View File

@@ -270,8 +270,7 @@ public:
}
// Internal links.
void checkStartUrl();
void checkSendPaths();
void checkStartUrls();
bool openLocalUrl(const QString &url, QVariant context);
bool openInternalUrl(const QString &url, QVariant context);
[[nodiscard]] QString changelogLink() const;
@@ -450,7 +449,7 @@ private:
crl::time _shouldLockAt = 0;
base::Timer _autoLockTimer;
QStringList _filesToOpen;
QList<QUrl> _urlsToOpen;
std::optional<base::Timer> _saveSettingsTimer;

View File

@@ -536,9 +536,8 @@ void Launcher::processArguments() {
{ "-tosettings" , KeyFormat::NoValues },
{ "-startintray" , KeyFormat::NoValues },
{ "-quit" , KeyFormat::NoValues },
{ "-sendpath" , KeyFormat::AllLeftValues },
{ "-workdir" , KeyFormat::OneValue },
{ "--" , KeyFormat::OneValue },
{ "--" , KeyFormat::AllLeftValues },
{ "-scale" , KeyFormat::OneValue },
};
auto parseResult = QMap<QByteArray, QStringList>();
@@ -585,19 +584,15 @@ void Launcher::processArguments() {
gStartToSettings = parseResult.contains("-tosettings");
gStartInTray = parseResult.contains("-startintray");
gQuit = parseResult.contains("-quit");
gSendPaths = parseResult.value("-sendpath", {});
_customWorkingDir = parseResult.value("-workdir", {}).join(QString());
if (!_customWorkingDir.isEmpty()) {
_customWorkingDir = QDir(_customWorkingDir).absolutePath() + '/';
}
auto startUrls = parseResult.value("--", {});
startUrls = ranges::views::filter(startUrls, [](const QString &url) {
return QUrl::fromUserInput(url).isValid();
}) | ranges::to<QStringList>;
if (!startUrls.isEmpty()) {
gStartUrl = startUrls[0];
}
const auto startUrls = parseResult.value("--", {});
gStartUrls = startUrls | ranges::views::transform([&](const QString &url) {
return QUrl::fromUserInput(url, _initialWorkingDir);
}) | ranges::views::filter(&QUrl::isValid) | ranges::to<QList<QUrl>>;
const auto scaleKey = parseResult.value("-scale", {});
if (scaleKey.size() > 0) {

View File

@@ -35,47 +35,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/qpa/qplatformscreen.h>
namespace Core {
namespace {
QChar _toHex(ushort v) {
v = v & 0x000F;
return QChar::fromLatin1((v >= 10) ? ('a' + (v - 10)) : ('0' + v));
}
ushort _fromHex(QChar c) {
return ((c.unicode() >= uchar('a')) ? (c.unicode() - uchar('a') + 10) : (c.unicode() - uchar('0'))) & 0x000F;
}
QString _escapeTo7bit(const QString &str) {
QString result;
result.reserve(str.size() * 2);
for (int i = 0, l = str.size(); i != l; ++i) {
QChar ch(str.at(i));
ushort uch(ch.unicode());
if (uch < 32 || uch > 127 || uch == ushort(uchar('%'))) {
result.append('%').append(_toHex(uch >> 12)).append(_toHex(uch >> 8)).append(_toHex(uch >> 4)).append(_toHex(uch));
} else {
result.append(ch);
}
}
return result;
}
QString _escapeFrom7bit(const QString &str) {
QString result;
result.reserve(str.size());
for (int i = 0, l = str.size(); i != l; ++i) {
QChar ch(str.at(i));
if (ch == QChar::fromLatin1('%') && i + 4 < l) {
result.append(QChar(ushort((_fromHex(str.at(i + 1)) << 12) | (_fromHex(str.at(i + 2)) << 8) | (_fromHex(str.at(i + 3)) << 4) | _fromHex(str.at(i + 4)))));
i += 4;
} else {
result.append(ch);
}
}
return result;
}
} // namespace
bool Sandbox::QuitOnStartRequested = false;
@@ -277,18 +236,15 @@ void Sandbox::socketConnected() {
_secondInstance = true;
QString commands;
const QStringList &lst(cSendPaths());
for (QStringList::const_iterator i = lst.cbegin(), e = lst.cend(); i != e; ++i) {
commands += u"SEND:"_q + _escapeTo7bit(*i) + ';';
}
if (qEnvironmentVariableIsSet("XDG_ACTIVATION_TOKEN")) {
commands += u"XDG_ACTIVATION_TOKEN:"_q + _escapeTo7bit(qEnvironmentVariable("XDG_ACTIVATION_TOKEN")) + ';';
commands += u"XDG_ACTIVATION_TOKEN:"_q + qgetenv("XDG_ACTIVATION_TOKEN").toBase64() + ';';
}
if (!cStartUrl().isEmpty()) {
commands += u"OPEN:"_q + _escapeTo7bit(cStartUrl()) + ';';
} else if (cQuit()) {
for (const auto &url : cRefStartUrls()) {
commands += u"OPEN:"_q + url.toString(QUrl::FullyEncoded) + ';';
}
if (cQuit()) {
commands += u"CMD:quit;"_q;
} else {
} else if (cRefStartUrls().isEmpty()) {
commands += u"CMD:show;"_q;
}
@@ -434,11 +390,11 @@ void Sandbox::newInstanceConnected() {
void Sandbox::readClients() {
// This method can be called before Application is constructed.
QString startUrl;
QStringList toSend;
QList<QUrl> startUrls;
for (LocalClients::iterator i = _localClients.begin(), e = _localClients.end(); i != e; ++i) {
i->second.append(i->first->readAll());
if (i->second.size()) {
bool activationRequired = false;
QString cmds(QString::fromLatin1(i->second));
int32 from = 0, l = cmds.length();
for (int32 to = cmds.indexOf(QChar(';'), from); to >= from; to = (from < l) ? cmds.indexOf(QChar(';'), from) : -1) {
@@ -448,21 +404,13 @@ void Sandbox::readClients() {
const auto windowId = execExternal(cmds.mid(from + 4, to - from - 4));
const auto response = u"RES:%1_%2;"_q.arg(processId).arg(windowId).toLatin1();
i->first->write(response.data(), response.size());
} else if (cmd.startsWith(u"SEND:"_q)) {
if (cSendPaths().isEmpty()) {
toSend.append(_escapeFrom7bit(cmds.mid(from + 5, to - from - 5)));
}
} else if (cmd.startsWith(u"XDG_ACTIVATION_TOKEN:"_q)) {
qputenv("XDG_ACTIVATION_TOKEN", _escapeFrom7bit(cmds.mid(from + 21, to - from - 21)).toUtf8());
qputenv("XDG_ACTIVATION_TOKEN", QByteArray::fromBase64(cmds.mid(from + 21, to - from - 21).toLatin1()));
} else if (cmd.startsWith(u"OPEN:"_q)) {
startUrl = _escapeFrom7bit(cmds.mid(from + 5, to - from - 5)).mid(0, 8192);
const auto activationRequired = StartUrlRequiresActivate(startUrl);
const auto processId = QApplication::applicationPid();
const auto windowId = activationRequired
? execExternal("show")
: 0;
const auto response = u"RES:%1_%2;"_q.arg(processId).arg(windowId).toLatin1();
i->first->write(response.data(), response.size());
startUrls.append(cmds.mid(from + 5, to - from - 5).mid(0, 8192));
if (!activationRequired) {
activationRequired = StartUrlRequiresActivate(startUrls.back().toString());
}
} else {
LOG(("Sandbox Error: unknown command %1 passed in local socket").arg(cmd.toString()));
}
@@ -471,21 +419,17 @@ void Sandbox::readClients() {
if (from > 0) {
i->second = i->second.mid(from);
}
const auto processId = QApplication::applicationPid();
const auto windowId = activationRequired
? execExternal("show")
: 0;
const auto response = u"RES:%1_%2;"_q.arg(processId).arg(windowId).toLatin1();
i->first->write(response.data(), response.size());
}
}
if (!toSend.isEmpty()) {
QStringList paths(cSendPaths());
paths.append(toSend);
cSetSendPaths(paths);
}
cRefStartUrls() << base::take(startUrls);
if (_application) {
_application->checkSendPaths();
}
if (!startUrl.isEmpty()) {
cSetStartUrl(startUrl);
}
if (_application) {
_application->checkStartUrl();
_application->checkStartUrls();
}
}

View File

@@ -2898,17 +2898,30 @@ bool MainWidget::contentOverlapped(const QRect &globalRect) {
void MainWidget::activate() {
if (_showAnimation) {
return;
} else if (const auto paths = cSendPaths(); !paths.isEmpty()) {
const auto interpret = u"interpret://"_q;
cSetSendPaths(QStringList());
if (paths[0].startsWith(interpret)) {
const auto error = Support::InterpretSendPath(
_controller,
paths[0].mid(interpret.size()));
if (!error.isEmpty()) {
_controller->show(Ui::MakeInformBox(error));
}
const auto urls = base::take(cRefStartUrls());
const auto interprets = urls | ranges::views::filter([](const QUrl &url) {
return url.scheme() == u"interpret"_q;
}) | ranges::views::transform([](const QUrl &url) {
return url.path();
}) | ranges::to<QStringList>;
const auto paths = urls | ranges::views::filter(
&QUrl::isLocalFile
) | ranges::views::transform(
&QUrl::toLocalFile
) | ranges::to<QStringList>;
if (!interprets.isEmpty() || !paths.isEmpty()) {
if (!interprets.isEmpty()) {
for (const auto &interpret : interprets) {
const auto error = Support::InterpretSendPath(
_controller,
interpret);
if (!error.isEmpty()) {
_controller->show(Ui::MakeInformBox(error));
}
}
} else {
}
if (!paths.isEmpty()) {
const auto chosen = [=](not_null<Data::Thread*> thread) {
return sendPaths(thread, paths);
};

View File

@@ -215,7 +215,7 @@ void MainWindow::clearPasscodeLock() {
_main->show();
updateControlsGeometry();
_main->showAnimated(std::move(oldContentCache), true);
Core::App().checkStartUrl();
Core::App().checkStartUrls();
}
}
@@ -285,7 +285,7 @@ void MainWindow::setupMain(
} else {
_main->activate();
}
Core::App().checkStartUrl();
Core::App().checkStartUrls();
}
fixOrder();
if (const auto strong = weakAnimatedLayer.get()) {

View File

@@ -707,7 +707,7 @@ void psSendToMenu(bool send, bool silent) {
send,
silent,
FOLDERID_SendTo,
L"-sendpath",
L"--",
L"Telegram send to link.\n"
"You can disable send to menu item in Telegram settings.");
}

View File

@@ -20,8 +20,7 @@ bool gManyInstance = false;
QString gKeyFile;
QString gWorkingDir;
QStringList gSendPaths;
QString gStartUrl;
QList<QUrl> gStartUrls;
QString gDialogLastPath, gDialogHelperPath; // optimize QFileDialog

View File

@@ -108,8 +108,7 @@ DeclareSetting(bool, PasswordRecovered);
DeclareSetting(int32, PasscodeBadTries);
DeclareSetting(crl::time, PasscodeLastTry);
DeclareSetting(QStringList, SendPaths);
DeclareSetting(QString, StartUrl);
DeclareRefSetting(QList<QUrl>, StartUrls);
DeclareSetting(int, OtherOnline);