Reland "Add VirtualDisplayUtilLinux with X11 support."
This is a reland of commit c20ada30ff
I've updated the build file conditionals to prevent an assertion
error on wayland ozone configuration on LaCros.
Original change's description:
> Add VirtualDisplayUtilLinux with X11 support.
>
> Adds VirtualDisplayUtilLinux class which uses
> remoting::X11DesktopResizer as the backing x11/XRandR logic.
> Adjusts some build rules to isolate some of the //remoting x11 utilities into its own component (to avoid layering/dependency violations).
>
> Bug: 40257169
> Change-Id: Ic7c3dd3cb7070acb6374ab2a161cff29ce8383bf
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5422325
> Reviewed-by: Lambros Lambrou <lambroslambrou@chromium.org>
> Auto-Submit: Brad Triebwasser <btriebw@chromium.org>
> Reviewed-by: Bruce Dawson <brucedawson@chromium.org>
> Reviewed-by: Mike Wasserman <msw@chromium.org>
> Reviewed-by: ccameron chromium <ccameron@chromium.org>
> Commit-Queue: Brad Triebwasser <btriebw@chromium.org>
> Reviewed-by: Ben Pastene <bpastene@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1304762}
Bug: 40257169
Change-Id: Iff5e306df1d5abedca600ea213378d9baa88d1fa
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5560555
Reviewed-by: ccameron chromium <ccameron@chromium.org>
Reviewed-by: Lambros Lambrou <lambroslambrou@chromium.org>
Reviewed-by: Mike Wasserman <msw@chromium.org>
Reviewed-by: Bruce Dawson <brucedawson@chromium.org>
Commit-Queue: Brad Triebwasser <btriebw@chromium.org>
Reviewed-by: Ben Pastene <bpastene@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1305345}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
3ee67f50bc
commit
d6ce652d6f
build/config/linux/gtk
chrome/test
remoting/host
testing
ui/display
@ -32,6 +32,7 @@ group("gtk") {
|
||||
"//examples:peerconnection_client",
|
||||
"//remoting/host:common",
|
||||
"//remoting/host:remoting_me2me_host_static",
|
||||
"//remoting/host:x11_display_utils",
|
||||
"//remoting/host/file_transfer",
|
||||
"//remoting/host/it2me:common",
|
||||
"//remoting/host/it2me:main",
|
||||
|
@ -11272,7 +11272,6 @@ if (!is_android && !is_chromeos_device) {
|
||||
"//ui/base:test_support",
|
||||
"//ui/base/clipboard:clipboard_test_support",
|
||||
"//ui/compositor:test_support",
|
||||
"//ui/display:display_interactive_ui_tests",
|
||||
"//ui/display:test_support",
|
||||
"//ui/events:events_interactive_ui_tests",
|
||||
"//ui/events:gesture_detection",
|
||||
@ -11698,6 +11697,7 @@ if (!is_android && !is_chromeos_device) {
|
||||
"../browser/ui/views/accessibility/uia_accessibility_event_waiter.cc",
|
||||
"../browser/ui/views/accessibility/uia_accessibility_event_waiter.h",
|
||||
"../browser/ui/views/touch_events_interactive_uitest_win.cc",
|
||||
"//ui/display/win/test/virtual_display_util_win_interactive_uitest.cc",
|
||||
]
|
||||
|
||||
if (use_aura) {
|
||||
@ -11740,6 +11740,7 @@ if (!is_android && !is_chromeos_device) {
|
||||
"../browser/ui/find_bar/find_bar_platform_helper_mac_interactive_uitest.mm",
|
||||
"../browser/ui/views/frame/immersive_mode_controller_mac_interactive_uitest.mm",
|
||||
"../browser/webauthn/chrome_webauthn_autofill_mac_interactive_uitest.mm",
|
||||
"//ui/display/mac/test/virtual_display_util_mac_interactive_uitest.mm",
|
||||
]
|
||||
|
||||
sources -= [
|
||||
@ -11794,6 +11795,9 @@ if (!is_android && !is_chromeos_device) {
|
||||
}
|
||||
|
||||
if (is_linux) {
|
||||
if (ozone_platform_x11) {
|
||||
sources += [ "//ui/display/linux/test/virtual_display_util_linux_interactive_uitest.cc" ]
|
||||
}
|
||||
deps += [ "//ui/linux:linux_ui" ]
|
||||
}
|
||||
|
||||
|
@ -297,10 +297,6 @@ static_library("common") {
|
||||
"desktop_display_info_loader.h",
|
||||
"desktop_display_info_monitor.cc",
|
||||
"desktop_display_info_monitor.h",
|
||||
"desktop_display_layout_util.cc",
|
||||
"desktop_display_layout_util.h",
|
||||
"desktop_geometry.cc",
|
||||
"desktop_geometry.h",
|
||||
"desktop_process.cc",
|
||||
"desktop_process.h",
|
||||
"desktop_resizer.h",
|
||||
@ -437,6 +433,7 @@ static_library("common") {
|
||||
deps = [
|
||||
":client_session_control",
|
||||
":clipboard",
|
||||
":display_layout",
|
||||
":host_main_headers",
|
||||
":ipc_constants",
|
||||
":resources",
|
||||
@ -570,18 +567,12 @@ static_library("common") {
|
||||
"linux/desktop_resizer_wayland.h",
|
||||
"linux/ei_event_watcher_glib.cc",
|
||||
"linux/ei_event_watcher_glib.h",
|
||||
"linux/gnome_display_config.cc",
|
||||
"linux/gnome_display_config.h",
|
||||
"linux/gnome_display_config_dbus_client.cc",
|
||||
"linux/gnome_display_config_dbus_client.h",
|
||||
"linux/input_injector_wayland.cc",
|
||||
"linux/input_injector_wayland.h",
|
||||
"linux/remote_desktop_portal.cc",
|
||||
"linux/remote_desktop_portal.h",
|
||||
"linux/remote_desktop_portal_injector.cc",
|
||||
"linux/remote_desktop_portal_injector.h",
|
||||
"linux/scoped_glib.cc",
|
||||
"linux/scoped_glib.h",
|
||||
"linux/wayland_connection.cc",
|
||||
"linux/wayland_connection.h",
|
||||
"linux/wayland_desktop_capturer.cc",
|
||||
@ -596,12 +587,6 @@ static_library("common") {
|
||||
"linux/wayland_manager.h",
|
||||
"linux/wayland_seat.cc",
|
||||
"linux/wayland_seat.h",
|
||||
"x11_crtc_resizer.cc",
|
||||
"x11_crtc_resizer.h",
|
||||
"x11_desktop_resizer.cc",
|
||||
"x11_desktop_resizer.h",
|
||||
"x11_display_util.cc",
|
||||
"x11_display_util.h",
|
||||
]
|
||||
libs += [ "//third_party/libei/lib64/libei.a" ]
|
||||
public_deps += [
|
||||
@ -609,9 +594,8 @@ static_library("common") {
|
||||
"//third_party/wayland-protocols:xdg_output_protocol",
|
||||
]
|
||||
deps += [
|
||||
"//build/config/linux/gtk",
|
||||
":x11_display_utils",
|
||||
"//remoting/host/linux:wayland",
|
||||
"//remoting/host/linux:x11",
|
||||
"//ui/base/x",
|
||||
"//ui/events/platform/x11",
|
||||
"//ui/gfx/x",
|
||||
@ -743,6 +727,45 @@ static_library("common") {
|
||||
}
|
||||
}
|
||||
|
||||
source_set("display_layout") {
|
||||
sources = [
|
||||
"desktop_display_layout_util.cc",
|
||||
"desktop_display_layout_util.h",
|
||||
"desktop_geometry.cc",
|
||||
"desktop_geometry.h",
|
||||
]
|
||||
deps = [ "//ui/gfx" ]
|
||||
}
|
||||
|
||||
if (is_linux && (ozone_platform_x11 || remoting_use_x11)) {
|
||||
source_set("x11_display_utils") {
|
||||
sources = [
|
||||
"linux/gnome_display_config.cc",
|
||||
"linux/gnome_display_config.h",
|
||||
"linux/gnome_display_config_dbus_client.cc",
|
||||
"linux/gnome_display_config_dbus_client.h",
|
||||
"linux/scoped_glib.cc",
|
||||
"linux/scoped_glib.h",
|
||||
"x11_crtc_resizer.cc",
|
||||
"x11_crtc_resizer.h",
|
||||
"x11_desktop_resizer.cc",
|
||||
"x11_desktop_resizer.h",
|
||||
"x11_display_util.cc",
|
||||
"x11_display_util.h",
|
||||
]
|
||||
deps = [
|
||||
":display_layout",
|
||||
"//build/config/linux/gtk",
|
||||
"//remoting/base:logging",
|
||||
"//remoting/host/linux:x11",
|
||||
"//third_party/webrtc_overrides:webrtc_component",
|
||||
"//ui/base",
|
||||
"//ui/base/x",
|
||||
"//ui/gfx/geometry",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
static_library("test_support") {
|
||||
testonly = true
|
||||
|
||||
@ -857,6 +880,7 @@ source_set("unit_tests") {
|
||||
":chromoting_host_services_client",
|
||||
":client_session_control",
|
||||
":common",
|
||||
":display_layout",
|
||||
":resources",
|
||||
":test_support",
|
||||
"//build:branding_buildflags",
|
||||
@ -891,8 +915,8 @@ source_set("unit_tests") {
|
||||
"//skia",
|
||||
"//testing/gmock",
|
||||
"//testing/gtest",
|
||||
"//ui/base:base",
|
||||
"//ui/events:events",
|
||||
"//ui/base",
|
||||
"//ui/events",
|
||||
]
|
||||
|
||||
# ChromotingHostServices currently only works on Linux and Windows.
|
||||
@ -927,7 +951,10 @@ source_set("unit_tests") {
|
||||
"x11_crtc_resizer_unittest.cc",
|
||||
"x11_display_util_unittest.cc",
|
||||
]
|
||||
deps += [ "//ui/gfx/x" ]
|
||||
deps += [
|
||||
":x11_display_utils",
|
||||
"//ui/gfx/x",
|
||||
]
|
||||
}
|
||||
|
||||
if (is_chromeos_ash) {
|
||||
|
@ -171,6 +171,7 @@ source_set("unit_tests") {
|
||||
deps = [
|
||||
"//remoting/host:common",
|
||||
"//remoting/host:test_support",
|
||||
"//remoting/host:x11_display_utils",
|
||||
"//remoting/host/it2me:common",
|
||||
"//remoting/host/native_messaging",
|
||||
"//remoting/host/security_key:unit_tests",
|
||||
|
@ -28,7 +28,6 @@ DEFAULT_XVFB_WHD = '1280x800x24'
|
||||
|
||||
# pylint: disable=useless-object-inheritance
|
||||
|
||||
|
||||
class _X11ProcessError(Exception):
|
||||
"""Exception raised when Xvfb or Xorg cannot start."""
|
||||
|
||||
@ -168,20 +167,29 @@ def run_executable(cmd,
|
||||
return test_env.run_executable(cmd, env, stdoutfile, cwd)
|
||||
|
||||
|
||||
def _make_xorg_config(whd):
|
||||
"""Generates an Xorg config file based on the specified WxHxD string and
|
||||
returns the file path. See:
|
||||
def _make_xorg_modeline(width, height, refresh):
|
||||
"""Generates a tuple of a modeline (list of parameters) and label based off a
|
||||
specified width, height and refresh rate.
|
||||
See: https://www.x.org/archive/X11R7.0/doc/html/chips4.html"""
|
||||
cvt_output = subprocess.check_output(
|
||||
['cvt', str(width), str(height),
|
||||
str(refresh)],
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True)
|
||||
re_matches = re.search('Modeline "(.*)"\s+(.*)', cvt_output, re.IGNORECASE)
|
||||
modeline_label = re_matches.group(1)
|
||||
modeline = re_matches.group(2)
|
||||
# Split the modeline string on spaces, and filter out empty element (cvt adds
|
||||
# double spaces between in some parts).
|
||||
return (modeline_label, list(filter(lambda a: a != '', modeline.split(' '))))
|
||||
|
||||
|
||||
def _make_xorg_config():
|
||||
"""Generates an Xorg config file and returns the file path. See:
|
||||
https://www.x.org/releases/current/doc/man/man5/xorg.conf.5.xhtml"""
|
||||
(width, height, depth) = whd.split('x')
|
||||
modeline = subprocess.check_output(['cvt', width, height, '60'],
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True)
|
||||
modeline_label = re.search('Modeline "(.*)"', modeline,
|
||||
re.IGNORECASE).group(1)
|
||||
config = f"""
|
||||
config = """
|
||||
Section "Monitor"
|
||||
Identifier "Monitor0"
|
||||
{modeline}
|
||||
EndSection
|
||||
Section "Device"
|
||||
Identifier "Device0"
|
||||
@ -190,14 +198,9 @@ Section "Device"
|
||||
VideoRam 256000
|
||||
EndSection
|
||||
Section "Screen"
|
||||
DefaultDepth {depth}
|
||||
Identifier "Screen0"
|
||||
Device "Device0"
|
||||
Monitor "Monitor0"
|
||||
SubSection "Display"
|
||||
Depth {depth}
|
||||
Modes "{modeline_label}"
|
||||
EndSubSection
|
||||
EndSection
|
||||
"""
|
||||
config_file = os.path.join(tempfile.gettempdir(), 'xorg.config')
|
||||
@ -206,6 +209,49 @@ EndSection
|
||||
return config_file
|
||||
|
||||
|
||||
def _setup_xrandr(env, default_whd):
|
||||
"""Configures xrandr dummy displays from xserver-xorg-video-dummy package."""
|
||||
(width, height, _) = default_whd.split('x')
|
||||
default_size = (int(width), int(height))
|
||||
xrandr_sizes = [(800, 600), (1024, 768), (1920, 1080), (1600, 1200),
|
||||
(3840, 2160)]
|
||||
if (default_size not in xrandr_sizes):
|
||||
xrandr_sizes.append(default_size)
|
||||
xrandr_sizes = sorted(xrandr_sizes)
|
||||
output_names = ['DUMMY0', 'DUMMY1', 'DUMMY2', 'DUMMY3', 'DUMMY4']
|
||||
refresh_rate = 60
|
||||
|
||||
# Calls xrandr with the provided argument array
|
||||
def call_xrandr(args):
|
||||
subprocess.check_call(['xrandr'] + args,
|
||||
env=env,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.STDOUT)
|
||||
|
||||
for width, height in xrandr_sizes:
|
||||
(modeline_label, modeline) = _make_xorg_modeline(width, height,
|
||||
refresh_rate)
|
||||
call_xrandr(['--newmode', modeline_label] + modeline)
|
||||
for output_name in output_names:
|
||||
call_xrandr(['--addmode', output_name, modeline_label])
|
||||
|
||||
(default_mode_label, _) = _make_xorg_modeline(*default_size, refresh_rate)
|
||||
|
||||
# Set the mode of all monitors to connect and activate them.
|
||||
for i in range(0, len(output_names)):
|
||||
args = ['--output', output_names[i], '--mode', default_mode_label]
|
||||
if (i > 0):
|
||||
args += ['--right-of', output_names[i - 1]]
|
||||
call_xrandr(args)
|
||||
|
||||
# Sets the primary monitor (DUMMY0) to the default size and marks the rest as
|
||||
# disabled.
|
||||
call_xrandr(["-s", "%dx%d" % default_size])
|
||||
|
||||
# Set the DPI to something realistic (as required by some desktops).
|
||||
call_xrandr(['--dpi', '96'])
|
||||
|
||||
|
||||
def _run_with_x11(cmd, env, stdoutfile, use_openbox, use_xcompmgr, use_xorg,
|
||||
xvfb_whd, cwd):
|
||||
"""Runs with an X11 server. Uses Xvfb by default and Xorg when use_xorg is
|
||||
@ -225,7 +271,7 @@ def _run_with_x11(cmd, env, stdoutfile, use_openbox, use_xcompmgr, use_xorg,
|
||||
|
||||
dbus_pid = None
|
||||
x11_binary = 'Xorg' if use_xorg else 'Xvfb'
|
||||
xorg_config_file = _make_xorg_config(xvfb_whd) if use_xorg else None
|
||||
xorg_config_file = _make_xorg_config() if use_xorg else None
|
||||
try:
|
||||
signal.signal(signal.SIGTERM, raise_x11_error)
|
||||
signal.signal(signal.SIGINT, raise_x11_error)
|
||||
@ -317,6 +363,9 @@ def _run_with_x11(cmd, env, stdoutfile, use_openbox, use_xcompmgr, use_xorg,
|
||||
stderr=subprocess.STDOUT,
|
||||
env=env)
|
||||
|
||||
if use_xorg:
|
||||
_setup_xrandr(env, xvfb_whd)
|
||||
|
||||
return test_env.run_executable(cmd, env, stdoutfile, cwd)
|
||||
except OSError as e:
|
||||
print('Failed to start %s or Openbox: %s\n' % (x11_binary, str(e)),
|
||||
|
@ -253,6 +253,17 @@ static_library("test_support") {
|
||||
]
|
||||
}
|
||||
|
||||
if (is_linux && ozone_platform_x11) {
|
||||
sources += [
|
||||
"linux/test/virtual_display_util_linux.cc",
|
||||
"linux/test/virtual_display_util_linux.h",
|
||||
]
|
||||
deps += [
|
||||
"//remoting/host:display_layout",
|
||||
"//remoting/host:x11_display_utils",
|
||||
]
|
||||
}
|
||||
|
||||
if (is_chromeos) {
|
||||
sources += [ "test/display_test_util.cc" ]
|
||||
}
|
||||
@ -284,7 +295,7 @@ static_library("test_support") {
|
||||
public_deps += [ "//ui/display:managed_display_info" ]
|
||||
}
|
||||
|
||||
if (!is_mac && !is_win) {
|
||||
if (!is_mac && !is_win && !(is_linux && ozone_platform_x11)) {
|
||||
sources += [
|
||||
# Virtual display util stub for unimplemented platforms.
|
||||
"test/virtual_display_util_stub.cc",
|
||||
@ -376,25 +387,3 @@ test("display_unittests") {
|
||||
deps += [ "//mojo/core/test:run_all_unittests" ]
|
||||
}
|
||||
}
|
||||
|
||||
# This target is added as a dependency of browser interactive_ui_tests. It must
|
||||
# be source_set, otherwise the linker will drop the tests as dead code.
|
||||
source_set("display_interactive_ui_tests") {
|
||||
testonly = true
|
||||
if (is_mac) {
|
||||
sources = [ "mac/test/virtual_display_util_mac_interactive_uitest.mm" ]
|
||||
}
|
||||
|
||||
if (is_win) {
|
||||
sources = [ "win/test/virtual_display_util_win_interactive_uitest.cc" ]
|
||||
}
|
||||
|
||||
if (is_win || is_mac) {
|
||||
deps = [
|
||||
":display",
|
||||
":test_support",
|
||||
"//base/test:test_support",
|
||||
"//testing/gtest",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
5
ui/display/linux/test/DEPS
Normal file
5
ui/display/linux/test/DEPS
Normal file
@ -0,0 +1,5 @@
|
||||
include_rules = [
|
||||
"+ui/aura/screen_ozone.h",
|
||||
"+remoting/host/x11_desktop_resizer.h",
|
||||
"+remoting/host/desktop_geometry.h"
|
||||
]
|
222
ui/display/linux/test/virtual_display_util_linux.cc
Normal file
222
ui/display/linux/test/virtual_display_util_linux.cc
Normal file
@ -0,0 +1,222 @@
|
||||
// Copyright 2024 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "ui/display/linux/test/virtual_display_util_linux.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "base/environment.h"
|
||||
#include "base/nix/xdg_util.h"
|
||||
#include "remoting/host/desktop_geometry.h"
|
||||
#include "remoting/host/x11_desktop_resizer.h"
|
||||
#include "ui/display/display.h"
|
||||
#include "ui/display/display_list.h"
|
||||
#include "ui/display/screen.h"
|
||||
#include "ui/display/types/display_constants.h"
|
||||
#include "ui/gfx/geometry/rect.h"
|
||||
#include "ui/gfx/geometry/vector2d.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Appends a new screen with `resolution` to the specified desktop
|
||||
// `layout`. Arranges horizontally left to right.
|
||||
void AppendScreen(remoting::DesktopLayoutSet& layout,
|
||||
const remoting::DesktopResolution& resolution) {
|
||||
// Find the rightmost screen layout.
|
||||
const remoting::DesktopLayout* rightmost_layout = nullptr;
|
||||
for (const auto& screen : layout.layouts) {
|
||||
if (rightmost_layout == nullptr ||
|
||||
screen.rect().right() > rightmost_layout->rect().right()) {
|
||||
rightmost_layout = &screen;
|
||||
}
|
||||
}
|
||||
layout.layouts.emplace_back(
|
||||
std::nullopt,
|
||||
gfx::Rect(rightmost_layout->rect().right() + 1,
|
||||
rightmost_layout->position_y(), resolution.dimensions().width(),
|
||||
resolution.dimensions().height()),
|
||||
resolution.dpi());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace display::test {
|
||||
|
||||
struct DisplayParams {
|
||||
explicit DisplayParams(remoting::DesktopResolution resolution)
|
||||
: resolution(resolution) {}
|
||||
remoting::DesktopResolution resolution;
|
||||
};
|
||||
|
||||
VirtualDisplayUtilLinux::VirtualDisplayUtilLinux(Screen* screen)
|
||||
: screen_(screen),
|
||||
desktop_resizer_(std::make_unique<remoting::X11DesktopResizer>()),
|
||||
initial_layout_(desktop_resizer_->GetLayout()),
|
||||
current_layout_(initial_layout_) {
|
||||
CHECK(screen_);
|
||||
screen_->AddObserver(this);
|
||||
}
|
||||
VirtualDisplayUtilLinux::~VirtualDisplayUtilLinux() {
|
||||
ResetDisplays();
|
||||
screen_->RemoveObserver(this);
|
||||
}
|
||||
|
||||
// static
|
||||
bool VirtualDisplayUtilLinux::IsAPIAvailable() {
|
||||
// Wayland is currently unimplemented. Note that this detection uses
|
||||
// XDG_SESSION_TYPE environment variable. When running in a pure SSH / virtual
|
||||
// X server, it may be necessary to manually set XDG_SESSION_TYPE=x11 in the
|
||||
// command.
|
||||
static base::nix::SessionType session_type =
|
||||
base::nix::GetSessionType(*base::Environment::Create());
|
||||
return session_type == base::nix::SessionType::kX11;
|
||||
}
|
||||
|
||||
int64_t VirtualDisplayUtilLinux::AddDisplay(
|
||||
uint8_t id,
|
||||
const DisplayParams& display_params) {
|
||||
if (requested_ids_to_display_ids_.contains(id) ||
|
||||
std::find(requested_ids_.begin(), requested_ids_.end(), id) !=
|
||||
requested_ids_.end()) {
|
||||
LOG(ERROR) << "Virtual display with id " << id
|
||||
<< " already exists or requested.";
|
||||
return kInvalidDisplayId;
|
||||
}
|
||||
if (current_layout_.layouts.size() - initial_layout_.layouts.size() >
|
||||
kMaxDisplays) {
|
||||
LOG(ERROR) << "Cannot exceed " << kMaxDisplays << " virtual displays.";
|
||||
return kInvalidDisplayId;
|
||||
}
|
||||
CHECK(!current_layout_.layouts.empty());
|
||||
last_requested_layout_ = current_layout_;
|
||||
AppendScreen(last_requested_layout_, display_params.resolution);
|
||||
requested_ids_.push_back(id);
|
||||
desktop_resizer_->SetVideoLayout(last_requested_layout_);
|
||||
detected_added_display_ids_.clear();
|
||||
StartWaiting();
|
||||
CHECK_EQ(detected_added_display_ids_.size(), 1u)
|
||||
<< "Did not detect exactly one new display.";
|
||||
// Reconcile the added resizer display ID to the detected display::Display id.
|
||||
int64_t new_display_id = detected_added_display_ids_.front();
|
||||
detected_added_display_ids_.pop_front();
|
||||
remoting::DesktopLayoutSet prev_layout = current_layout_;
|
||||
current_layout_ = desktop_resizer_->GetLayout();
|
||||
for (const auto& layout : current_layout_.layouts) {
|
||||
auto was_added =
|
||||
std::find_if(prev_layout.layouts.begin(), prev_layout.layouts.end(),
|
||||
[&](const remoting::DesktopLayout& prev) {
|
||||
return prev.rect() == layout.rect();
|
||||
});
|
||||
if (was_added == prev_layout.layouts.end()) {
|
||||
display_id_to_resizer_id_[new_display_id] = *layout.screen_id();
|
||||
}
|
||||
}
|
||||
return new_display_id;
|
||||
}
|
||||
void VirtualDisplayUtilLinux::RemoveDisplay(int64_t display_id) {
|
||||
if (!display_id_to_resizer_id_.contains(display_id)) {
|
||||
LOG(ERROR) << "Invalid display_id. Missing mapping for " << display_id
|
||||
<< " to resizer ID.";
|
||||
return;
|
||||
}
|
||||
last_requested_layout_ = current_layout_;
|
||||
std::erase_if(last_requested_layout_.layouts,
|
||||
[&](const remoting::DesktopLayout& layout) {
|
||||
return layout.screen_id() ==
|
||||
display_id_to_resizer_id_[display_id];
|
||||
});
|
||||
desktop_resizer_->SetVideoLayout(last_requested_layout_);
|
||||
StartWaiting();
|
||||
}
|
||||
void VirtualDisplayUtilLinux::ResetDisplays() {
|
||||
last_requested_layout_ = initial_layout_;
|
||||
desktop_resizer_->SetVideoLayout(last_requested_layout_);
|
||||
StartWaiting();
|
||||
current_layout_ = desktop_resizer_->GetLayout();
|
||||
}
|
||||
|
||||
void VirtualDisplayUtilLinux::OnDisplayAdded(
|
||||
const display::Display& new_display) {
|
||||
// TODO(crbug.com/40257169): Support adding multiple displays at a time, or
|
||||
// ignoring external display configuration changes.
|
||||
CHECK_EQ(requested_ids_.size(), 1u)
|
||||
<< "An extra display was detected that was either not requested by this "
|
||||
"controller, or multiple displays were requested concurrently. This "
|
||||
"is not supported.";
|
||||
detected_added_display_ids_.push_back(new_display.id());
|
||||
uint8_t requested_id = requested_ids_.front();
|
||||
requested_ids_.pop_front();
|
||||
requested_ids_to_display_ids_[requested_id] = new_display.id();
|
||||
OnDisplayAddedOrRemoved(new_display.id());
|
||||
}
|
||||
|
||||
void VirtualDisplayUtilLinux::OnDisplaysRemoved(
|
||||
const display::Displays& removed_displays) {
|
||||
for (const auto& display : removed_displays) {
|
||||
base::EraseIf(requested_ids_to_display_ids_,
|
||||
[&](std::pair<uint8_t, int64_t>& pair) {
|
||||
return pair.second == display.id();
|
||||
});
|
||||
base::EraseIf(display_id_to_resizer_id_,
|
||||
[&](std::pair<DisplayId, ResizerDisplayId>& pair) {
|
||||
return pair.first == display.id();
|
||||
});
|
||||
base::EraseIf(detected_added_display_ids_,
|
||||
[&](DisplayId& id) { return id == display.id(); });
|
||||
OnDisplayAddedOrRemoved(display.id());
|
||||
}
|
||||
}
|
||||
|
||||
void VirtualDisplayUtilLinux::OnDisplayAddedOrRemoved(int64_t id) {
|
||||
if (!RequestedLayoutIsSet()) {
|
||||
return;
|
||||
}
|
||||
StopWaiting();
|
||||
}
|
||||
|
||||
bool VirtualDisplayUtilLinux::RequestedLayoutIsSet() {
|
||||
// Checks that the number of virtual displays (delta of last requested layout
|
||||
// minus initial layout) is equal to the number of requested displays.
|
||||
return last_requested_layout_.layouts.size() -
|
||||
initial_layout_.layouts.size() ==
|
||||
requested_ids_to_display_ids_.size();
|
||||
}
|
||||
|
||||
void VirtualDisplayUtilLinux::StartWaiting() {
|
||||
CHECK(!run_loop_);
|
||||
if (RequestedLayoutIsSet()) {
|
||||
return;
|
||||
}
|
||||
run_loop_ = std::make_unique<base::RunLoop>();
|
||||
run_loop_->Run();
|
||||
run_loop_.reset();
|
||||
}
|
||||
void VirtualDisplayUtilLinux::StopWaiting() {
|
||||
CHECK(run_loop_);
|
||||
run_loop_->Quit();
|
||||
}
|
||||
|
||||
// static
|
||||
const DisplayParams VirtualDisplayUtilLinux::k1920x1080 = DisplayParams(
|
||||
remoting::DesktopResolution(gfx::Size(1920, 1080), gfx::Vector2d(96, 96)));
|
||||
const DisplayParams VirtualDisplayUtilLinux::k1024x768 = DisplayParams(
|
||||
remoting::DesktopResolution(gfx::Size(1024, 768), gfx::Vector2d(96, 96)));
|
||||
|
||||
// VirtualDisplayUtil definitions:
|
||||
const DisplayParams VirtualDisplayUtil::k1920x1080 =
|
||||
VirtualDisplayUtilLinux::k1920x1080;
|
||||
const DisplayParams VirtualDisplayUtil::k1024x768 =
|
||||
VirtualDisplayUtilLinux::k1024x768;
|
||||
|
||||
// static
|
||||
std::unique_ptr<VirtualDisplayUtil> VirtualDisplayUtil::TryCreate(
|
||||
Screen* screen) {
|
||||
if (!VirtualDisplayUtilLinux::IsAPIAvailable()) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<VirtualDisplayUtilLinux>(screen);
|
||||
}
|
||||
|
||||
} // namespace display::test
|
93
ui/display/linux/test/virtual_display_util_linux.h
Normal file
93
ui/display/linux/test/virtual_display_util_linux.h
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright 2024 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef UI_DISPLAY_LINUX_TEST_VIRTUAL_DISPLAY_UTIL_LINUX_H_
|
||||
#define UI_DISPLAY_LINUX_TEST_VIRTUAL_DISPLAY_UTIL_LINUX_H_
|
||||
|
||||
#include "base/containers/circular_deque.h"
|
||||
#include "base/containers/flat_map.h"
|
||||
#include "base/run_loop.h"
|
||||
#include "remoting/host/x11_desktop_resizer.h"
|
||||
#include "ui/display/display_observer.h"
|
||||
#include "ui/display/test/virtual_display_util.h"
|
||||
|
||||
namespace display {
|
||||
class Display;
|
||||
class Screen;
|
||||
|
||||
namespace test {
|
||||
|
||||
// Linux implementation of VirtualDisplayUtil. This uses remote desktop code
|
||||
// (remoting::X11DesktopResizer) to do the X11/XRandR heavy lifting.
|
||||
class VirtualDisplayUtilLinux : public display::DisplayObserver,
|
||||
public VirtualDisplayUtil {
|
||||
public:
|
||||
explicit VirtualDisplayUtilLinux(Screen* screen);
|
||||
~VirtualDisplayUtilLinux() override;
|
||||
// Maximum number of displays that can be added through AddDisplay().
|
||||
// It should be one less than the number of dummy monitors configured in
|
||||
// //testing/xvfb.py
|
||||
static constexpr int kMaxDisplays = 4;
|
||||
// Check whether the related drivers are available on the current system.
|
||||
static bool IsAPIAvailable();
|
||||
|
||||
// VirtualDisplayUtil overrides:
|
||||
int64_t AddDisplay(uint8_t id, const DisplayParams& display_params) override;
|
||||
void RemoveDisplay(int64_t display_id) override;
|
||||
void ResetDisplays() override;
|
||||
|
||||
// These should be a subset of the resolutions configured in //testing/xvfb.py
|
||||
static const DisplayParams k800x600;
|
||||
static const DisplayParams k1024x768;
|
||||
static const DisplayParams k1280x800;
|
||||
static const DisplayParams k1920x1080;
|
||||
static const DisplayParams k1600x1200;
|
||||
static const DisplayParams k3840x2160;
|
||||
|
||||
private:
|
||||
// display::DisplayObserver:
|
||||
void OnDisplayAdded(const display::Display& new_display) override;
|
||||
void OnDisplaysRemoved(const display::Displays& removed_displays) override;
|
||||
|
||||
void OnDisplayAddedOrRemoved(int64_t id);
|
||||
bool RequestedLayoutIsSet();
|
||||
// Start waiting for the detected displays to match `current_config_`.
|
||||
void StartWaiting();
|
||||
void StopWaiting();
|
||||
|
||||
std::unique_ptr<base::RunLoop> run_loop_;
|
||||
raw_ptr<Screen> screen_;
|
||||
std::unique_ptr<remoting::X11DesktopResizer> desktop_resizer_;
|
||||
// Initial layout when this class was instantiated that should be restored.
|
||||
remoting::DesktopLayoutSet initial_layout_;
|
||||
// Current layout calculated by `desktop_resizer_` after an operation.
|
||||
remoting::DesktopLayoutSet current_layout_;
|
||||
// Last layout request sent to `desktop_resizer_`.
|
||||
remoting::DesktopLayoutSet last_requested_layout_;
|
||||
|
||||
// There are lots of IDS to track here:
|
||||
// 1. The user-requested ID set in AddDisplay().
|
||||
// 2. The resizer (xrandr) display ID
|
||||
// 3. The display ID detected by the display::Screen implementation.
|
||||
using RequestedId = uint8_t;
|
||||
using ResizerDisplayId = int64_t;
|
||||
using DisplayId = int64_t;
|
||||
|
||||
// Queue of displays added via OnDisplayAdded. Removed as they are reconciled
|
||||
// and moved to `display_id_to_resizer_id_`.
|
||||
base::circular_deque<DisplayId> detected_added_display_ids_;
|
||||
base::flat_map<DisplayId, ResizerDisplayId> display_id_to_resizer_id_;
|
||||
|
||||
// Tracks display IDs requested in AddDisplay(). The IDs don't do anything in
|
||||
// this implementation, but they are tracked to prevent the user from
|
||||
// specifying the same ID twice without deleting it first (to match other
|
||||
// platform behavior);
|
||||
base::circular_deque<RequestedId> requested_ids_;
|
||||
base::flat_map<RequestedId, DisplayId> requested_ids_to_display_ids_;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace display
|
||||
|
||||
#endif // UI_DISPLAY_LINUX_TEST_VIRTUAL_DISPLAY_UTIL_LINUX_H_
|
@ -0,0 +1,101 @@
|
||||
// Copyright 2024 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/test/task_environment.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "ui/aura/screen_ozone.h"
|
||||
#include "ui/display/linux/test/virtual_display_util_linux.h"
|
||||
#include "ui/display/screen.h"
|
||||
#include "ui/display/types/display_constants.h"
|
||||
|
||||
class VirtualDisplayUtilLinuxInteractiveUitest : public testing::Test {
|
||||
public:
|
||||
VirtualDisplayUtilLinuxInteractiveUitest(
|
||||
const VirtualDisplayUtilLinuxInteractiveUitest&) = delete;
|
||||
VirtualDisplayUtilLinuxInteractiveUitest& operator=(
|
||||
const VirtualDisplayUtilLinuxInteractiveUitest&) = delete;
|
||||
|
||||
protected:
|
||||
VirtualDisplayUtilLinuxInteractiveUitest() = default;
|
||||
~VirtualDisplayUtilLinuxInteractiveUitest() override = default;
|
||||
void SetUp() override {
|
||||
if (!display::test::VirtualDisplayUtilLinux::IsAPIAvailable()) {
|
||||
GTEST_SKIP() << "Host does not support virtual displays.";
|
||||
}
|
||||
CHECK(!display::Screen::HasScreen());
|
||||
screen_ = std::make_unique<aura::ScreenOzone>();
|
||||
virtual_display_util_ =
|
||||
std::make_unique<display::test::VirtualDisplayUtilLinux>(screen());
|
||||
testing::Test::SetUp();
|
||||
}
|
||||
|
||||
display::Screen* screen() { return screen_.get(); }
|
||||
base::test::TaskEnvironment task_environment_{
|
||||
base::test::TaskEnvironment::MainThreadType::UI};
|
||||
std::unique_ptr<aura::ScreenOzone> screen_;
|
||||
std::unique_ptr<display::test::VirtualDisplayUtilLinux> virtual_display_util_;
|
||||
};
|
||||
|
||||
TEST_F(VirtualDisplayUtilLinuxInteractiveUitest, IsAPIAvailable) {
|
||||
EXPECT_TRUE(virtual_display_util_->IsAPIAvailable());
|
||||
}
|
||||
|
||||
TEST_F(VirtualDisplayUtilLinuxInteractiveUitest, AddDisplay) {
|
||||
int initial_display_count = screen()->GetNumDisplays();
|
||||
int64_t display_id = virtual_display_util_->AddDisplay(
|
||||
1, display::test::VirtualDisplayUtilLinux::k1920x1080);
|
||||
EXPECT_NE(display_id, display::kInvalidDisplayId);
|
||||
EXPECT_EQ(screen()->GetNumDisplays(), initial_display_count + 1);
|
||||
display::Display d;
|
||||
EXPECT_TRUE(screen()->GetDisplayWithDisplayId(display_id, &d));
|
||||
EXPECT_EQ(d.size(), gfx::Size(1920, 1080));
|
||||
|
||||
// Expect failure when adding a duplicate index.
|
||||
EXPECT_EQ(virtual_display_util_->AddDisplay(
|
||||
1, display::test::VirtualDisplayUtilLinux::k1920x1080),
|
||||
display::kInvalidDisplayId);
|
||||
|
||||
virtual_display_util_->ResetDisplays();
|
||||
EXPECT_FALSE(screen()->GetDisplayWithDisplayId(display_id, &d));
|
||||
EXPECT_EQ(screen()->GetNumDisplays(), initial_display_count);
|
||||
}
|
||||
|
||||
TEST_F(VirtualDisplayUtilLinuxInteractiveUitest, AddRemove) {
|
||||
int64_t display_id[3];
|
||||
int initial_display_count = screen()->GetNumDisplays();
|
||||
display_id[0] = virtual_display_util_->AddDisplay(
|
||||
0, display::test::VirtualDisplayUtilLinux::k1920x1080);
|
||||
EXPECT_NE(display_id[0], display::kInvalidDisplayId);
|
||||
display::Display d;
|
||||
EXPECT_TRUE(screen()->GetDisplayWithDisplayId(display_id[0], &d));
|
||||
|
||||
display_id[1] = virtual_display_util_->AddDisplay(
|
||||
1, display::test::VirtualDisplayUtilLinux::k1024x768);
|
||||
EXPECT_NE(display_id[1], display::kInvalidDisplayId);
|
||||
EXPECT_TRUE(screen()->GetDisplayWithDisplayId(display_id[1], &d));
|
||||
|
||||
display_id[2] = virtual_display_util_->AddDisplay(
|
||||
2, display::test::VirtualDisplayUtilLinux::k1920x1080);
|
||||
EXPECT_NE(display_id[2], display::kInvalidDisplayId);
|
||||
EXPECT_TRUE(screen()->GetDisplayWithDisplayId(display_id[2], &d));
|
||||
|
||||
EXPECT_EQ(screen()->GetNumDisplays(), initial_display_count + 3);
|
||||
virtual_display_util_->RemoveDisplay(display_id[1]);
|
||||
EXPECT_EQ(screen()->GetNumDisplays(), initial_display_count + 2);
|
||||
// Only virtual display 2 should no longer exist.
|
||||
EXPECT_TRUE(screen()->GetDisplayWithDisplayId(display_id[0], &d));
|
||||
EXPECT_EQ(d.size(), gfx::Size(1920, 1080));
|
||||
EXPECT_FALSE(screen()->GetDisplayWithDisplayId(display_id[1], &d));
|
||||
EXPECT_TRUE(screen()->GetDisplayWithDisplayId(display_id[2], &d));
|
||||
EXPECT_EQ(d.size(), gfx::Size(1920, 1080));
|
||||
|
||||
virtual_display_util_->ResetDisplays();
|
||||
EXPECT_FALSE(screen()->GetDisplayWithDisplayId(display_id[0], &d));
|
||||
EXPECT_FALSE(screen()->GetDisplayWithDisplayId(display_id[1], &d));
|
||||
EXPECT_FALSE(screen()->GetDisplayWithDisplayId(display_id[2], &d));
|
||||
EXPECT_EQ(screen()->GetNumDisplays(), initial_display_count);
|
||||
}
|
Reference in New Issue
Block a user