summaryrefslogtreecommitdiff
path: root/archived/projt-launcher/launcher/ui/widgets/CefHubView.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'archived/projt-launcher/launcher/ui/widgets/CefHubView.cpp')
-rw-r--r--archived/projt-launcher/launcher/ui/widgets/CefHubView.cpp1334
1 files changed, 1334 insertions, 0 deletions
diff --git a/archived/projt-launcher/launcher/ui/widgets/CefHubView.cpp b/archived/projt-launcher/launcher/ui/widgets/CefHubView.cpp
new file mode 100644
index 0000000000..421589216d
--- /dev/null
+++ b/archived/projt-launcher/launcher/ui/widgets/CefHubView.cpp
@@ -0,0 +1,1334 @@
+// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: 2026 Project Tick
+// SPDX-FileContributor: Project Tick Team
+/*
+ * ProjT Launcher - Minecraft Launcher
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "CefHubView.h"
+
+#if defined(PROJT_USE_CEF)
+
+#include <algorithm>
+#include <cmath>
+#include <QApplication>
+#include <QColor>
+#include <QFocusEvent>
+#include <QHideEvent>
+#include <QKeyEvent>
+#include <QMetaObject>
+#include <QMouseEvent>
+#include <QPainter>
+#include <QPaintEvent>
+#include <QResizeEvent>
+#include <QScreen>
+#include <QShowEvent>
+#include <QTimer>
+#include <QWheelEvent>
+
+#include "CefRuntime.h"
+
+#include "include/cef_browser.h"
+#include "include/cef_client.h"
+#include "include/cef_dialog_handler.h"
+#include "include/cef_display_handler.h"
+#include "include/cef_jsdialog_handler.h"
+#include "include/cef_life_span_handler.h"
+#include "include/cef_load_handler.h"
+#include "include/cef_permission_handler.h"
+#include "include/cef_request_context.h"
+#include "include/cef_render_handler.h"
+#include "include/cef_request_handler.h"
+#include "include/internal/cef_types_wrappers.h"
+#include "include/wrapper/cef_helpers.h"
+
+namespace
+{
+ uint32_t toCefModifiers(Qt::KeyboardModifiers keyboardModifiers, Qt::MouseButtons mouseButtons)
+ {
+ uint32_t modifiers = EVENTFLAG_NONE;
+ if (keyboardModifiers.testFlag(Qt::ShiftModifier))
+ {
+ modifiers |= EVENTFLAG_SHIFT_DOWN;
+ }
+ if (keyboardModifiers.testFlag(Qt::ControlModifier))
+ {
+ modifiers |= EVENTFLAG_CONTROL_DOWN;
+ }
+ if (keyboardModifiers.testFlag(Qt::AltModifier))
+ {
+ modifiers |= EVENTFLAG_ALT_DOWN;
+ }
+ if (keyboardModifiers.testFlag(Qt::MetaModifier))
+ {
+ modifiers |= EVENTFLAG_COMMAND_DOWN;
+ }
+ if (mouseButtons.testFlag(Qt::LeftButton))
+ {
+ modifiers |= EVENTFLAG_LEFT_MOUSE_BUTTON;
+ }
+ if (mouseButtons.testFlag(Qt::MiddleButton))
+ {
+ modifiers |= EVENTFLAG_MIDDLE_MOUSE_BUTTON;
+ }
+ if (mouseButtons.testFlag(Qt::RightButton))
+ {
+ modifiers |= EVENTFLAG_RIGHT_MOUSE_BUTTON;
+ }
+ return modifiers;
+ }
+
+ CefMouseEvent toCefMouseEvent(const QPointF& localPosition,
+ Qt::KeyboardModifiers keyboardModifiers,
+ Qt::MouseButtons mouseButtons)
+ {
+ CefMouseEvent event;
+ event.x = static_cast<int>(std::lround(localPosition.x()));
+ event.y = static_cast<int>(std::lround(localPosition.y()));
+ event.modifiers = toCefModifiers(keyboardModifiers, mouseButtons);
+ return event;
+ }
+
+ cef_mouse_button_type_t toCefMouseButton(Qt::MouseButton button)
+ {
+ switch (button)
+ {
+ case Qt::LeftButton: return MBT_LEFT;
+ case Qt::MiddleButton: return MBT_MIDDLE;
+ case Qt::RightButton: return MBT_RIGHT;
+ default: return MBT_LEFT;
+ }
+ }
+
+ int toWindowsKeyCode(const QKeyEvent* event)
+ {
+ switch (event->key())
+ {
+ case Qt::Key_Backspace: return 0x08;
+ case Qt::Key_Tab: return 0x09;
+ case Qt::Key_Return:
+ case Qt::Key_Enter: return 0x0D;
+ case Qt::Key_Shift: return 0x10;
+ case Qt::Key_Control: return 0x11;
+ case Qt::Key_Alt: return 0x12;
+ case Qt::Key_Pause: return 0x13;
+ case Qt::Key_CapsLock: return 0x14;
+ case Qt::Key_Escape: return 0x1B;
+ case Qt::Key_Space: return 0x20;
+ case Qt::Key_PageUp: return 0x21;
+ case Qt::Key_PageDown: return 0x22;
+ case Qt::Key_End: return 0x23;
+ case Qt::Key_Home: return 0x24;
+ case Qt::Key_Left: return 0x25;
+ case Qt::Key_Up: return 0x26;
+ case Qt::Key_Right: return 0x27;
+ case Qt::Key_Down: return 0x28;
+ case Qt::Key_Insert: return 0x2D;
+ case Qt::Key_Delete: return 0x2E;
+ case Qt::Key_0: return 0x30;
+ case Qt::Key_1: return 0x31;
+ case Qt::Key_2: return 0x32;
+ case Qt::Key_3: return 0x33;
+ case Qt::Key_4: return 0x34;
+ case Qt::Key_5: return 0x35;
+ case Qt::Key_6: return 0x36;
+ case Qt::Key_7: return 0x37;
+ case Qt::Key_8: return 0x38;
+ case Qt::Key_9: return 0x39;
+ case Qt::Key_A: return 0x41;
+ case Qt::Key_B: return 0x42;
+ case Qt::Key_C: return 0x43;
+ case Qt::Key_D: return 0x44;
+ case Qt::Key_E: return 0x45;
+ case Qt::Key_F: return 0x46;
+ case Qt::Key_G: return 0x47;
+ case Qt::Key_H: return 0x48;
+ case Qt::Key_I: return 0x49;
+ case Qt::Key_J: return 0x4A;
+ case Qt::Key_K: return 0x4B;
+ case Qt::Key_L: return 0x4C;
+ case Qt::Key_M: return 0x4D;
+ case Qt::Key_N: return 0x4E;
+ case Qt::Key_O: return 0x4F;
+ case Qt::Key_P: return 0x50;
+ case Qt::Key_Q: return 0x51;
+ case Qt::Key_R: return 0x52;
+ case Qt::Key_S: return 0x53;
+ case Qt::Key_T: return 0x54;
+ case Qt::Key_U: return 0x55;
+ case Qt::Key_V: return 0x56;
+ case Qt::Key_W: return 0x57;
+ case Qt::Key_X: return 0x58;
+ case Qt::Key_Y: return 0x59;
+ case Qt::Key_Z: return 0x5A;
+ case Qt::Key_F1: return 0x70;
+ case Qt::Key_F2: return 0x71;
+ case Qt::Key_F3: return 0x72;
+ case Qt::Key_F4: return 0x73;
+ case Qt::Key_F5: return 0x74;
+ case Qt::Key_F6: return 0x75;
+ case Qt::Key_F7: return 0x76;
+ case Qt::Key_F8: return 0x77;
+ case Qt::Key_F9: return 0x78;
+ case Qt::Key_F10: return 0x79;
+ case Qt::Key_F11: return 0x7A;
+ case Qt::Key_F12: return 0x7B;
+ default: break;
+ }
+
+ if (event->nativeVirtualKey() != 0)
+ {
+ return static_cast<int>(event->nativeVirtualKey());
+ }
+
+ return event->key();
+ }
+
+ bool isDarkPalette(const QPalette& palette)
+ {
+ const QColor baseColor = palette.color(QPalette::Base);
+ const QColor textColor = palette.color(QPalette::Text);
+ return baseColor.lightnessF() < textColor.lightnessF();
+ }
+
+ QString cssColor(const QColor& color)
+ {
+ return color.name(QColor::HexRgb);
+ }
+
+ cef_color_t toCefColor(const QColor& color)
+ {
+ return CefColorSetARGB(static_cast<uint8_t>(color.alpha()),
+ static_cast<uint8_t>(color.red()),
+ static_cast<uint8_t>(color.green()),
+ static_cast<uint8_t>(color.blue()));
+ }
+
+ QString launcherThemeBridgeScript(const QPalette& palette)
+ {
+ const bool prefersDark = isDarkPalette(palette);
+ const QString scheme = prefersDark ? QStringLiteral("dark") : QStringLiteral("light");
+ const QString baseColor = cssColor(palette.color(QPalette::Base));
+ const QString textColor = cssColor(palette.color(QPalette::Text));
+ const QString accentColor = cssColor(palette.color(QPalette::Highlight));
+ const QString surfaceColor = cssColor(palette.color(QPalette::AlternateBase));
+
+ return QStringLiteral(R"JS(
+(() => {
+ const theme = {
+ prefersDark: %1,
+ scheme: '%2',
+ baseColor: '%3',
+ textColor: '%4',
+ accentColor: '%5',
+ surfaceColor: '%6'
+ };
+ window.__projtLauncherTheme = theme;
+
+ const mountPoint = document.head || document.documentElement || document.body;
+ if (mountPoint) {
+ let style = document.getElementById('projt-launcher-theme-bridge');
+ if (!style) {
+ style = document.createElement('style');
+ style.id = 'projt-launcher-theme-bridge';
+ mountPoint.appendChild(style);
+ }
+ style.textContent = `
+ :root {
+ color-scheme: ${theme.scheme};
+ accent-color: ${theme.accentColor};
+ scrollbar-color: ${theme.accentColor} ${theme.surfaceColor};
+ --projt-launcher-base: ${theme.baseColor};
+ --projt-launcher-text: ${theme.textColor};
+ --projt-launcher-accent: ${theme.accentColor};
+ --projt-launcher-surface: ${theme.surfaceColor};
+ }
+ html, body {
+ background: ${theme.baseColor} !important;
+ color: ${theme.textColor} !important;
+ }
+ input, textarea, select, button {
+ color-scheme: ${theme.scheme} !important;
+ }
+ ::selection {
+ background: ${theme.accentColor};
+ }
+ `;
+ }
+
+ if (document.documentElement) {
+ document.documentElement.style.colorScheme = theme.scheme;
+ document.documentElement.style.backgroundColor = theme.baseColor;
+ document.documentElement.style.color = theme.textColor;
+ document.documentElement.dataset.projtLauncherScheme = theme.scheme;
+ }
+
+ if (document.body) {
+ document.body.style.backgroundColor = theme.baseColor;
+ document.body.style.color = theme.textColor;
+ document.body.style.accentColor = theme.accentColor;
+ }
+
+ let themeColorMeta = document.querySelector('meta[name="theme-color"]');
+ if (!themeColorMeta && document.head) {
+ themeColorMeta = document.createElement('meta');
+ themeColorMeta.name = 'theme-color';
+ document.head.appendChild(themeColorMeta);
+ }
+ if (themeColorMeta) {
+ themeColorMeta.content = theme.baseColor;
+ }
+
+ let colorSchemeMeta = document.querySelector('meta[name="color-scheme"]');
+ if (!colorSchemeMeta && document.head) {
+ colorSchemeMeta = document.createElement('meta');
+ colorSchemeMeta.name = 'color-scheme';
+ document.head.appendChild(colorSchemeMeta);
+ }
+ if (colorSchemeMeta) {
+ colorSchemeMeta.content = theme.prefersDark ? 'dark light' : 'light dark';
+ }
+
+ if (window.matchMedia && !window.__projtLauncherMatchMediaPatched) {
+ const originalMatchMedia = window.matchMedia.bind(window);
+ const createList = (query, matches) => {
+ const listeners = new Set();
+ return {
+ matches,
+ media: query,
+ onchange: null,
+ addListener(listener) {
+ if (typeof listener === 'function') listeners.add(listener);
+ },
+ removeListener(listener) {
+ listeners.delete(listener);
+ },
+ addEventListener(type, listener) {
+ if (type === 'change' && typeof listener === 'function') listeners.add(listener);
+ },
+ removeEventListener(type, listener) {
+ if (type === 'change') listeners.delete(listener);
+ },
+ dispatchEvent(event) {
+ listeners.forEach((listener) => listener(event));
+ if (typeof this.onchange === 'function') this.onchange(event);
+ return true;
+ }
+ };
+ };
+
+ window.matchMedia = (query) => {
+ const normalized = String(query).trim().toLowerCase();
+ if (normalized.includes('prefers-color-scheme')) {
+ const currentTheme = window.__projtLauncherTheme || { prefersDark: false };
+ const matches = (normalized.includes('dark') && currentTheme.prefersDark)
+ || (normalized.includes('light') && !currentTheme.prefersDark);
+ return createList(query, matches);
+ }
+ return originalMatchMedia(query);
+ };
+
+ Object.defineProperty(window, '__projtLauncherMatchMediaPatched', {
+ value: true,
+ configurable: true
+ });
+ }
+})();
+)JS")
+ .arg(prefersDark ? QStringLiteral("true") : QStringLiteral("false"),
+ scheme,
+ baseColor,
+ textColor,
+ accentColor,
+ surfaceColor);
+ }
+
+ class CefHubClient final : public CefClient,
+ public CefDisplayHandler,
+ public CefDialogHandler,
+ public CefJSDialogHandler,
+ public CefLifeSpanHandler,
+ public CefLoadHandler,
+ public CefPermissionHandler,
+ public CefRenderHandler,
+ public CefRequestHandler
+ {
+ public:
+ explicit CefHubClient(CefHubView* owner) : m_owner(owner)
+ {}
+
+ bool isPrimaryBrowser(CefRefPtr<CefBrowser> browser) const
+ {
+ return browser && m_browser && browser->GetIdentifier() == m_browser->GetIdentifier();
+ }
+
+ bool isUnexpectedSecondaryBrowser(CefRefPtr<CefBrowser> browser) const
+ {
+ return browser && m_browser && browser->GetIdentifier() != m_browser->GetIdentifier();
+ }
+
+ void queuePopupRequest(const QUrl& url)
+ {
+ if (!m_owner || !url.isValid() || url.isEmpty() || url == QUrl(QStringLiteral("about:blank")))
+ {
+ return;
+ }
+
+ QMetaObject::invokeMethod(
+ m_owner,
+ [owner = m_owner, url]() { owner->handlePopupRequest(url); },
+ Qt::QueuedConnection);
+ }
+
+ CefRefPtr<CefDisplayHandler> GetDisplayHandler() override
+ {
+ return this;
+ }
+
+ CefRefPtr<CefDialogHandler> GetDialogHandler() override
+ {
+ return this;
+ }
+
+ CefRefPtr<CefJSDialogHandler> GetJSDialogHandler() override
+ {
+ return this;
+ }
+
+ CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override
+ {
+ return this;
+ }
+
+ CefRefPtr<CefLoadHandler> GetLoadHandler() override
+ {
+ return this;
+ }
+
+ CefRefPtr<CefPermissionHandler> GetPermissionHandler() override
+ {
+ return this;
+ }
+
+ CefRefPtr<CefRenderHandler> GetRenderHandler() override
+ {
+ return this;
+ }
+
+ CefRefPtr<CefRequestHandler> GetRequestHandler() override
+ {
+ return this;
+ }
+
+ bool GetRootScreenRect(CefRefPtr<CefBrowser>, CefRect& rect) override
+ {
+ if (!m_owner)
+ {
+ return false;
+ }
+
+ const QRect globalRect(m_owner->mapToGlobal(QPoint(0, 0)), m_owner->size());
+ rect = CefRect(globalRect.x(),
+ globalRect.y(),
+ std::max(1, globalRect.width()),
+ std::max(1, globalRect.height()));
+ return true;
+ }
+
+ void GetViewRect(CefRefPtr<CefBrowser>, CefRect& rect) override
+ {
+ if (!m_owner)
+ {
+ rect = CefRect(0, 0, 1, 1);
+ return;
+ }
+
+ rect = CefRect(0, 0, std::max(1, m_owner->width()), std::max(1, m_owner->height()));
+ }
+
+ bool GetScreenPoint(CefRefPtr<CefBrowser>, int viewX, int viewY, int& screenX, int& screenY) override
+ {
+ if (!m_owner)
+ {
+ return false;
+ }
+
+ const QPoint globalPoint = m_owner->mapToGlobal(QPoint(viewX, viewY));
+ screenX = globalPoint.x();
+ screenY = globalPoint.y();
+ return true;
+ }
+
+ bool GetScreenInfo(CefRefPtr<CefBrowser>, CefScreenInfo& screen_info) override
+ {
+ if (!m_owner)
+ {
+ return false;
+ }
+
+ const QRect globalRect(m_owner->mapToGlobal(QPoint(0, 0)), m_owner->size());
+ const qreal scaleFactor = m_owner->devicePixelRatioF();
+
+ screen_info.device_scale_factor = scaleFactor;
+ screen_info.depth = 32;
+ screen_info.depth_per_component = 8;
+ screen_info.is_monochrome = false;
+ screen_info.rect = CefRect(globalRect.x(),
+ globalRect.y(),
+ std::max(1, globalRect.width()),
+ std::max(1, globalRect.height()));
+ screen_info.available_rect = screen_info.rect;
+ return true;
+ }
+
+ void OnPopupShow(CefRefPtr<CefBrowser>, bool show) override
+ {
+ if (m_owner)
+ {
+ m_owner->handlePopupVisibility(show);
+ }
+ }
+
+ void OnPopupSize(CefRefPtr<CefBrowser>, const CefRect& rect) override
+ {
+ if (m_owner)
+ {
+ m_owner->handlePopupRect(QRect(rect.x, rect.y, rect.width, rect.height));
+ }
+ }
+
+ void OnPaint(CefRefPtr<CefBrowser>,
+ PaintElementType type,
+ const RectList& dirtyRects,
+ const void* buffer,
+ int width,
+ int height) override
+ {
+ if (!m_owner || !buffer || width <= 0 || height <= 0)
+ {
+ return;
+ }
+
+ QImage image(static_cast<const uchar*>(buffer), width, height, QImage::Format_ARGB32);
+ QImage copy = image.copy();
+ copy.setDevicePixelRatio(m_owner->devicePixelRatioF());
+
+ QVector<QRect> qtDirtyRects;
+ qtDirtyRects.reserve(static_cast<qsizetype>(dirtyRects.size()));
+ for (const auto& rect : dirtyRects)
+ {
+ qtDirtyRects.append(QRect(rect.x, rect.y, rect.width, rect.height));
+ }
+
+ m_owner->handlePaint(type == PET_POPUP, copy, qtDirtyRects);
+ }
+
+ void OnAddressChange(CefRefPtr<CefBrowser> cefBrowser, CefRefPtr<CefFrame> frame, const CefString& url) override
+ {
+ if (!m_owner || !frame || !frame->IsMain())
+ {
+ return;
+ }
+ const QUrl qtUrl(QString::fromStdString(url.ToString()));
+ if (cefBrowser && (cefBrowser->IsPopup() || isUnexpectedSecondaryBrowser(cefBrowser)))
+ {
+ queuePopupRequest(qtUrl);
+ cefBrowser->GetHost()->CloseBrowser(true);
+ return;
+ }
+ if (cefBrowser && !isPrimaryBrowser(cefBrowser))
+ {
+ return;
+ }
+ QMetaObject::invokeMethod(
+ m_owner,
+ [owner = m_owner, qtUrl]() { owner->handleAddressChange(qtUrl); },
+ Qt::QueuedConnection);
+ }
+
+ void OnTitleChange(CefRefPtr<CefBrowser> browser, const CefString& title) override
+ {
+ if (browser && (browser->IsPopup() || !isPrimaryBrowser(browser)))
+ {
+ return;
+ }
+ const QString qtTitle = QString::fromStdString(title.ToString());
+ QMetaObject::invokeMethod(
+ m_owner,
+ [owner = m_owner, qtTitle]() { owner->handleTitleChange(qtTitle); },
+ Qt::QueuedConnection);
+ }
+
+ void OnLoadingStateChange(CefRefPtr<CefBrowser> browser,
+ bool isLoading,
+ bool canGoBack,
+ bool canGoForward) override
+ {
+ if (browser && browser->IsPopup())
+ {
+ return;
+ }
+ if (browser && isUnexpectedSecondaryBrowser(browser))
+ {
+ if (!isLoading)
+ {
+ const QUrl qtUrl(QString::fromStdString(browser->GetMainFrame()->GetURL().ToString()));
+ queuePopupRequest(qtUrl);
+ browser->GetHost()->CloseBrowser(true);
+ }
+ return;
+ }
+ if (browser && !isPrimaryBrowser(browser))
+ {
+ return;
+ }
+ QMetaObject::invokeMethod(
+ m_owner,
+ [owner = m_owner, isLoading, canGoBack, canGoForward]()
+ { owner->handleLoadingState(isLoading, canGoBack, canGoForward); },
+ Qt::QueuedConnection);
+ }
+
+ void OnLoadStart(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, TransitionType) override
+ {
+ if (!m_owner || !frame || !frame->IsMain())
+ {
+ return;
+ }
+ if (browser && (browser->IsPopup() || !isPrimaryBrowser(browser)))
+ {
+ return;
+ }
+
+ m_owner->applyLauncherThemeToFrame(frame);
+ }
+
+ void OnLoadEnd(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, int) override
+ {
+ if (!m_owner || !frame || !frame->IsMain())
+ {
+ return;
+ }
+ if (browser && (browser->IsPopup() || !isPrimaryBrowser(browser)))
+ {
+ return;
+ }
+ QMetaObject::invokeMethod(
+ m_owner,
+ [owner = m_owner]() { owner->handleLoadFinished(true); },
+ Qt::QueuedConnection);
+ }
+
+ void OnLoadError(CefRefPtr<CefBrowser> browser,
+ CefRefPtr<CefFrame> frame,
+ ErrorCode,
+ const CefString&,
+ const CefString&) override
+ {
+ if (!m_owner || !frame || !frame->IsMain())
+ {
+ return;
+ }
+ if (browser && (browser->IsPopup() || !isPrimaryBrowser(browser)))
+ {
+ return;
+ }
+ QMetaObject::invokeMethod(
+ m_owner,
+ [owner = m_owner]() { owner->handleLoadFinished(false); },
+ Qt::QueuedConnection);
+ }
+
+ void OnAfterCreated(CefRefPtr<CefBrowser> browser) override
+ {
+ if (!browser)
+ {
+ return;
+ }
+
+ if (!m_browser)
+ {
+ m_browser = browser;
+ return;
+ }
+
+ if (browser->IsPopup() || isUnexpectedSecondaryBrowser(browser))
+ {
+ if (m_pendingPopupUrl.isValid())
+ {
+ const QUrl qtUrl = m_pendingPopupUrl;
+ m_pendingPopupUrl = QUrl();
+ queuePopupRequest(qtUrl);
+ }
+ else
+ {
+ const QUrl qtUrl(QString::fromStdString(browser->GetMainFrame()->GetURL().ToString()));
+ queuePopupRequest(qtUrl);
+ }
+ browser->GetHost()->CloseBrowser(true);
+ }
+ }
+
+ bool OnBeforePopup(CefRefPtr<CefBrowser>,
+ CefRefPtr<CefFrame>,
+ int,
+ const CefString& target_url,
+ const CefString&,
+ cef_window_open_disposition_t,
+ bool,
+ const CefPopupFeatures&,
+ CefWindowInfo&,
+ CefRefPtr<CefClient>&,
+ CefBrowserSettings&,
+ CefRefPtr<CefDictionaryValue>&,
+ bool*) override
+ {
+ if (!m_owner)
+ {
+ return true;
+ }
+
+ const QUrl qtUrl(QString::fromStdString(target_url.ToString()));
+ if (qtUrl.isValid() && !qtUrl.isEmpty())
+ {
+ m_pendingPopupUrl = qtUrl;
+ queuePopupRequest(qtUrl);
+ }
+ return true;
+ }
+
+ bool OnOpenURLFromTab(CefRefPtr<CefBrowser>,
+ CefRefPtr<CefFrame>,
+ const CefString& target_url,
+ cef_window_open_disposition_t target_disposition,
+ bool) override
+ {
+ if (!m_owner)
+ {
+ return false;
+ }
+
+ switch (target_disposition)
+ {
+ case CEF_WOD_SINGLETON_TAB:
+ case CEF_WOD_NEW_FOREGROUND_TAB:
+ case CEF_WOD_NEW_BACKGROUND_TAB:
+ case CEF_WOD_SWITCH_TO_TAB:
+ case CEF_WOD_NEW_POPUP:
+ case CEF_WOD_NEW_WINDOW:
+ case CEF_WOD_OFF_THE_RECORD:
+ case CEF_WOD_UNKNOWN:
+ {
+ const QUrl qtUrl(QString::fromStdString(target_url.ToString()));
+ if (qtUrl.isValid() && !qtUrl.isEmpty())
+ {
+ m_pendingPopupUrl = qtUrl;
+ queuePopupRequest(qtUrl);
+ return true;
+ }
+ break;
+ }
+ default: break;
+ }
+
+ return false;
+ }
+
+ bool OnFileDialog(CefRefPtr<CefBrowser>,
+ FileDialogMode,
+ const CefString& title,
+ const CefString&,
+ const std::vector<CefString>&,
+ const std::vector<CefString>&,
+ const std::vector<CefString>&,
+ CefRefPtr<CefFileDialogCallback> callback) override
+ {
+ qWarning() << "[LauncherHub][CEF] Blocked file dialog:" << QString::fromStdString(title.ToString());
+ if (callback)
+ {
+ callback->Cancel();
+ }
+ return true;
+ }
+
+ bool OnJSDialog(CefRefPtr<CefBrowser>,
+ const CefString& origin_url,
+ JSDialogType,
+ const CefString& message_text,
+ const CefString&,
+ CefRefPtr<CefJSDialogCallback> callback,
+ bool& suppress_message) override
+ {
+ qWarning() << "[LauncherHub][CEF] Blocked JS dialog from" << QString::fromStdString(origin_url.ToString())
+ << ":" << QString::fromStdString(message_text.ToString());
+ suppress_message = true;
+ if (callback)
+ {
+ callback->Continue(false, CefString());
+ }
+ return true;
+ }
+
+ bool OnBeforeUnloadDialog(CefRefPtr<CefBrowser>,
+ const CefString& message_text,
+ bool,
+ CefRefPtr<CefJSDialogCallback> callback) override
+ {
+ qWarning() << "[LauncherHub][CEF] Auto-accepted beforeunload dialog:"
+ << QString::fromStdString(message_text.ToString());
+ if (callback)
+ {
+ callback->Continue(true, CefString());
+ }
+ return true;
+ }
+
+ bool OnRequestMediaAccessPermission(CefRefPtr<CefBrowser>,
+ CefRefPtr<CefFrame>,
+ const CefString& requesting_origin,
+ uint32_t,
+ CefRefPtr<CefMediaAccessCallback> callback) override
+ {
+ qWarning() << "[LauncherHub][CEF] Blocked media permission request from"
+ << QString::fromStdString(requesting_origin.ToString());
+ if (callback)
+ {
+ callback->Cancel();
+ }
+ return true;
+ }
+
+ bool OnShowPermissionPrompt(CefRefPtr<CefBrowser>,
+ uint64_t,
+ const CefString& requesting_origin,
+ uint32_t,
+ CefRefPtr<CefPermissionPromptCallback> callback) override
+ {
+ qWarning() << "[LauncherHub][CEF] Blocked permission prompt from"
+ << QString::fromStdString(requesting_origin.ToString());
+ if (callback)
+ {
+ callback->Continue(CEF_PERMISSION_RESULT_DENY);
+ }
+ return true;
+ }
+
+ void OnBeforeClose(CefRefPtr<CefBrowser> browser) override
+ {
+ if (browser && browser->IsPopup())
+ {
+ return;
+ }
+ if (browser && isUnexpectedSecondaryBrowser(browser))
+ {
+ return;
+ }
+ QMetaObject::invokeMethod(
+ m_owner,
+ [owner = m_owner]() { owner->handleBrowserClosed(); },
+ Qt::QueuedConnection);
+ m_browser = nullptr;
+ }
+
+ CefRefPtr<CefBrowser> browser() const
+ {
+ return m_browser;
+ }
+
+ void detachOwner()
+ {
+ m_owner = nullptr;
+ }
+
+ IMPLEMENT_REFCOUNTING(CefHubClient);
+
+ private:
+ CefHubView* m_owner = nullptr;
+ CefRefPtr<CefBrowser> m_browser;
+ QUrl m_pendingPopupUrl;
+ };
+}
+
+struct CefHubView::Impl
+{
+ CefRefPtr<CefHubClient> client;
+};
+
+CefHubView::CefHubView(QWidget* parent) : HubViewBase(parent), m_impl(new Impl())
+{
+ setAttribute(Qt::WA_NativeWindow);
+ setAttribute(Qt::WA_NoSystemBackground);
+ setAttribute(Qt::WA_OpaquePaintEvent);
+ setAttribute(Qt::WA_InputMethodEnabled);
+ setFocusPolicy(Qt::StrongFocus);
+ setMouseTracking(true);
+}
+
+CefHubView::~CefHubView()
+{
+ if (m_impl && m_impl->client)
+ {
+ m_impl->client->detachOwner();
+ if (m_impl->client->browser() && !m_closing)
+ {
+ m_closing = true;
+ m_impl->client->browser()->GetHost()->CloseBrowser(true);
+ }
+ }
+ delete m_impl;
+}
+
+void CefHubView::setUrl(const QUrl& url)
+{
+ m_url = url;
+ ensureBrowser();
+
+ if (m_impl && m_impl->client && m_impl->client->browser() && m_url.isValid())
+ {
+ m_impl->client->browser()->GetMainFrame()->LoadURL(m_url.toString().toStdString());
+ }
+}
+
+QUrl CefHubView::url() const
+{
+ return m_url;
+}
+
+bool CefHubView::canGoBack() const
+{
+ return m_canGoBack;
+}
+
+bool CefHubView::canGoForward() const
+{
+ return m_canGoForward;
+}
+
+void CefHubView::setActive(bool active)
+{
+ m_active = active;
+ if (m_impl && m_impl->client && m_impl->client->browser())
+ {
+ auto host = m_impl->client->browser()->GetHost();
+ host->WasHidden(!m_active || isHidden());
+ if (m_active && !isHidden())
+ {
+ host->Invalidate(PET_VIEW);
+ }
+ }
+}
+
+void CefHubView::back()
+{
+ if (m_impl && m_impl->client && m_impl->client->browser() && m_canGoBack)
+ {
+ m_impl->client->browser()->GoBack();
+ }
+}
+
+void CefHubView::forward()
+{
+ if (m_impl && m_impl->client && m_impl->client->browser() && m_canGoForward)
+ {
+ m_impl->client->browser()->GoForward();
+ }
+}
+
+void CefHubView::reload()
+{
+ if (m_impl && m_impl->client && m_impl->client->browser())
+ {
+ m_impl->client->browser()->Reload();
+ }
+}
+
+void CefHubView::paintEvent(QPaintEvent* event)
+{
+ Q_UNUSED(event);
+
+ QPainter painter(this);
+ painter.fillRect(rect(), palette().brush(QPalette::Base));
+
+ if (!m_viewImage.isNull())
+ {
+ painter.drawImage(QPoint(0, 0), m_viewImage);
+ }
+
+ if (m_popupVisible && !m_popupImage.isNull())
+ {
+ painter.drawImage(m_popupRect.topLeft(), m_popupImage);
+ }
+}
+
+void CefHubView::changeEvent(QEvent* event)
+{
+ HubViewBase::changeEvent(event);
+ if (!event)
+ {
+ return;
+ }
+
+ if (event->type() == QEvent::PaletteChange || event->type() == QEvent::ApplicationPaletteChange)
+ {
+ applyLauncherTheme();
+ }
+}
+
+void CefHubView::resizeEvent(QResizeEvent* event)
+{
+ HubViewBase::resizeEvent(event);
+ if (m_impl && m_impl->client && m_impl->client->browser())
+ {
+ auto host = m_impl->client->browser()->GetHost();
+ host->NotifyMoveOrResizeStarted();
+ host->NotifyScreenInfoChanged();
+ host->WasResized();
+ }
+ else
+ {
+ QTimer::singleShot(0, this, &CefHubView::ensureBrowser);
+ }
+}
+
+void CefHubView::showEvent(QShowEvent* event)
+{
+ HubViewBase::showEvent(event);
+ QTimer::singleShot(0, this, &CefHubView::ensureBrowser);
+ if (m_impl && m_impl->client && m_impl->client->browser())
+ {
+ m_impl->client->browser()->GetHost()->WasHidden(!m_active);
+ }
+}
+
+void CefHubView::hideEvent(QHideEvent* event)
+{
+ HubViewBase::hideEvent(event);
+ if (m_impl && m_impl->client && m_impl->client->browser())
+ {
+ m_impl->client->browser()->GetHost()->WasHidden(true);
+ }
+}
+
+void CefHubView::focusInEvent(QFocusEvent* event)
+{
+ HubViewBase::focusInEvent(event);
+ if (m_impl && m_impl->client && m_impl->client->browser())
+ {
+ m_impl->client->browser()->GetHost()->SetFocus(true);
+ }
+}
+
+void CefHubView::focusOutEvent(QFocusEvent* event)
+{
+ HubViewBase::focusOutEvent(event);
+ if (m_impl && m_impl->client && m_impl->client->browser())
+ {
+ m_impl->client->browser()->GetHost()->SetFocus(false);
+ }
+}
+
+void CefHubView::mouseMoveEvent(QMouseEvent* event)
+{
+ HubViewBase::mouseMoveEvent(event);
+ if (!(m_impl && m_impl->client && m_impl->client->browser()))
+ {
+ return;
+ }
+
+ m_impl->client->browser()->GetHost()->SendMouseMoveEvent(
+ toCefMouseEvent(event->position(), event->modifiers(), event->buttons()),
+ false);
+}
+
+void CefHubView::mousePressEvent(QMouseEvent* event)
+{
+ HubViewBase::mousePressEvent(event);
+ if (!(m_impl && m_impl->client && m_impl->client->browser()))
+ {
+ return;
+ }
+
+ setFocus(Qt::MouseFocusReason);
+ auto host = m_impl->client->browser()->GetHost();
+ host->SetFocus(true);
+ host->SendMouseClickEvent(toCefMouseEvent(event->position(), event->modifiers(), event->buttons()),
+ toCefMouseButton(event->button()),
+ false,
+ 1);
+}
+
+void CefHubView::mouseReleaseEvent(QMouseEvent* event)
+{
+ HubViewBase::mouseReleaseEvent(event);
+ if (!(m_impl && m_impl->client && m_impl->client->browser()))
+ {
+ return;
+ }
+
+ m_impl->client->browser()->GetHost()->SendMouseClickEvent(
+ toCefMouseEvent(event->position(), event->modifiers(), event->buttons()),
+ toCefMouseButton(event->button()),
+ true,
+ 1);
+}
+
+void CefHubView::wheelEvent(QWheelEvent* event)
+{
+ HubViewBase::wheelEvent(event);
+ if (!(m_impl && m_impl->client && m_impl->client->browser()))
+ {
+ return;
+ }
+
+ const QPoint angleDelta = event->angleDelta();
+ m_impl->client->browser()->GetHost()->SendMouseWheelEvent(
+ toCefMouseEvent(event->position(), event->modifiers(), event->buttons()),
+ angleDelta.x(),
+ angleDelta.y());
+}
+
+void CefHubView::keyPressEvent(QKeyEvent* event)
+{
+ HubViewBase::keyPressEvent(event);
+ if (!(m_impl && m_impl->client && m_impl->client->browser()))
+ {
+ return;
+ }
+
+ CefKeyEvent keyEvent;
+ keyEvent.type = KEYEVENT_RAWKEYDOWN;
+ keyEvent.modifiers = toCefModifiers(event->modifiers(), QApplication::mouseButtons());
+ if (event->isAutoRepeat())
+ {
+ keyEvent.modifiers |= EVENTFLAG_IS_REPEAT;
+ }
+ keyEvent.windows_key_code = toWindowsKeyCode(event);
+ keyEvent.native_key_code = static_cast<int>(event->nativeScanCode());
+ keyEvent.is_system_key = 0;
+ keyEvent.character = event->text().isEmpty() ? 0 : event->text().at(0).unicode();
+ keyEvent.unmodified_character = keyEvent.character;
+ keyEvent.focus_on_editable_field = 0;
+
+ auto host = m_impl->client->browser()->GetHost();
+ host->SendKeyEvent(keyEvent);
+
+ if (!event->text().isEmpty())
+ {
+ CefKeyEvent charEvent = keyEvent;
+ charEvent.type = KEYEVENT_CHAR;
+ host->SendKeyEvent(charEvent);
+ }
+}
+
+void CefHubView::keyReleaseEvent(QKeyEvent* event)
+{
+ HubViewBase::keyReleaseEvent(event);
+ if (!(m_impl && m_impl->client && m_impl->client->browser()))
+ {
+ return;
+ }
+
+ CefKeyEvent keyEvent;
+ keyEvent.type = KEYEVENT_KEYUP;
+ keyEvent.modifiers = toCefModifiers(event->modifiers(), QApplication::mouseButtons());
+ keyEvent.windows_key_code = toWindowsKeyCode(event);
+ keyEvent.native_key_code = static_cast<int>(event->nativeScanCode());
+ keyEvent.is_system_key = 0;
+ keyEvent.character = event->text().isEmpty() ? 0 : event->text().at(0).unicode();
+ keyEvent.unmodified_character = keyEvent.character;
+ keyEvent.focus_on_editable_field = 0;
+
+ m_impl->client->browser()->GetHost()->SendKeyEvent(keyEvent);
+}
+
+void CefHubView::leaveEvent(QEvent* event)
+{
+ HubViewBase::leaveEvent(event);
+ if (!(m_impl && m_impl->client && m_impl->client->browser()))
+ {
+ return;
+ }
+
+ m_impl->client->browser()->GetHost()->SendMouseMoveEvent(
+ toCefMouseEvent(QPointF(-1, -1), Qt::NoModifier, Qt::NoButton),
+ true);
+}
+
+void CefHubView::ensureBrowser()
+{
+ if (m_created || width() <= 0 || height() <= 0 || !projt::cef::Runtime::instance().isInitialized())
+ {
+ return;
+ }
+
+ m_impl->client = new CefHubClient(this);
+
+ CefWindowInfo windowInfo;
+ windowInfo.SetAsWindowless(static_cast<CefWindowHandle>(winId()));
+
+ CefBrowserSettings browserSettings;
+ browserSettings.windowless_frame_rate = 60;
+ const QColor baseColor = palette().color(QPalette::Base);
+ browserSettings.background_color = toCefColor(baseColor);
+ const QString initialUrl = m_url.isValid() ? m_url.toString() : QStringLiteral("about:blank");
+ auto browser = CefBrowserHost::CreateBrowserSync(windowInfo,
+ m_impl->client,
+ initialUrl.toStdString(),
+ browserSettings,
+ nullptr,
+ nullptr);
+ if (!browser)
+ {
+ emit loadFinished(false);
+ return;
+ }
+
+ m_created = true;
+ browser->GetHost()->SetWindowlessFrameRate(60);
+ browser->GetHost()->WasHidden(!m_active || isHidden());
+ browser->GetHost()->NotifyScreenInfoChanged();
+ applyLauncherTheme();
+ browser->GetHost()->Invalidate(PET_VIEW);
+ syncNavigationState();
+}
+
+void CefHubView::syncNavigationState()
+{
+ if (!m_impl || !m_impl->client || !m_impl->client->browser())
+ {
+ return;
+ }
+
+ const bool oldCanGoBack = m_canGoBack;
+ const bool oldCanGoForward = m_canGoForward;
+ m_canGoBack = m_impl->client->browser()->CanGoBack();
+ m_canGoForward = m_impl->client->browser()->CanGoForward();
+ if (oldCanGoBack != m_canGoBack || oldCanGoForward != m_canGoForward)
+ {
+ emit navigationStateChanged();
+ }
+}
+
+void CefHubView::handleAddressChange(const QUrl& url)
+{
+ m_url = url;
+ emit urlChanged(m_url);
+ syncNavigationState();
+}
+
+void CefHubView::handleTitleChange(const QString& title)
+{
+ m_title = title;
+ emit titleChanged(m_title);
+}
+
+void CefHubView::handleLoadingState(bool isLoading, bool canGoBack, bool canGoForward)
+{
+ Q_UNUSED(isLoading);
+ m_canGoBack = canGoBack;
+ m_canGoForward = canGoForward;
+ emit navigationStateChanged();
+}
+
+void CefHubView::handleLoadFinished(bool ok)
+{
+ applyLauncherTheme();
+ syncNavigationState();
+ emit loadFinished(ok);
+}
+
+void CefHubView::handleBrowserClosed()
+{
+ m_created = false;
+ m_closing = true;
+ emit navigationStateChanged();
+}
+
+void CefHubView::handlePopupRequest(const QUrl& url)
+{
+ if (!url.isValid())
+ {
+ return;
+ }
+
+ emit newTabRequested(url);
+}
+
+void CefHubView::handlePopupVisibility(bool visible)
+{
+ m_popupVisible = visible;
+ if (!m_popupVisible)
+ {
+ m_popupImage = QImage();
+ m_popupRect = QRect();
+ }
+ update();
+}
+
+void CefHubView::handlePopupRect(const QRect& rect)
+{
+ m_popupRect = rect;
+ update();
+}
+
+void CefHubView::handlePaint(bool popup, const QImage& image, const QVector<QRect>& dirtyRects)
+{
+ Q_UNUSED(dirtyRects);
+
+ if (popup)
+ {
+ m_popupImage = image;
+ }
+ else
+ {
+ m_viewImage = image;
+ }
+
+ update();
+}
+
+void CefHubView::applyLauncherTheme()
+{
+ if (!(m_impl && m_impl->client && m_impl->client->browser()))
+ {
+ return;
+ }
+
+ applyLauncherThemeToFrame(m_impl->client->browser()->GetMainFrame());
+
+ auto requestContext = m_impl->client->browser()->GetHost()->GetRequestContext();
+ if (!requestContext)
+ {
+ return;
+ }
+
+ const cef_color_variant_t variant =
+ isDarkPalette(palette()) ? CEF_COLOR_VARIANT_DARK : CEF_COLOR_VARIANT_LIGHT;
+ requestContext->SetChromeColorScheme(variant, toCefColor(palette().color(QPalette::Highlight)));
+}
+
+void CefHubView::applyLauncherThemeToFrame(CefRefPtr<CefFrame> frame)
+{
+ if (!frame)
+ {
+ return;
+ }
+
+ frame->ExecuteJavaScript(launcherThemeBridgeScript(palette()).toStdString(), frame->GetURL(), 0);
+}
+
+#endif