diff --git a/browser_ext/background.js b/browser_ext/background.js index 84ce20e15..068866de2 100644 --- a/browser_ext/background.js +++ b/browser_ext/background.js @@ -8,6 +8,9 @@ const ignoredPages = { "directory": true, }; +let popup = chrome.extension.getViews({type: "popup"})[0]; +popup.window.getElementById("status").innerHTML = "NaM"; + /// return channel name if it should contain a chat function matchChannelName(url) { if (!url) @@ -43,6 +46,7 @@ function getPort() { function connectPort() { port = chrome.runtime.connectNative(appName); console.log("port connected"); + let connected = true; port.onMessage.addListener(function (msg) { console.log(msg); @@ -52,6 +56,14 @@ function connectPort() { port = null; }); + + let sendPing = () => { + if (connected) { + port.postMessage({ ping: true }); + } else { + setTimeout(sendPing, 5000); + } + } } @@ -63,6 +75,12 @@ chrome.tabs.onActivated.addListener((activeInfo) => { chrome.windows.get(tab.windowId, {}, (window) => { if (!window.focused) return; + if (window.state == "fullscreen") { + tryDetach(tab.windowId); + return; + } + + console.log("onActivated"); onTabSelected(tab.url, tab); }); }); @@ -73,16 +91,27 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { if (!tab.highlighted) return; - onTabSelected(changeInfo.url, tab); + chrome.windows.get(tab.windowId, {}, (window) => { + if (!window.focused) return; + if (window.state == "fullscreen") { + tryDetach(tab.windowId); + return; + } + + console.log("onUpdated"); + onTabSelected(tab.url, tab); + }); }); // tab detached chrome.tabs.onDetached.addListener((tabId, detachInfo) => { + console.log("onDetached"); tryDetach(detachInfo.oldWindowId); }); // tab closed chrome.windows.onRemoved.addListener((windowId) => { + console.log("onRemoved"); tryDetach(windowId); }); @@ -96,13 +125,20 @@ chrome.windows.onFocusChanged.addListener((windowId) => { if (tabs.length === 1) { let tab = tabs[0]; - onTabSelected(tab.url, tab); + chrome.windows.get(tab.windowId, (window) => { + if (window.state == "fullscreen") { + tryDetach(tab.windowId); + return; + } + + console.log("onFocusChanged"); + onTabSelected(tab.url, tab); + }); } }); }); - // attach or detach from tab function onTabSelected(url, tab) { let channelName = matchChannelName(url); @@ -131,6 +167,10 @@ chrome.runtime.onMessage.addListener((message, sender, callback) => { // is window focused chrome.windows.get(sender.tab.windowId, {}, (window) => { if (!window.focused) return; + if (window.state == "fullscreen") { + tryDetach(sender.tab.windowId); + return; + } // get zoom value chrome.tabs.getZoom(sender.tab.id, (zoom) => { @@ -169,6 +209,8 @@ function tryAttach(windowId, data) { function tryDetach(windowId) { let port = getPort(); + console.log("tryDetach"); + if (port) { port.postMessage({ action: "detach", diff --git a/browser_ext/inject.js b/browser_ext/inject.js index 812b6580f..29d132817 100644 --- a/browser_ext/inject.js +++ b/browser_ext/inject.js @@ -2,72 +2,9 @@ let lastRect = {}; let port = null; - function log(str) { - console.log("Chatterino Native: " + str); - } - - function findChatDiv() { - return document.getElementsByClassName("right-column")[0]; - } - - function queryChatRect() { - if (!matchChannelName(window.location.href)) return; - - let element = findChatDiv(); - - if (element === undefined) { - log("failed to find chat div"); - return; - } - - if (!element.chatterino) { - let xd = element.getElementsByClassName("channel-page__right-column")[0] - - if (xd != undefined) { - element.chatterino = true; - xd.style.opacity = 0; - - setTimeout(() => { - xd.innerHTML = "
" + - "Disconnected from the chatterino extension.

