feat: support dip <-> screen conversion on Linux X11 (#46895)

feat: support dip <-> screen conversion on Linux

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
This commit is contained in:
trop[bot] 2025-05-02 19:08:50 -05:00 committed by GitHub
commit d783f134d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 106 additions and 31 deletions

View file

@ -670,6 +670,8 @@ source_set("electron_lib") {
sources += [ sources += [
"shell/browser/certificate_manager_model.cc", "shell/browser/certificate_manager_model.cc",
"shell/browser/certificate_manager_model.h", "shell/browser/certificate_manager_model.h",
"shell/browser/linux/x11_util.cc",
"shell/browser/linux/x11_util.h",
"shell/browser/ui/gtk_util.cc", "shell/browser/ui/gtk_util.cc",
"shell/browser/ui/gtk_util.h", "shell/browser/ui/gtk_util.h",
] ]

View file

@ -356,7 +356,7 @@ as `-webkit-app-region: drag` in a frameless window.
Calling `event.preventDefault()` will prevent the menu from being displayed. Calling `event.preventDefault()` will prevent the menu from being displayed.
To convert `point` to DIP, use [`screen.screenToDipPoint(point)`](./screen.md#screenscreentodippointpoint-windows). To convert `point` to DIP, use [`screen.screenToDipPoint(point)`](./screen.md#screenscreentodippointpoint-windows-linux).
### Static Methods ### Static Methods

View file

@ -435,7 +435,7 @@ as `-webkit-app-region: drag` in a frameless window.
Calling `event.preventDefault()` will prevent the menu from being displayed. Calling `event.preventDefault()` will prevent the menu from being displayed.
To convert `point` to DIP, use [`screen.screenToDipPoint(point)`](./screen.md#screenscreentodippointpoint-windows). To convert `point` to DIP, use [`screen.screenToDipPoint(point)`](./screen.md#screenscreentodippointpoint-windows-linux).
### Static Methods ### Static Methods

View file

@ -124,7 +124,7 @@ Returns [`Display`](structures/display.md) - The display nearest the specified p
Returns [`Display`](structures/display.md) - The display that most closely Returns [`Display`](structures/display.md) - The display that most closely
intersects the provided bounds. intersects the provided bounds.
### `screen.screenToDipPoint(point)` _Windows_ ### `screen.screenToDipPoint(point)` _Windows_ _Linux_
* `point` [Point](structures/point.md) * `point` [Point](structures/point.md)
@ -133,7 +133,10 @@ Returns [`Point`](structures/point.md)
Converts a screen physical point to a screen DIP point. Converts a screen physical point to a screen DIP point.
The DPI scale is performed relative to the display containing the physical point. The DPI scale is performed relative to the display containing the physical point.
### `screen.dipToScreenPoint(point)` _Windows_ Not currently supported on Wayland - if used there it will return the point passed
in with no changes.
### `screen.dipToScreenPoint(point)` _Windows_ _Linux_
* `point` [Point](structures/point.md) * `point` [Point](structures/point.md)
@ -142,6 +145,8 @@ Returns [`Point`](structures/point.md)
Converts a screen DIP point to a screen physical point. Converts a screen DIP point to a screen physical point.
The DPI scale is performed relative to the display containing the DIP point. The DPI scale is performed relative to the display containing the DIP point.
Not currently supported on Wayland.
### `screen.screenToDipRect(window, rect)` _Windows_ ### `screen.screenToDipRect(window, rect)` _Windows_
* `window` [BrowserWindow](browser-window.md) | null * `window` [BrowserWindow](browser-window.md) | null

View file

@ -20,11 +20,18 @@
#include "ui/display/display.h" #include "ui/display/display.h"
#include "ui/display/screen.h" #include "ui/display/screen.h"
#include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/vector2d_conversions.h"
#if BUILDFLAG(IS_WIN) #if BUILDFLAG(IS_WIN)
#include "ui/display/win/screen_win.h" #include "ui/display/win/screen_win.h"
#endif #endif
#if BUILDFLAG(IS_LINUX)
#include "shell/browser/linux/x11_util.h"
#endif
#if defined(USE_OZONE) #if defined(USE_OZONE)
#include "ui/ozone/public/ozone_platform.h" #include "ui/ozone/public/ozone_platform.h"
#endif #endif
@ -102,14 +109,6 @@ static gfx::Rect DIPToScreenRect(electron::NativeWindow* window,
return display::win::GetScreenWin()->DIPToScreenRect(hwnd, rect); return display::win::GetScreenWin()->DIPToScreenRect(hwnd, rect);
} }
static gfx::PointF ScreenToDIPPoint(const gfx::PointF& pixel_point) {
return display::win::GetScreenWin()->ScreenToDIPPoint(pixel_point);
}
static gfx::Point DIPToScreenPoint(const gfx::Point& dip_point) {
return display::win::GetScreenWin()->DIPToScreenPoint(dip_point);
}
#endif #endif
void Screen::OnDisplayAdded(const display::Display& new_display) { void Screen::OnDisplayAdded(const display::Display& new_display) {
@ -134,6 +133,44 @@ void Screen::OnDisplayMetricsChanged(const display::Display& display,
MetricsToArray(changed_metrics))); MetricsToArray(changed_metrics)));
} }
gfx::PointF Screen::ScreenToDIPPoint(const gfx::PointF& point_px) {
#if BUILDFLAG(IS_WIN)
return display::win::GetScreenWin()->ScreenToDIPPoint(point_px);
#elif BUILDFLAG(IS_LINUX)
if (x11_util::IsX11()) {
gfx::Point pt_px = gfx::ToFlooredPoint(point_px);
display::Display display = GetDisplayNearestPoint(pt_px);
gfx::Vector2d delta_px = pt_px - display.native_origin();
gfx::Vector2dF delta_dip =
gfx::ScaleVector2d(delta_px, 1.0 / display.device_scale_factor());
return gfx::PointF(display.bounds().origin()) + delta_dip;
} else {
return point_px;
}
#else
return point_px;
#endif
}
gfx::Point Screen::DIPToScreenPoint(const gfx::Point& point_dip) {
#if BUILDFLAG(IS_WIN)
return display::win::GetScreenWin()->DIPToScreenPoint(point_dip);
#elif BUILDFLAG(IS_LINUX)
if (x11_util::IsX11()) {
display::Display display = GetDisplayNearestPoint(point_dip);
gfx::Rect bounds_dip = display.bounds();
gfx::Vector2d delta_dip = point_dip - bounds_dip.origin();
gfx::Vector2d delta_px = gfx::ToFlooredVector2d(
gfx::ScaleVector2d(delta_dip, display.device_scale_factor()));
return display.native_origin() + delta_px;
} else {
return point_dip;
}
#else
return point_dip;
#endif
}
// static // static
v8::Local<v8::Value> Screen::Create(gin_helper::ErrorThrower error_thrower) { v8::Local<v8::Value> Screen::Create(gin_helper::ErrorThrower error_thrower) {
if (!Browser::Get()->is_ready()) { if (!Browser::Get()->is_ready()) {
@ -161,9 +198,11 @@ gin::ObjectTemplateBuilder Screen::GetObjectTemplateBuilder(
.SetMethod("getPrimaryDisplay", &Screen::GetPrimaryDisplay) .SetMethod("getPrimaryDisplay", &Screen::GetPrimaryDisplay)
.SetMethod("getAllDisplays", &Screen::GetAllDisplays) .SetMethod("getAllDisplays", &Screen::GetAllDisplays)
.SetMethod("getDisplayNearestPoint", &Screen::GetDisplayNearestPoint) .SetMethod("getDisplayNearestPoint", &Screen::GetDisplayNearestPoint)
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
.SetMethod("screenToDipPoint", &Screen::ScreenToDIPPoint)
.SetMethod("dipToScreenPoint", &Screen::DIPToScreenPoint)
#endif
#if BUILDFLAG(IS_WIN) #if BUILDFLAG(IS_WIN)
.SetMethod("screenToDipPoint", &ScreenToDIPPoint)
.SetMethod("dipToScreenPoint", &DIPToScreenPoint)
.SetMethod("screenToDipRect", &ScreenToDIPRect) .SetMethod("screenToDipRect", &ScreenToDIPRect)
.SetMethod("dipToScreenRect", &DIPToScreenRect) .SetMethod("dipToScreenRect", &DIPToScreenRect)
#endif #endif

View file

@ -15,6 +15,7 @@
namespace gfx { namespace gfx {
class Point; class Point;
class PointF;
class Rect; class Rect;
class Screen; class Screen;
} // namespace gfx } // namespace gfx
@ -58,6 +59,9 @@ class Screen final : public gin::Wrappable<Screen>,
return screen_->GetDisplayMatching(match_rect); return screen_->GetDisplayMatching(match_rect);
} }
gfx::PointF ScreenToDIPPoint(const gfx::PointF& point_px);
gfx::Point DIPToScreenPoint(const gfx::Point& point_dip);
// display::DisplayObserver: // display::DisplayObserver:
void OnDisplayAdded(const display::Display& new_display) override; void OnDisplayAdded(const display::Display& new_display) override;
void OnDisplaysRemoved(const display::Displays& removed_displays) override; void OnDisplaysRemoved(const display::Displays& removed_displays) override;

View file

@ -0,0 +1,17 @@
// Copyright (c) 2025 Microsoft GmbH.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/linux/x11_util.h"
#include "ui/ozone/public/ozone_platform.h"
namespace x11_util {
bool IsX11() {
return ui::OzonePlatform::GetInstance()
->GetPlatformProperties()
.electron_can_call_x11;
}
} // namespace x11_util

View file

@ -0,0 +1,14 @@
// Copyright (c) 2025 Microsoft GmbH.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_LINUX_X11_UTIL_H_
#define ELECTRON_SHELL_BROWSER_LINUX_X11_UTIL_H_
namespace x11_util {
bool IsX11();
} // namespace x11_util
#endif // ELECTRON_SHELL_BROWSER_LINUX_X11_UTIL_H_

View file

@ -53,6 +53,7 @@
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "shell/browser/browser.h" #include "shell/browser/browser.h"
#include "shell/browser/linux/unity_service.h" #include "shell/browser/linux/unity_service.h"
#include "shell/browser/linux/x11_util.h"
#include "shell/browser/ui/electron_desktop_window_tree_host_linux.h" #include "shell/browser/ui/electron_desktop_window_tree_host_linux.h"
#include "shell/browser/ui/views/client_frame_view_linux.h" #include "shell/browser/ui/views/client_frame_view_linux.h"
#include "shell/browser/ui/views/native_frame_view.h" #include "shell/browser/ui/views/native_frame_view.h"
@ -163,13 +164,6 @@ gfx::Size WindowSizeToContentSizeBuggy(HWND hwnd, const gfx::Size& size) {
#endif #endif
[[maybe_unused, nodiscard]] bool IsX11() {
static const bool is_x11 = ui::OzonePlatform::GetInstance()
->GetPlatformProperties()
.electron_can_call_x11;
return is_x11;
}
class NativeWindowClientView : public views::ClientView { class NativeWindowClientView : public views::ClientView {
public: public:
NativeWindowClientView(views::Widget* widget, NativeWindowClientView(views::Widget* widget,
@ -331,7 +325,7 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
if (parent) if (parent)
SetParentWindow(parent); SetParentWindow(parent);
if (IsX11()) { if (x11_util::IsX11()) {
// Before the window is mapped the SetWMSpecState can not work, so we have // Before the window is mapped the SetWMSpecState can not work, so we have
// to manually set the _NET_WM_STATE. // to manually set the _NET_WM_STATE.
std::vector<x11::Atom> state_atom_list; std::vector<x11::Atom> state_atom_list;
@ -452,7 +446,7 @@ NativeWindowViews::~NativeWindowViews() {
void NativeWindowViews::SetGTKDarkThemeEnabled(bool use_dark_theme) { void NativeWindowViews::SetGTKDarkThemeEnabled(bool use_dark_theme) {
#if BUILDFLAG(IS_LINUX) #if BUILDFLAG(IS_LINUX)
if (IsX11()) { if (x11_util::IsX11()) {
const std::string color = use_dark_theme ? "dark" : "light"; const std::string color = use_dark_theme ? "dark" : "light";
auto* connection = x11::Connection::Get(); auto* connection = x11::Connection::Get();
connection->SetStringProperty( connection->SetStringProperty(
@ -514,7 +508,7 @@ void NativeWindowViews::Show() {
// On X11, setting Z order before showing the window doesn't take effect, // On X11, setting Z order before showing the window doesn't take effect,
// so we have to call it again. // so we have to call it again.
if (IsX11()) if (x11_util::IsX11())
widget()->SetZOrderLevel(widget()->GetZOrderLevel()); widget()->SetZOrderLevel(widget()->GetZOrderLevel());
#endif #endif
} }
@ -530,7 +524,7 @@ void NativeWindowViews::ShowInactive() {
// On X11, setting Z order before showing the window doesn't take effect, // On X11, setting Z order before showing the window doesn't take effect,
// so we have to call it again. // so we have to call it again.
if (IsX11()) if (x11_util::IsX11())
widget()->SetZOrderLevel(widget()->GetZOrderLevel()); widget()->SetZOrderLevel(widget()->GetZOrderLevel());
#endif #endif
} }
@ -575,7 +569,7 @@ bool NativeWindowViews::IsEnabled() const {
#if BUILDFLAG(IS_WIN) #if BUILDFLAG(IS_WIN)
return ::IsWindowEnabled(GetAcceleratedWidget()); return ::IsWindowEnabled(GetAcceleratedWidget());
#elif BUILDFLAG(IS_LINUX) #elif BUILDFLAG(IS_LINUX)
if (IsX11()) if (x11_util::IsX11())
return !event_disabler_.get(); return !event_disabler_.get();
NOTIMPLEMENTED(); NOTIMPLEMENTED();
return true; return true;
@ -612,7 +606,7 @@ void NativeWindowViews::SetEnabledInternal(bool enable) {
#if BUILDFLAG(IS_WIN) #if BUILDFLAG(IS_WIN)
::EnableWindow(GetAcceleratedWidget(), enable); ::EnableWindow(GetAcceleratedWidget(), enable);
#else #else
if (IsX11()) { if (x11_util::IsX11()) {
views::DesktopWindowTreeHostPlatform* tree_host = views::DesktopWindowTreeHostPlatform* tree_host =
views::DesktopWindowTreeHostLinux::GetHostForWidget( views::DesktopWindowTreeHostLinux::GetHostForWidget(
GetAcceleratedWidget()); GetAcceleratedWidget());
@ -944,7 +938,7 @@ bool NativeWindowViews::MoveAbove(const std::string& sourceId) {
0, 0, 0, 0, 0, 0,
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
#else #else
if (IsX11()) { if (x11_util::IsX11()) {
if (!IsWindowValid(static_cast<x11::Window>(id.id))) if (!IsWindowValid(static_cast<x11::Window>(id.id)))
return false; return false;
@ -966,7 +960,7 @@ void NativeWindowViews::MoveTop() {
size.width(), size.height(), size.width(), size.height(),
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
#else #else
if (IsX11()) if (x11_util::IsX11())
electron::MoveWindowToForeground( electron::MoveWindowToForeground(
static_cast<x11::Window>(GetAcceleratedWidget())); static_cast<x11::Window>(GetAcceleratedWidget()));
#endif #endif
@ -1280,7 +1274,7 @@ void NativeWindowViews::SetIgnoreMouseEvents(bool ignore, bool forward) {
SetForwardMouseMessages(forward); SetForwardMouseMessages(forward);
} }
#else #else
if (IsX11()) { if (x11_util::IsX11()) {
auto* connection = x11::Connection::Get(); auto* connection = x11::Connection::Get();
if (ignore) { if (ignore) {
x11::Rectangle r{0, 0, 1, 1}; x11::Rectangle r{0, 0, 1, 1};
@ -1401,7 +1395,7 @@ void NativeWindowViews::SetParentWindow(NativeWindow* parent) {
NativeWindow::SetParentWindow(parent); NativeWindow::SetParentWindow(parent);
#if BUILDFLAG(IS_LINUX) #if BUILDFLAG(IS_LINUX)
if (IsX11()) { if (x11_util::IsX11()) {
auto* connection = x11::Connection::Get(); auto* connection = x11::Connection::Get();
connection->SetProperty( connection->SetProperty(
static_cast<x11::Window>(GetAcceleratedWidget()), static_cast<x11::Window>(GetAcceleratedWidget()),