UI/Qt: Add an advanced setting to enable server-side decorations

This adds a setting to disable our custom window decorations and let the
system's window server paint the window instead.
This commit is contained in:
Timothy Flynn 2026-05-29 09:57:15 -04:00 committed by Sam Atkins
parent 5b2dc0dabc
commit e18cd253f0
8 changed files with 63 additions and 6 deletions

View file

@ -73,6 +73,7 @@ public:
static ImageDecoderClient::Client& image_decoder_client() { return *the().m_image_decoder_client; }
virtual bool supports_vertical_tabs() const { return false; }
virtual bool supports_server_side_window_decorations() const { return false; }
void tab_settings_changed(Badge<ApplicationSettingsObserver>);
static BookmarkStore& bookmark_store() { return the().m_bookmark_store; }

View file

@ -98,6 +98,14 @@ static Array<ConfigVariableDefinition, static_cast<size_t>(ConfigVariableID::Cou
.default_value = true,
.array_element_type = {},
},
{
.id = ConfigVariableID::UseServerSideWindowDecorations,
.name = "ui.window.use_server_side_decorations"sv,
.title = "Use server-side window decorations"sv,
.description = "Use the system window frame instead of the custom title bar and window controls."sv,
.default_value = false,
.array_element_type = {},
},
} };
ReadonlySpan<ConfigVariableDefinition const> config_variable_definitions()

View file

@ -54,6 +54,7 @@ enum class ConfigVariableID : u8 {
ShowAdvancedDebugMenu,
ContentBlockerListPaths,
UseRoundedWindowCorners,
UseServerSideWindowDecorations,
Count,
};

View file

@ -39,6 +39,8 @@ static bool should_show_config_variable([[maybe_unused]] ConfigVariableID id)
if (id == ConfigVariableID::UseRoundedWindowCorners)
return false;
#endif
if (id == ConfigVariableID::UseServerSideWindowDecorations)
return Application::the().supports_server_side_window_decorations();
return true;
}

View file

@ -60,6 +60,7 @@ private:
virtual void insert_clipboard_entry(Web::Clipboard::SystemClipboardRepresentation) override;
virtual bool supports_vertical_tabs() const override { return true; }
virtual bool supports_server_side_window_decorations() const override { return true; }
virtual void update_tabs_display() const override;
virtual void rebuild_bookmarks_menu() const override;

View file