Please refresh the page." + - "
"; - xd.style.opacity = 1; - }, 2000); - } - } - - let rect = element.getBoundingClientRect(); - - // if ( - // lastRect.left == rect.left && - // lastRect.right == rect.right && - // lastRect.top == rect.top && - // lastRect.bottom == rect.bottom - // ) { - // // log("skipped sending message"); - // return; - // } - lastRect = rect; - - let data = { - rect: rect, - }; - - try { - chrome.runtime.sendMessage(data); - } catch { - // failed to send a message to the runtime -> maybe the extension got reloaded - // alert("reload the page to re-enable chatterino native"); - } - } - - function queryCharRectLoop() { - let t1 = performance.now(); - queryChatRect(); - let t2 = performance.now(); - console.log("queryCharRect " + (t2 - t1) + "ms"); - // setTimeout(queryCharRectLoop, 500); - } + let installedObjects = {}; + let rightCollapseButton = null; + let isCollapsed = false; const ignoredPages = { "settings": true, @@ -79,7 +16,126 @@ "directory": true, }; - /// return channel name if it should contain a chat + let findChatDiv = () => document.getElementsByClassName("right-column")[0]; + let findRightCollapse = () => document.getElementsByClassName("right-column__toggle-visibility")[0]; + let findRightColumn = () => document.getElementsByClassName("channel-page__right-column")[0]; + let findNavBar = () => document.getElementsByClassName("top-nav__menu")[0]; + + // logging function + function log(str) { + console.log("Chatterino Native: " + str); + } + + // install events + function installChatterino() { + log("trying to install events"); + + let retry = false; + + // right collapse button + if (!installedObjects.rightCollapse) { + retry = true; + + let x = findRightCollapse(); + + if (x != undefined) { + rightCollapseButton = x; + + x.addEventListener("mouseup", () => { + let y = findChatDiv(); + + if (parseInt(y.style.width) == 0) { + y.style.width = "340px"; + isCollapsed = false; + } else { + y.style.width = 0; + isCollapsed = true; + } + }); + + installedObjects.rightCollapse = true; + } + } + + // right column + if (!installedObjects.rightColumn && installedObjects.rightCollapse) { + let x = findChatDiv(); + + if (x != undefined && x.children.length >= 2) { + x.children[0].innerHTML = "
" + + "Disconnected from the chatterino extension.

