Handle shortcuts in IV.

This commit is contained in:
John Preston 2023-12-01 18:47:24 +04:00
parent 6d733bb566
commit 212259aae3
7 changed files with 261 additions and 95 deletions

View file

@ -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;

View file

@ -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()) {

View file

@ -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 <QtCore/QRegularExpression>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonValue>
#include <QtCore/QFile>
#include <QtGui/QPainter>
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<QEvent*> e) {
if (e->type() == QEvent::Close) {
close();
} else if (e->type() == QEvent::KeyPress) {
const auto event = static_cast<QKeyEvent*>(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<Webview::DataRequest> 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() {

View file

@ -25,16 +25,34 @@ public:
Controller();
~Controller();
void show(const QString &dataPath, Prepared page);
enum class Event {
Close,
Quit,
};
[[nodiscard]] rpl::producer<Webview::DataRequest> dataRequests() const;
void show(const QString &dataPath, Prepared page);
[[nodiscard]] bool active() const;
void minimize();
[[nodiscard]] rpl::producer<Webview::DataRequest> dataRequests() const {
return _dataRequests.events();
}
[[nodiscard]] rpl::producer<Event> events() const {
return _events.events();
}
[[nodiscard]] rpl::lifetime &lifetime();
private:
void escape();
void close();
void quit();
std::unique_ptr<Ui::RpWindow> _window;
std::unique_ptr<Webview::Window> _webview;
rpl::event_stream<Webview::DataRequest> _dataRequests;
rpl::event_stream<Event> _events;
rpl::lifetime _lifetime;
};

View file

@ -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*> data) const;
[[nodiscard]] bool showingFrom(not_null<Main::Session*> session) const;
[[nodiscard]] bool activeFor(not_null<Main::Session*> session) const;
[[nodiscard]] bool active() const;
void minimize();
[[nodiscard]] rpl::producer<Controller::Event> events() const {
return _events.events();
}
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
}
private:
struct MapPreview {
@ -131,6 +143,8 @@ private:
std::vector<QByteArray> _resources;
int _resource = -1;
rpl::event_stream<Controller::Event> _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>();
_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<Main::Session*> 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<Shown>(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<Main::Session*> 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;
}

View file

@ -30,6 +30,9 @@ public:
[[nodiscard]] bool hasActiveWindow(
not_null<Main::Session*> session) const;
bool closeActive();
bool minimizeActive();
void closeAll();
[[nodiscard]] rpl::lifetime &lifetime();

View file

@ -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}