diff --git a/Cargo.toml b/Cargo.toml index 55dcc540570..92d52050901 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -140,7 +140,7 @@ softbuffer = { version = "0.3.3", default-features = false } strum = { version = "0.26.1", default-features = false, features = ["derive"] } toml_edit = { version = "0.22.7" } cfg_aliases = { version = "0.2.0" } - +slab = { version = "0.4.3", default-features = false } raw-window-handle-06 = { package = "raw-window-handle", version = "0.6", features = ["alloc"] } [profile.release] diff --git a/api/cpp/CMakeLists.txt b/api/cpp/CMakeLists.txt index a2569876277..38dad122cd4 100644 --- a/api/cpp/CMakeLists.txt +++ b/api/cpp/CMakeLists.txt @@ -105,6 +105,7 @@ define_cargo_dependent_feature(backend-linuxkms-noseat "Enable support for the b define_cargo_dependent_feature(gettext "Enable support of translations using gettext" OFF "NOT SLINT_FEATURE_FREESTANDING") define_cargo_dependent_feature(accessibility "Enable integration with operating system provided accessibility APIs" ON "NOT SLINT_FEATURE_FREESTANDING") define_cargo_dependent_feature(testing "Enable support for testing API (experimental)" ON "NOT SLINT_FEATURE_FREESTANDING") +define_cargo_dependent_feature(system-testing "Enable system testing support" OFF "NOT SLINT_FEATURE_FREESTANDING") define_cargo_feature(experimental "Enable experimental features. (No backward compatibility guarantees)" OFF) if (SLINT_BUILD_RUNTIME) diff --git a/api/cpp/Cargo.toml b/api/cpp/Cargo.toml index 70dc1fab0da..e8f38a04a28 100644 --- a/api/cpp/Cargo.toml +++ b/api/cpp/Cargo.toml @@ -42,6 +42,7 @@ renderer-skia-vulkan = ["i-slint-backend-selector/renderer-skia-vulkan", "render renderer-software = ["i-slint-backend-selector/renderer-software"] gettext = ["i-slint-core/gettext-rs"] accessibility = ["i-slint-backend-selector/accessibility"] +system-testing = ["i-slint-backend-selector/system-testing"] std = ["image", "i-slint-core/default", "i-slint-backend-selector"] freestanding = ["i-slint-core/libm", "i-slint-core/unsafe-single-threaded"] diff --git a/api/rs/slint/Cargo.toml b/api/rs/slint/Cargo.toml index 3328bda0123..a0141bbca18 100644 --- a/api/rs/slint/Cargo.toml +++ b/api/rs/slint/Cargo.toml @@ -84,6 +84,9 @@ accessibility = ["i-slint-backend-selector/accessibility"] ## [HasDisplayHandle](raw_window_handle_06::HasDisplayHandle) implementation. raw-window-handle-06 = ["dep:raw-window-handle-06", "i-slint-backend-selector/raw-window-handle-06"] +## Enable support for system testing via out of process orchestration. +system-testing = ["i-slint-backend-selector/system-testing"] + #! ### Backends #! Slint needs a backend that will act as liaison between Slint and the OS. diff --git a/internal/backends/selector/Cargo.toml b/internal/backends/selector/Cargo.toml index 32250e24c15..2646f5f2dc0 100644 --- a/internal/backends/selector/Cargo.toml +++ b/internal/backends/selector/Cargo.toml @@ -35,12 +35,15 @@ accessibility = ["i-slint-backend-winit?/accessibility"] raw-window-handle-06 = ["i-slint-core/raw-window-handle-06", "i-slint-backend-winit?/raw-window-handle-06"] +system-testing = ["i-slint-backend-testing/system-testing"] + # note that default enable the i-slint-backend-qt, but not its enable feature default = ["i-slint-backend-qt", "backend-winit"] [dependencies] cfg-if = "1" i-slint-core = { workspace = true } +i-slint-backend-testing = { workspace = true, optional = true } [target.'cfg(not(target_os = "android"))'.dependencies] i-slint-backend-winit = { workspace = true, features = ["default"], optional = true } diff --git a/internal/backends/selector/lib.rs b/internal/backends/selector/lib.rs index 9e1f78bfcf2..be49b126acb 100644 --- a/internal/backends/selector/lib.rs +++ b/internal/backends/selector/lib.rs @@ -133,5 +133,20 @@ cfg_if::cfg_if! { pub fn with_platform( f: impl FnOnce(&dyn Platform) -> Result, ) -> Result { - i_slint_core::with_platform(create_backend, f) + let mut platform_created = false; + let result = i_slint_core::with_platform( + || { + let backend = create_backend(); + platform_created = backend.is_ok(); + backend + }, + f, + ); + + #[cfg(feature = "system-testing")] + if result.is_ok() && platform_created { + i_slint_backend_testing::systest::init(); + } + + result } diff --git a/internal/backends/testing/Cargo.toml b/internal/backends/testing/Cargo.toml index 29cb89ef90c..ffd623675bf 100644 --- a/internal/backends/testing/Cargo.toml +++ b/internal/backends/testing/Cargo.toml @@ -12,6 +12,7 @@ repository.workspace = true rust-version.workspace = true version.workspace = true publish = false +build = "build.rs" [lib] path = "lib.rs" @@ -21,10 +22,17 @@ path = "lib.rs" internal = [] # ffi for C++ bindings ffi = [] +system-testing = ["quick-protobuf", "pb-rs", "slab", "smol"] [dependencies] i-slint-core = { workspace = true } vtable = { workspace = true } +quick-protobuf = { version = "0.8.1", optional = true } +slab = { workspace = true, optional = true } +smol = { version = "2.0.0", optional = true } + +[build-dependencies] +pb-rs = { version = "0.10.0", optional = true } [dev-dependencies] slint = { workspace = true, default-features = false, features = ["std", "compat-1-2"] } diff --git a/internal/backends/testing/build.rs b/internal/backends/testing/build.rs new file mode 100644 index 00000000000..dfb04b807fe --- /dev/null +++ b/internal/backends/testing/build.rs @@ -0,0 +1,15 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.2 OR LicenseRef-Slint-commercial + +fn main() { + #[cfg(feature = "system-testing")] + { + let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()); + let proto_file = std::path::PathBuf::from(::std::env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("slint_systest.proto"); + let config_builder = pb_rs::ConfigBuilder::new(&[proto_file], None, Some(&out_dir), &[]) + .unwrap() + .headers(false); + pb_rs::types::FileDescriptor::run(&config_builder.build()).unwrap(); + } +} diff --git a/internal/backends/testing/lib.rs b/internal/backends/testing/lib.rs index 8591419bec1..b7d22f0b3fd 100644 --- a/internal/backends/testing/lib.rs +++ b/internal/backends/testing/lib.rs @@ -15,6 +15,8 @@ mod testing_backend; pub use testing_backend::*; #[cfg(feature = "ffi")] mod ffi; +#[cfg(feature = "system-testing")] +pub mod systest; /// Initialize the testing backend without support for event loop. /// This means that each test thread can use its own backend, but global functions that needs diff --git a/internal/backends/testing/slint_systest.proto b/internal/backends/testing/slint_systest.proto new file mode 100644 index 00000000000..279b2cddaae --- /dev/null +++ b/internal/backends/testing/slint_systest.proto @@ -0,0 +1,27 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.2 OR LicenseRef-Slint-commercial + +syntax = "proto3"; + +message RequestWindowListMessage { +} + +message RequestToAUT { + oneof msg { + RequestWindowListMessage request_window_list = 1; + } +} + +message WindowDetails { + // Can add information such as size +} + +message WindowListResponse { + map windows = 1; +} + +message AUTResponse { + oneof msg { + WindowListResponse window_list = 1; + } +} diff --git a/internal/backends/testing/systest.rs b/internal/backends/testing/systest.rs new file mode 100644 index 00000000000..0794c6b9ade --- /dev/null +++ b/internal/backends/testing/systest.rs @@ -0,0 +1,62 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.2 OR LicenseRef-Slint-commercial + +use i_slint_core::api::invoke_from_event_loop; +use i_slint_core::api::EventLoopError; +use i_slint_core::platform::PlatformError; +use i_slint_core::platform::WindowAdapter; +use std::cell::OnceCell; +use std::cell::RefCell; +use std::rc::Rc; + +mod proto { + include!(concat!(env!("OUT_DIR"), "/slint_systest.rs")); +} + +struct TestingClient { + windows: RefCell>>, +} + +impl TestingClient { + fn new() -> Self { + todo!() + } + + fn window_shown(self: Rc, adapter: Rc) { + let id = self.windows.borrow_mut().insert(adapter); + + let _ = invoke_from_event_loop(move || eprintln!("{id}")); + } +} + +pub fn init() -> Result<(), EventLoopError> { + let Ok(server_addr) = std::env::var("SLINT_TEST_SERVER") else { + return Ok(()); + }; + eprintln!("Attempting to connect to testing server at {server_addr}"); + + i_slint_core::future::spawn_local(async move { + let stream = match smol::net::TcpStream::connect(server_addr.clone()).await { + Ok(stream) => stream, + Err(err) => { + eprintln!("Error connecting to Slint test server at {server_addr}: {}", err); + return; + } + }; + eprintln!("Connected to test server"); + + //loop { + + //} + })?; + /* + let Some(client) = TestingClient::connect() else { + return Ok(()); + }; + + i_slint_core::context::set_window_shown_hook(Some(Box::new(move |adapter| { + client.window_shown(adapter); + })))?; + */ + Ok(()) +} diff --git a/internal/core/Cargo.toml b/internal/core/Cargo.toml index f4e13644c1b..a20dfc902a1 100644 --- a/internal/core/Cargo.toml +++ b/internal/core/Cargo.toml @@ -73,7 +73,7 @@ pin-weak = { version = "1.1", default-features = false } rgb = "0.8.27" scoped-tls-hkt = { version = "0.1", optional = true } scopeguard = { version = "1.1.0", default-features = false } -slab = { version = "0.4.3", default-features = false } +slab = { workspace = true } static_assertions = "1.1" strum = { workspace = true } unicode-segmentation = "1.8.0" diff --git a/internal/core/context.rs b/internal/core/context.rs index f192fcd9b14..9d900b3a01b 100644 --- a/internal/core/context.rs +++ b/internal/core/context.rs @@ -21,6 +21,8 @@ pub(crate) struct SlintContextInner { /// This property is read by all translations, and marked dirty when the language change /// so that every translated string gets re-translated pub(crate) translations_dirty: core::pin::Pin>>, + pub(crate) window_shown_hook: + core::cell::RefCell)>>>, } /// This context is meant to hold the state and the backend. @@ -36,6 +38,7 @@ impl SlintContext { platform, window_count: 0.into(), translations_dirty: Box::pin(Property::new_named((), "SlintContext::translations")), + window_shown_hook: Default::default(), })) } @@ -74,3 +77,12 @@ pub fn with_platform( } }) } + +pub fn set_window_shown_hook( + hook: Option)>>, +) -> Result)>>, PlatformError> { + GLOBAL_CONTEXT.with(|p| match p.get() { + Some(ctx) => Ok(ctx.0.window_shown_hook.replace(hook)), + None => Err(PlatformError::NoPlatform), + }) +} diff --git a/internal/core/window.rs b/internal/core/window.rs index 09f9e4f6bfb..848b44bf6aa 100644 --- a/internal/core/window.rs +++ b/internal/core/window.rs @@ -903,6 +903,9 @@ impl WindowInner { let size = self.window_adapter().size(); self.set_window_item_geometry(size.to_logical(self.scale_factor()).to_euclid()); self.window_adapter().renderer().resize(size).unwrap(); + if let Some(hook) = self.ctx.0.window_shown_hook.borrow_mut().as_mut() { + hook(self.window_adapter()); + } Ok(()) } diff --git a/internal/interpreter/Cargo.toml b/internal/interpreter/Cargo.toml index 9b4b43c1765..93abf6add40 100644 --- a/internal/interpreter/Cargo.toml +++ b/internal/interpreter/Cargo.toml @@ -113,6 +113,9 @@ accessibility = ["i-slint-backend-selector/accessibility"] ## [HasDisplayHandle](raw_window_handle_06::HasDisplayHandle) implementation. raw-window-handle-06 = ["dep:raw-window-handle-06", "i-slint-backend-selector/raw-window-handle-06"] +## Enable support for system testing via out of process orchestration. +system-testing = ["i-slint-backend-selector/system-testing"] + ## Features used internally by Slint tooling that are not stable and come without ## any stability guarantees whatsoever. internal = []