@ -231,7 +231,7 @@ BrowserWindow::BrowserWindow(Vector<URL::URL> const& initial_urls, IsPopupWindow
{
auto const& browser_options = WebView::Application::browser_options();
setWindowFlag(Qt::FramelessWindowHint);
setWindowFlag(Qt::FramelessWindowHint, uses_client_side_decorations());
setAttribute(Qt::WA_OpaquePaintEvent);
setWindowIcon(app_icon());
qApp->installEventFilter(this);
@ -573,6 +573,11 @@ FullscreenMode& BrowserWindow::fullscreen_mode()
return *m_fullscreen_mode;
}
bool BrowserWindow::uses_client_side_decorations()
{
return !WebView::Application::settings().config_variable_as_bool(WebView::ConfigVariableID::UseServerSideWindowDecorations);
}
Tab& BrowserWindow::create_new_tab(Web::HTML::ActivateTab activate_tab)
{
auto* tab = new Tab(this);
@ -931,8 +936,8 @@ void BrowserWindow::update_menu_bar_visibility(bool show_menubar)
{
menuBar()->setVisible(show_menubar);
if (m_menu_bar_window_controls)
m_menu_bar_window_controls->setVisible(show_menubar);
m_tabs_container->set_window_controls_visible(!show_menubar);
m_menu_bar_window_controls->setVisible(show_menubar && uses_client_side_decorations());
m_tabs_container->set_window_controls_visible(!show_menubar && uses_client_side_decorations());
}
void BrowserWindow::update_menu_bar_window_control_icons()
@ -947,6 +952,33 @@ void BrowserWindow::update_menu_bar_window_control_icons()
m_menu_bar_close_window_button->setIcon(create_chrome_icon(ChromeIcon::WindowClose, palette()));
}
void BrowserWindow::update_window_decoration_state()
{
clear_resize_cursor();
auto should_be_frameless = uses_client_side_decorations();
auto is_frameless = windowFlags().testFlag(Qt::FramelessWindowHint);
if (is_frameless != should_be_frameless) {
auto was_visible = isVisible();
auto was_fullscreen = isFullScreen();
auto was_maximized = isMaximized();
setWindowFlag(Qt::FramelessWindowHint, should_be_frameless);
if (was_visible) {
if (was_fullscreen)
showFullScreen();
else if (was_maximized)
showMaximized();
else
show();
}
}
update_menu_bar_visibility(Settings::the()->show_menubar());
}
void BrowserWindow::toggle_window_maximized()
{
if (isMaximized())
@ -1133,6 +1165,8 @@ bool BrowserWindow::eventFilter(QObject* object, QEvent* event)
auto* widget = as_if<QWidget>(object);
if (!widget || widget->window() != this)
return QMainWindow::eventFilter(object, event);
if (!uses_client_side_decorations())
return QMainWindow::eventFilter(object, event);
auto const is_button = qobject_cast<QAbstractButton*>(object) != nullptr;
@ -1233,7 +1267,7 @@ Optional<Qt::CursorShape> BrowserWindow::resize_cursor_for_edges(Qt::Edges edges
void BrowserWindow::update_resize_cursor(QPoint const& position)
{
if (isMaximized() || isFullScreen() || !rect().contains(position)) {
if (!uses_client_side_decorations() || isMaximized() || isFullScreen() || !rect().contains(position)) {
clear_resize_cursor();
return;
}
@ -1301,6 +1335,8 @@ void BrowserWindow::config_variable_changed(WebView::ConfigVariableID variable)
{
if (variable == WebView::ConfigVariableID::UseRoundedWindowCorners)
update_window_corners();
else if (variable == WebView::ConfigVariableID::UseServerSideWindowDecorations)
update_window_decoration_state();
}
void BrowserWindow::update_window_corners()

View file

@ -106,6 +106,7 @@ public:
FullscreenMode& fullscreen_mode();
QMenu& hamburger_menu() const { return *m_hamburger_menu; }
static bool uses_client_side_decorations();
QAction& new_window_action() const { return *m_new_window_action; }
QAction& find_action() const { return *m_find_in_page_action; }
@ -181,6 +182,7 @@ private:
void update_menu_bar_style();
void update_menu_bar_visibility(bool);
void update_menu_bar_window_control_icons();
void update_window_decoration_state();
void toggle_window_maximized();
bool start_window_move();
bool connect_window_screen_changed_signal();

View file

@ -99,6 +99,12 @@ static QPointer<Tab> s_active_tab_dragged_tab;
static QPointer<TabWidget> s_pending_tab_drop_target;
static int s_pending_tab_drop_index { -1 };
static bool window_uses_client_side_decorations(QWidget& widget)
{
auto* top_level_window = qobject_cast<BrowserWindow*>(widget.window());
return !top_level_window || top_level_window->uses_client_side_decorations();
}
static QPainterPath tab_shape_path(QRectF const& rect, qreal top_radius, qreal bottom_radius)
{
top_radius = min(top_radius, rect.height() / 2.0);
@ -735,7 +741,7 @@ void TabBar::mouseReleaseEvent(QMouseEvent* event)
void TabBar::mouseDoubleClickEvent(QMouseEvent* event)
{
if (tab_index_at(event->pos()) < 0 && event->button() == Qt::LeftButton) {
if (window_uses_client_side_decorations(*this) && tab_index_at(event->pos()) < 0 && event->button() == Qt::LeftButton) {
toggle_window_maximized();
event->accept();
return;
@ -1465,7 +1471,7 @@ bool TabWidget::eventFilter(QObject* watched, QEvent* event)
}
}
if (watched == m_tab_bar_row || watched == m_vertical_tab_bar_column) {
if ((watched == m_tab_bar_row || watched == m_vertical_tab_bar_column) && window_uses_client_side_decorations(*this)) {
auto is_empty_chrome_area = [this, watched](QMouseEvent const& mouse_event) {
if (watched == m_vertical_tab_bar_column) {
auto* child = m_vertical_tab_bar_column->childAt(mouse_event.pos());