diff --git a/Telegram/Resources/iv_html/page.js b/Telegram/Resources/iv_html/page.js
index d821a1e43..8486e2cc7 100644
--- a/Telegram/Resources/iv_html/page.js
+++ b/Telegram/Resources/iv_html/page.js
@@ -1,86 +1,112 @@
var IV = {
- sendPostMessage: function(data) {
- try {
- window.parent.postMessage(JSON.stringify(data), window.parentOrigin);
- } catch(e) {}
- },
- frameClickHandler: function(e) {
- var target = e.target, href;
- do {
- if (target.tagName == 'SUMMARY') return;
- if (target.tagName == 'DETAILS') return;
- if (target.tagName == 'LABEL') return;
- if (target.tagName == 'AUDIO') return;
- if (target.tagName == 'A') break;
- } while (target = target.parentNode);
- if (target && target.hasAttribute('href')) {
- var base_loc = document.createElement('A');
- base_loc.href = window.currentUrl;
- if (base_loc.origin != target.origin ||
- base_loc.pathname != target.pathname ||
- base_loc.search != target.search) {
- IV.sendPostMessage({event: 'link_click', url: target.href});
- }
- }
- e.preventDefault();
- },
- postMessageHandler: function(event) {
- if (event.source !== window.parent ||
- event.origin != window.parentOrigin) {
- return;
- }
- try {
- var data = JSON.parse(event.data);
- } catch(e) {
- var data = {};
- }
- },
- slideshowSlide: function(el, next) {
- var dir = window.getComputedStyle(el, null).direction || 'ltr';
- var marginProp = dir == 'rtl' ? 'marginRight' : 'marginLeft';
- if (next) {
- var s = el.previousSibling.s;
- s.value = (+s.value + 1 == s.length) ? 0 : +s.value + 1;
- s.forEach(function(el){ el.checked && el.parentNode.scrollIntoView && el.parentNode.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'center'}); });
- el.firstChild.style[marginProp] = (-100 * s.value) + '%';
- } else {
- el.form.nextSibling.firstChild.style[marginProp] = (-100 * el.value) + '%';
- }
- return false;
- },
- initPreBlocks: function() {
- if (!hljs) return;
- var pres = document.getElementsByTagName('pre');
- for (var i = 0; i < pres.length; i++) {
- if (pres[i].hasAttribute('data-language')) {
- hljs.highlightBlock(pres[i]);
- }
- }
- },
- initEmbedBlocks: function() {
- var iframes = document.getElementsByTagName('iframe');
- for (var i = 0; i < iframes.length; i++) {
- (function(iframe) {
- window.addEventListener('message', function(event) {
- if (event.source !== iframe.contentWindow ||
- event.origin != window.origin) {
- return;
- }
- try {
- var data = JSON.parse(event.data);
- } catch(e) {
- var data = {};
- }
- if (data.eventType == 'resize_frame') {
- if (data.eventData.height) {
- iframe.style.height = data.eventData.height + 'px';
- }
- }
- }, false);
- })(iframes[i]);
- }
- }
+ notify: function(message) {
+ if (window.external && window.external.invoke) {
+ window.external.invoke(JSON.stringify(message));
+ }
+ },
+ frameClickHandler: function(e) {
+ var target = e.target, href;
+ do {
+ if (target.tagName == 'SUMMARY') return;
+ if (target.tagName == 'DETAILS') return;
+ if (target.tagName == 'LABEL') return;
+ if (target.tagName == 'AUDIO') return;
+ if (target.tagName == 'A') break;
+ } while (target = target.parentNode);
+ if (target && target.hasAttribute('href')) {
+ var base_loc = document.createElement('A');
+ base_loc.href = window.currentUrl;
+ if (base_loc.origin != target.origin ||
+ base_loc.pathname != target.pathname ||
+ base_loc.search != target.search) {
+ IV.notify({ event: 'link_click', url: target.href });
+ }
+ }
+ e.preventDefault();
+ },
+ frameKeyDown: function (e) {
+ let keyW = (e.key === 'w')
+ || (e.code === 'KeyW')
+ || (e.keyCode === 87);
+ let keyQ = (e.key === 'q')
+ || (e.code === 'KeyQ')
+ || (e.keyCode === 81);
+ let keyM = (e.key === 'm')
+ || (e.code === 'KeyM')
+ || (e.keyCode === 77);
+ if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM)) {
+ e.preventDefault();
+ IV.notify({
+ event: 'keydown',
+ modifier: e.ctrlKey ? 'ctrl' : 'cmd',
+ key: keyW ? 'w' : keyQ ? 'q' : 'm',
+ });
+ } else if (e.key === 'Escape' || e.keyCode === 27) {
+ e.preventDefault();
+ IV.notify({
+ event: 'keydown',
+ key: 'escape',
+ });
+ }
+ },
+ postMessageHandler: function(event) {
+ if (event.source !== window.parent ||
+ event.origin != window.parentOrigin) {
+ return;
+ }
+ try {
+ var data = JSON.parse(event.data);
+ } catch(e) {
+ var data = {};
+ }
+ },
+ slideshowSlide: function(el, next) {
+ var dir = window.getComputedStyle(el, null).direction || 'ltr';
+ var marginProp = dir == 'rtl' ? 'marginRight' : 'marginLeft';
+ if (next) {
+ var s = el.previousSibling.s;
+ s.value = (+s.value + 1 == s.length) ? 0 : +s.value + 1;
+ s.forEach(function(el){ el.checked && el.parentNode.scrollIntoView && el.parentNode.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'center'}); });
+ el.firstChild.style[marginProp] = (-100 * s.value) + '%';
+ } else {
+ el.form.nextSibling.firstChild.style[marginProp] = (-100 * el.value) + '%';
+ }
+ return false;
+ },
+ initPreBlocks: function() {
+ if (!hljs) return;
+ var pres = document.getElementsByTagName('pre');
+ for (var i = 0; i < pres.length; i++) {
+ if (pres[i].hasAttribute('data-language')) {
+ hljs.highlightBlock(pres[i]);
+ }
+ }
+ },
+ initEmbedBlocks: function() {
+ var iframes = document.getElementsByTagName('iframe');
+ for (var i = 0; i < iframes.length; i++) {
+ (function(iframe) {
+ window.addEventListener('message', function(event) {
+ if (event.source !== iframe.contentWindow ||
+ event.origin != window.origin) {
+ return;
+ }
+ try {
+ var data = JSON.parse(event.data);
+ } catch(e) {
+ var data = {};
+ }
+ if (data.eventType == 'resize_frame') {
+ if (data.eventData.height) {
+ iframe.style.height = data.eventData.height + 'px';
+ }
+ }
+ }, false);
+ })(iframes[i]);
+ }
+ }
};
document.onclick = IV.frameClickHandler;
+document.onkeydown = IV.frameKeyDown;
window.onmessage = IV.postMessageHandler;
diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp
index 8fe1e75c7..06691b5c9 100644
--- a/Telegram/SourceFiles/core/application.cpp
+++ b/Telegram/SourceFiles/core/application.cpp
@@ -1547,12 +1547,12 @@ bool Application::closeActiveWindow() {
if (_mediaView && _mediaView->isActive()) {
_mediaView->close();
return true;
- } else if (!calls().closeCurrentActiveCall()) {
- if (const auto window = activeWindow()) {
- if (window->widget()->isActive()) {
- window->close();
- return true;
- }
+ } else if (_iv->closeActive() || calls().closeCurrentActiveCall()) {
+ return true;
+ } else if (const auto window = activeWindow()) {
+ if (window->widget()->isActive()) {
+ window->close();
+ return true;
}
}
return false;
@@ -1562,7 +1562,8 @@ bool Application::minimizeActiveWindow() {
if (_mediaView && _mediaView->isActive()) {
_mediaView->minimize();
return true;
- } else if (calls().minimizeCurrentActiveCall()) {
+ } else if (_iv->minimizeActive()
+ || calls().minimizeCurrentActiveCall()) {
return true;
} else {
if (const auto window = activeWindow()) {
diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp
index 6f9069dfd..709461eaa 100644
--- a/Telegram/SourceFiles/iv/iv_controller.cpp
+++ b/Telegram/SourceFiles/iv/iv_controller.cpp
@@ -7,14 +7,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "iv/iv_controller.h"
+#include "base/platform/base_platform_info.h"
#include "iv/iv_data.h"
#include "ui/widgets/rp_window.h"
#include "webview/webview_data_stream_memory.h"
#include "webview/webview_embed.h"
#include "webview/webview_interface.h"
+#include "styles/palette.h"
#include
+#include
+#include
+#include
#include
+#include
namespace Iv {
@@ -47,22 +53,56 @@ void Controller::show(const QString &dataPath, Prepared page) {
_webview = nullptr;
});
if (!raw->widget()) {
- _webview = nullptr;
- _window = nullptr;
+ _events.fire(Event::Close);
return;
}
+ window->events(
+ ) | rpl::start_with_next([=](not_null e) {
+ if (e->type() == QEvent::Close) {
+ close();
+ } else if (e->type() == QEvent::KeyPress) {
+ const auto event = static_cast(e.get());
+ if (event->key() == Qt::Key_Escape) {
+ escape();
+ }
+ }
+ }, window->lifetime());
raw->widget()->show();
container->geometryValue(
) | rpl::start_with_next([=](QRect geometry) {
raw->widget()->setGeometry(geometry);
- }, _lifetime);
+ }, container->lifetime());
+
+ container->paintRequest() | rpl::start_with_next([=](QRect clip) {
+ QPainter(container).fillRect(clip, st::windowBg);
+ }, container->lifetime());
raw->setNavigationStartHandler([=](const QString &uri, bool newWindow) {
return true;
});
raw->setNavigationDoneHandler([=](bool success) {
});
+ raw->setMessageHandler([=](const QJsonDocument &message) {
+ crl::on_main(_window.get(), [=] {
+ const auto object = message.object();
+ const auto event = object.value("event").toString();
+ if (event == u"keydown"_q) {
+ const auto key = object.value("key").toString();
+ const auto modifier = object.value("modifier").toString();
+ const auto ctrl = Platform::IsMac() ? u"cmd"_q : u"ctrl"_q;
+ if (key == u"escape"_q) {
+ escape();
+ } else if (key == u"w"_q && modifier == ctrl) {
+ close();
+ } else if (key == u"m"_q && modifier == ctrl) {
+ minimize();
+ } else if (key == u"q"_q && modifier == ctrl) {
+ quit();
+ }
+ }
+ });
+ });
raw->setDataRequestHandler([=](Webview::DataRequest request) {
if (!request.id.starts_with("iv/")) {
_dataRequests.fire(std::move(request));
@@ -104,8 +144,27 @@ void Controller::show(const QString &dataPath, Prepared page) {
window->show();
}
-rpl::producer Controller::dataRequests() const {
- return _dataRequests.events();
+bool Controller::active() const {
+ return _window && _window->isActiveWindow();
+}
+
+void Controller::minimize() {
+ if (_window) {
+ _window->setWindowState(_window->windowState()
+ | Qt::WindowMinimized);
+ }
+}
+
+void Controller::escape() {
+ close();
+}
+
+void Controller::close() {
+ _events.fire(Event::Close);
+}
+
+void Controller::quit() {
+ _events.fire(Event::Quit);
}
rpl::lifetime &Controller::lifetime() {
diff --git a/Telegram/SourceFiles/iv/iv_controller.h b/Telegram/SourceFiles/iv/iv_controller.h
index b35dfc00a..7e623f7ce 100644
--- a/Telegram/SourceFiles/iv/iv_controller.h
+++ b/Telegram/SourceFiles/iv/iv_controller.h
@@ -25,16 +25,34 @@ public:
Controller();
~Controller();
- void show(const QString &dataPath, Prepared page);
+ enum class Event {
+ Close,
+ Quit,
+ };
- [[nodiscard]] rpl::producer dataRequests() const;
+ void show(const QString &dataPath, Prepared page);
+ [[nodiscard]] bool active() const;
+ void minimize();
+
+ [[nodiscard]] rpl::producer dataRequests() const {
+ return _dataRequests.events();
+ }
+
+ [[nodiscard]] rpl::producer events() const {
+ return _events.events();
+ }
[[nodiscard]] rpl::lifetime &lifetime();
private:
+ void escape();
+ void close();
+ void quit();
+
std::unique_ptr _window;
std::unique_ptr _webview;
rpl::event_stream _dataRequests;
+ rpl::event_stream _events;
rpl::lifetime _lifetime;
};
diff --git a/Telegram/SourceFiles/iv/iv_instance.cpp b/Telegram/SourceFiles/iv/iv_instance.cpp
index 27f3dcfb2..4a1accd7e 100644
--- a/Telegram/SourceFiles/iv/iv_instance.cpp
+++ b/Telegram/SourceFiles/iv/iv_instance.cpp
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "iv/iv_instance.h"
#include "core/file_utilities.h"
+#include "core/shortcuts.h"
#include "data/data_cloud_file.h"
#include "data/data_document.h"
#include "data/data_file_origin.h"
@@ -63,6 +64,17 @@ public:
not_null data) const;
[[nodiscard]] bool showingFrom(not_null session) const;
[[nodiscard]] bool activeFor(not_null session) const;
+ [[nodiscard]] bool active() const;
+
+ void minimize();
+
+ [[nodiscard]] rpl::producer events() const {
+ return _events.events();
+ }
+
+ [[nodiscard]] rpl::lifetime &lifetime() {
+ return _lifetime;
+ }
private:
struct MapPreview {
@@ -131,6 +143,8 @@ private:
std::vector _resources;
int _resource = -1;
+ rpl::event_stream _events;
+
rpl::lifetime _lifetime;
};
@@ -358,6 +372,10 @@ void Shown::writeEmbed(QString id, QString hash) {
void Shown::showWindowed(Prepared result) {
_controller = std::make_unique();
+
+ _controller->events(
+ ) | rpl::start_to_stream(_events, _controller->lifetime());
+
_controller->dataRequests(
) | rpl::start_with_next([=](Webview::DataRequest request) {
const auto requested = QString::fromStdString(request.id);
@@ -372,6 +390,7 @@ void Shown::showWindowed(Prepared result) {
sendEmbed(id.mid(5).toUtf8(), std::move(request));
}
}, _controller->lifetime());
+
const auto domain = &_session->domain();
_controller->show(domain->local().webviewDataPath(), std::move(result));
}
@@ -628,6 +647,16 @@ bool Shown::activeFor(not_null session) const {
return showingFrom(session) && _controller;
}
+bool Shown::active() const {
+ return _controller && _controller->active();
+}
+
+void Shown::minimize() {
+ if (_controller) {
+ _controller->minimize();
+ }
+}
+
Instance::Instance() = default;
Instance::~Instance() = default;
@@ -641,6 +670,14 @@ void Instance::show(
return;
}
_shown = std::make_unique(show, data, local);
+ _shown->events() | rpl::start_with_next([=](Controller::Event event) {
+ if (event == Controller::Event::Close) {
+ _shown = nullptr;
+ } else if (event == Controller::Event::Quit) {
+ Shortcuts::Launch(Shortcuts::Command::Quit);
+ }
+ }, _shown->lifetime());
+
if (!_tracking.contains(session)) {
_tracking.emplace(session);
session->lifetime().add([=] {
@@ -656,6 +693,22 @@ bool Instance::hasActiveWindow(not_null session) const {
return _shown && _shown->activeFor(session);
}
+bool Instance::closeActive() {
+ if (!_shown || !_shown->active()) {
+ return false;
+ }
+ _shown = nullptr;
+ return true;
+}
+
+bool Instance::minimizeActive() {
+ if (!_shown || !_shown->active()) {
+ return false;
+ }
+ _shown->minimize();
+ return true;
+}
+
void Instance::closeAll() {
_shown = nullptr;
}
diff --git a/Telegram/SourceFiles/iv/iv_instance.h b/Telegram/SourceFiles/iv/iv_instance.h
index 7a1b21f62..566876303 100644
--- a/Telegram/SourceFiles/iv/iv_instance.h
+++ b/Telegram/SourceFiles/iv/iv_instance.h
@@ -30,6 +30,9 @@ public:
[[nodiscard]] bool hasActiveWindow(
not_null session) const;
+ bool closeActive();
+ bool minimizeActive();
+
void closeAll();
[[nodiscard]] rpl::lifetime &lifetime();
diff --git a/Telegram/cmake/td_iv.cmake b/Telegram/cmake/td_iv.cmake
index 64fe487ce..c15db5afc 100644
--- a/Telegram/cmake/td_iv.cmake
+++ b/Telegram/cmake/td_iv.cmake
@@ -20,6 +20,12 @@ PRIVATE
iv/iv_prepare.h
)
+nice_target_sources(td_iv ${res_loc}
+PRIVATE
+ iv_html/page.css
+ iv_html/page.js
+)
+
target_include_directories(td_iv
PUBLIC
${src_loc}