Please focus the window or refresh the page." + + "
"; + + installedObjects.rightColumn = true; + } else { + retry = true; + } + } + + // nav bar + if (rightCollapseButton && !installedObjects.topNav) { + let x = findRightCollapse(); + + installedObjects.topNav = true; + } else { + retry = true; + } + + // retry if needed + if (retry) { + setTimeout(installChatterino, 1000); + } else { + log("installed all events"); + } + } + + // query the rect of the chat + function queryChatRect() { + if (!matchChannelName(window.location.href)) return; + + let element = findChatDiv(); + + if (element === undefined) { + log("failed to find chat div"); + return; + } + + let rect = element.getBoundingClientRect(); + + /* if ( + lastRect.left == rect.left && + lastRect.right == rect.right && + lastRect.top == rect.top && + lastRect.bottom == rect.bottom + ) { + // log("skipped sending message"); + return; + } */ + lastRect = rect; + + let data = { + rect: rect, + }; + + isCollapsed = rect.width == 0; + + try { + chrome.runtime.sendMessage(data); + } catch { + // failed to send a message to the runtime -> maybe the extension got reloaded + // alert("reload the page to re-enable chatterino native"); + } + } + + function queryChatRectLoop() { + let t1 = performance.now(); + queryChatRect(); + let t2 = performance.now(); + console.log("queryCharRect " + (t2 - t1) + "ms"); + // setTimeout(queryCharRectLoop, 500); + } + + // return channel name if it should contain a chat or undefined function matchChannelName(url) { if (!url) return undefined; @@ -94,15 +150,20 @@ return undefined; } - queryCharRectLoop(); - window.addEventListener("load", () => { - setTimeout(queryChatRect, 1000); - }); + + // event listeners + window.addEventListener("load", () => setTimeout(queryChatRect, 1000)); window.addEventListener("resize", queryChatRect); window.addEventListener("focus", queryChatRect); - window.addEventListener("mouseup", () => { - setTimeout(queryChatRect, 10); + window.addEventListener("mouseup", () => setTimeout(queryChatRect, 10)); + window.addEventListener("hashchange", () => { + installedObjects = {}; + installChatterino(); }); - log("initialized"); + // + log("hello there in the dev tools 👋"); + + queryChatRectLoop(); + installChatterino(); })() diff --git a/browser_ext/popup.html b/browser_ext/popup.html index e24ef44ab..221fc73e6 100644 --- a/browser_ext/popup.html +++ b/browser_ext/popup.html @@ -1,6 +1,10 @@ + - xd +
+ Chatterino extension :) +
+ diff --git a/lib/settings b/lib/settings index 439b58597..ad31b3886 160000 --- a/lib/settings +++ b/lib/settings @@ -1 +1 @@ -Subproject commit 439b585976a864de679fc5d5b2c688380b2ef72c +Subproject commit ad31b38866d80a17ced902476ed06da69edce3a0 diff --git a/src/main.cpp b/src/main.cpp index ab8bfc622..6a8e85f5d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -84,10 +84,10 @@ int runGui(int argc, char *argv[]) qApp->setPalette(darkPalette); -// Install native event handler for hidpi on windows -#ifdef USEWINSDK - a.installNativeEventFilter(new chatterino::util::DpiNativeEventFilter); -#endif + // Install native event handler for hidpi on windows + //#ifdef USEWINSDK + // a.installNativeEventFilter(new chatterino::util::DpiNativeEventFilter); + //#endif // Initialize NetworkManager chatterino::util::NetworkManager::init(); @@ -149,6 +149,17 @@ void runNativeMessagingHost() bool bigEndian = isBigEndian(); #endif + std::atomic ping(false); + + QTimer timer; + QObject::connect(&timer, &QTimer::timeout, [&ping] { + if (!ping.exchange(false)) { + _exit(0); + } + }); + timer.setInterval(11000); + timer.start(); + while (true) { char size_c[4]; std::cin.read(size_c, 4); diff --git a/src/singletons/nativemessagingmanager.cpp b/src/singletons/nativemessagingmanager.cpp index a4810fddc..f817a91a0 100644 --- a/src/singletons/nativemessagingmanager.cpp +++ b/src/singletons/nativemessagingmanager.cpp @@ -195,7 +195,10 @@ void NativeMessagingManager::ReceiverThread::handleMessage(const QJsonObject &ro } #ifdef USEWINSDK - util::postToThread([winId] { widgets::AttachedWindow::detach(winId); }); + util::postToThread([winId] { + qDebug() << "NW detach"; + widgets::AttachedWindow::detach(winId); + }); #endif } else { qDebug() << "NM unknown action " + action; diff --git a/src/widgets/attachedwindow.cpp b/src/widgets/attachedwindow.cpp index d28facfb8..8738e4c26 100644 --- a/src/widgets/attachedwindow.cpp +++ b/src/widgets/attachedwindow.cpp @@ -1,15 +1,15 @@ #include "attachedwindow.hpp" #include "application.hpp" +#include "util/debugcount.hpp" +#include "widgets/split.hpp" #include #include -#include "widgets/split.hpp" - #ifdef USEWINSDK #include "Windows.h" - +// don't even think about reordering these #include "Psapi.h" #pragma comment(lib, "Dwmapi.lib") #endif @@ -19,17 +19,19 @@ namespace widgets { AttachedWindow::AttachedWindow(void *_target, int _yOffset) : QWidget(nullptr, Qt::FramelessWindowHint | Qt::Window) - , target(_target) - , yOffset(_yOffset) + , target_(_target) + , yOffset_(_yOffset) { QLayout *layout = new QVBoxLayout(this); layout->setMargin(0); this->setLayout(layout); auto *split = new Split(this); - this->ui.split = split; + this->ui_.split = split; split->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::MinimumExpanding); layout->addWidget(split); + + util::DebugCount::increase("attached window"); } AttachedWindow::~AttachedWindow() @@ -40,6 +42,8 @@ AttachedWindow::~AttachedWindow() break; } } + + util::DebugCount::decrease("attached window"); } AttachedWindow *AttachedWindow::get(void *target, const GetArgs &args) @@ -64,7 +68,7 @@ AttachedWindow *AttachedWindow::get(void *target, const GetArgs &args) window->hide(); show = false; } else { - window->_height = args.height; + window->height_ = args.height; size.setHeight(args.height); } } @@ -73,14 +77,14 @@ AttachedWindow *AttachedWindow::get(void *target, const GetArgs &args) window->hide(); show = false; } else { - window->_width = args.width; + window->width_ = args.width; size.setWidth(args.width); } } if (show) { + window->updateWindowRect_(window->target_); window->show(); - // window->resize(size); } return window; @@ -97,80 +101,93 @@ void AttachedWindow::detach(const QString &winId) void AttachedWindow::setChannel(ChannelPtr channel) { - this->ui.split->setChannel(channel); + this->ui_.split->setChannel(channel); } void AttachedWindow::showEvent(QShowEvent *) { - attachToHwnd(this->target); + attachToHwnd_(this->target_); } -void AttachedWindow::attachToHwnd(void *_hwnd) +void AttachedWindow::attachToHwnd_(void *_attachedPtr) { #ifdef USEWINSDK - QTimer *timer = new QTimer(this); - timer->setInterval(1); + if (this->attached_) { + return; + } - HWND hwnd = HWND(this->winId()); - HWND attached = HWND(_hwnd); + this->attached_ = true; + this->timer_.setInterval(1); - QObject::connect(timer, &QTimer::timeout, [this, hwnd, attached, timer] { + auto hwnd = HWND(this->winId()); + auto attached = HWND(_attachedPtr); + QObject::connect(&this->timer_, &QTimer::timeout, [this, hwnd, attached] { // check process id - DWORD processId; - ::GetWindowThreadProcessId(attached, &processId); + if (!this->validProcessName_) { + DWORD processId; + ::GetWindowThreadProcessId(attached, &processId); - HANDLE process = - ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, processId); + HANDLE process = + ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, processId); - std::unique_ptr filename(new TCHAR[512]); - DWORD filenameLength = ::GetModuleFileNameEx(process, nullptr, filename.get(), 512); - QString qfilename = QString::fromWCharArray(filename.get(), filenameLength); + std::unique_ptr filename(new TCHAR[512]); + DWORD filenameLength = ::GetModuleFileNameEx(process, nullptr, filename.get(), 512); + QString qfilename = QString::fromWCharArray(filename.get(), filenameLength); - if (!qfilename.endsWith("chrome.exe")) { - qDebug() << "NM Illegal callee" << qfilename; - timer->stop(); - timer->deleteLater(); - this->deleteLater(); - return; + if (!qfilename.endsWith("chrome.exe")) { + qDebug() << "NM Illegal caller" << qfilename; + this->timer_.stop(); + this->deleteLater(); + return; + } + this->validProcessName_ = true; } - // We get the window rect first so we can close this window when it returns an error. - // If we query the process first and check the filename then it will return and empty string - // that doens't match. - ::SetLastError(0); - RECT rect; - ::GetWindowRect(attached, &rect); - - if (::GetLastError() != 0) { - timer->stop(); - timer->deleteLater(); - this->deleteLater(); - return; - } - - // set the correct z-order - HWND next = ::GetNextWindow(attached, GW_HWNDPREV); - - ::SetWindowPos(hwnd, next ? next : HWND_TOPMOST, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); - - if (this->_height == -1) { - // ::MoveWindow(hwnd, rect.right - this->_width - 8, rect.top + this->yOffset - // - 8, - // this->_width, rect.bottom - rect.top - this->yOffset, false); - } else { - ::MoveWindow(hwnd, rect.right - this->_width - 8, rect.bottom - this->_height - 8, - this->_width, this->_height, false); - } - - // ::MoveWindow(hwnd, rect.right - 360, rect.top + 82, 360 - 8, rect.bottom - - // rect.top - 82 - 8, false); + this->updateWindowRect_(attached); }); - timer->start(); + this->timer_.start(); #endif } +void AttachedWindow::updateWindowRect_(void *_attachedPtr) +{ + auto hwnd = HWND(this->winId()); + auto attached = HWND(_attachedPtr); + + // We get the window rect first so we can close this window when it returns an error. + // If we query the process first and check the filename then it will return and empty string + // that doens't match. + ::SetLastError(0); + RECT rect; + ::GetWindowRect(attached, &rect); + + if (::GetLastError() != 0) { + qDebug() << "NM GetLastError()" << ::GetLastError(); + + this->timer_.stop(); + this->deleteLater(); + return; + } + + // set the correct z-order + HWND next = ::GetNextWindow(attached, GW_HWNDPREV); + + ::SetWindowPos(hwnd, next ? next : HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + + if (this->height_ == -1) { + // ::MoveWindow(hwnd, rect.right - this->width_ - 8, rect.top + this->yOffset_ - 8, + // this->width_, rect.bottom - rect.top - this->yOffset_, false); + } else { + ::MoveWindow(hwnd, rect.right - this->width_ - 8, rect.bottom - this->height_ - 8, + this->width_, this->height_, false); + } + + // ::MoveWindow(hwnd, rect.right - 360, rect.top + 82, 360 - 8, rect.bottom - + // rect.top - 82 - 8, false); +} + // void AttachedWindow::nativeEvent(const QByteArray &eventType, void *message, long *result) //{ // MSG *msg = reinterpret_cast diff --git a/src/widgets/attachedwindow.hpp b/src/widgets/attachedwindow.hpp index 8c8dd6bf8..8d7c216cd 100644 --- a/src/widgets/attachedwindow.hpp +++ b/src/widgets/attachedwindow.hpp @@ -10,7 +10,7 @@ namespace widgets { class AttachedWindow : public QWidget { - AttachedWindow(void *target, int asdf); + AttachedWindow(void *target_, int asdf); public: struct GetArgs { @@ -20,9 +20,9 @@ public: int height = -1; }; - ~AttachedWindow(); + virtual ~AttachedWindow() override; - static AttachedWindow *get(void *target, const GetArgs &args); + static AttachedWindow *get(void *target_, const GetArgs &args); static void detach(const QString &winId); void setChannel(ChannelPtr channel); @@ -33,17 +33,21 @@ protected: // override; private: - void *target; - int yOffset; - int currentYOffset; - int _width = 360; - int _height = -1; + void *target_; + int yOffset_; + int currentYOffset_; + int width_ = 360; + int height_ = -1; + bool validProcessName_ = false; + bool attached_ = false; + QTimer timer_; struct { Split *split; - } ui; + } ui_; - void attachToHwnd(void *hwnd); + void attachToHwnd_(void *attached); + void updateWindowRect_(void *attached); struct Item { void *hwnd;