Compare commits
7 commits
portallist
...
nigig-make
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d82c6dc93 | |||
| 18e110d432 | |||
| ec8ac35d88 | |||
| a9af660e45 | |||
| ce899827a9 | |||
| ec2c0aaf15 | |||
| 7de0f215d9 |
15 changed files with 876 additions and 65 deletions
|
|
@ -6,15 +6,8 @@ app_main!(App);
|
|||
|
||||
script_mod! {
|
||||
use mod.prelude.widgets.*
|
||||
let state = {
|
||||
counter: 0
|
||||
}
|
||||
mod.state = state
|
||||
startup() do #(App::script_component(vm)){
|
||||
ui: Root{
|
||||
on_startup:||{ // right now render isnt called automatically yet
|
||||
ui.main_view.render()
|
||||
}
|
||||
main_window := Window{
|
||||
window.inner_size: vec2(420, 220)
|
||||
body +: {
|
||||
|
|
@ -24,11 +17,9 @@ script_mod! {
|
|||
flow: Down
|
||||
spacing: 12
|
||||
align: Center
|
||||
on_render: ||{
|
||||
counter_label := Label{
|
||||
text: "Count: " + state.counter
|
||||
draw_text.text_style.font_size: 24
|
||||
}
|
||||
counter_label := Label{
|
||||
text: "Count: 0"
|
||||
draw_text.text_style.font_size: 24
|
||||
}
|
||||
}
|
||||
increment_button := Button{
|
||||
|
|
@ -44,15 +35,15 @@ script_mod! {
|
|||
pub struct App {
|
||||
#[live]
|
||||
ui: WidgetRef,
|
||||
#[rust]
|
||||
counter: i32,
|
||||
}
|
||||
|
||||
impl MatchEvent for App {
|
||||
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
|
||||
if self.ui.button(cx, ids!(increment_button)).clicked(actions) {
|
||||
script_eval!(cx,{
|
||||
mod.state.counter += 1
|
||||
ui.main_view.render()
|
||||
});
|
||||
self.counter += 1;
|
||||
self.ui.label(cx, ids!(counter_label)).set_text(cx, &format!("Count: {}", self.counter));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ use makepad_micro_serde::{SerBin, SerJson};
|
|||
use makepad_studio_hub::{HubConfig, HubConnection, MountConfig, StudioHub};
|
||||
use makepad_studio_protocol::hub_protocol::{ClientToHub, HubToClient, LogEntry, QueryId};
|
||||
use makepad_studio_protocol::{
|
||||
KeyCode, KeyEvent, KeyModifiers, MouseButton, RemoteKeyModifiers, RemoteMouseDown,
|
||||
RemoteMouseMove, RemoteMouseUp, RemoteScroll, StudioToApp, StudioToAppVec, WidgetSnapshot,
|
||||
KeyCode, KeyEvent, KeyModifiers, MouseButton, RemoteIMEComposition, RemoteKeyModifiers,
|
||||
RemoteLongPress, RemoteMouseDown, RemoteMouseMove, RemoteMouseUp, RemoteScroll,
|
||||
RemoteTextPaste, RemoteTouchPoint, RemoteTouchState, RemoteTouchUpdate, StudioToApp,
|
||||
StudioToAppVec, WidgetSnapshot,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
use std::cmp;
|
||||
|
|
@ -85,6 +87,11 @@ pub struct TestConfig {
|
|||
pub startup_pause: Duration,
|
||||
pub action_delay: Duration,
|
||||
pub keep_open: Duration,
|
||||
pub android: bool,
|
||||
pub device_serial: Option<String>,
|
||||
pub adb_path: Option<String>,
|
||||
pub android_port: u16,
|
||||
pub android_native_activity: bool,
|
||||
}
|
||||
|
||||
impl TestConfig {
|
||||
|
|
@ -126,6 +133,11 @@ impl TestConfig {
|
|||
startup_pause: env_duration_ms("MAKEPAD_TEST_STARTUP_DELAY_MS"),
|
||||
action_delay: env_duration_ms("MAKEPAD_TEST_ACTION_DELAY_MS"),
|
||||
keep_open: env_duration_ms("MAKEPAD_TEST_KEEP_OPEN_MS"),
|
||||
android: android_test_enabled(),
|
||||
device_serial: android_device_serial(),
|
||||
adb_path: android_adb_path(),
|
||||
android_port: android_hub_port(),
|
||||
android_native_activity: env_truthy("MAKEPAD_TEST_NATIVE_ACTIVITY"),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -157,6 +169,13 @@ impl TestConnection {
|
|||
Self::Remote(connection) => connection.recv_timeout(timeout),
|
||||
}
|
||||
}
|
||||
|
||||
fn studio_addr(&self) -> Option<String> {
|
||||
match self {
|
||||
Self::InProcess(connection) => connection.studio_addr(),
|
||||
Self::Remote(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TestAppInner {
|
||||
|
|
@ -208,7 +227,9 @@ impl TestApp {
|
|||
}
|
||||
|
||||
fn start_once(config: TestConfig) -> TestResult<Self> {
|
||||
let (connection, build_id) = if visible_mode_enabled() {
|
||||
let (connection, build_id) = if config.android {
|
||||
start_android_app(&config)?
|
||||
} else if visible_mode_enabled() {
|
||||
start_visible_app(&config)?
|
||||
} else {
|
||||
start_headless_app(&config)?
|
||||
|
|
@ -454,6 +475,128 @@ impl TestApp {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn touch_down(&self, x: f64, y: f64) {
|
||||
if let Err(err) = self.try_touch_down(x, y) {
|
||||
panic_for_error(err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_touch_down(&self, x: f64, y: f64) -> TestResult<()> {
|
||||
self.try_forward(vec![StudioToApp::TouchUpdate(RemoteTouchUpdate {
|
||||
time: now_seconds(),
|
||||
touches: vec![RemoteTouchPoint {
|
||||
state: RemoteTouchState::Start,
|
||||
abs_x: x,
|
||||
abs_y: y,
|
||||
time: now_seconds(),
|
||||
uid: 0,
|
||||
rotation_angle: 0.0,
|
||||
force: 1.0,
|
||||
radius_x: 4.0,
|
||||
radius_y: 4.0,
|
||||
}],
|
||||
})])?;
|
||||
self.pace_after_action();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn touch_move(&self, x: f64, y: f64) {
|
||||
if let Err(err) = self.try_touch_move(x, y) {
|
||||
panic_for_error(err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_touch_move(&self, x: f64, y: f64) -> TestResult<()> {
|
||||
self.try_forward(vec![StudioToApp::TouchUpdate(RemoteTouchUpdate {
|
||||
time: now_seconds(),
|
||||
touches: vec![RemoteTouchPoint {
|
||||
state: RemoteTouchState::Move,
|
||||
abs_x: x,
|
||||
abs_y: y,
|
||||
time: now_seconds(),
|
||||
uid: 0,
|
||||
rotation_angle: 0.0,
|
||||
force: 1.0,
|
||||
radius_x: 4.0,
|
||||
radius_y: 4.0,
|
||||
}],
|
||||
})])?;
|
||||
self.pace_after_action();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn touch_up(&self, x: f64, y: f64) {
|
||||
if let Err(err) = self.try_touch_up(x, y) {
|
||||
panic_for_error(err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_touch_up(&self, x: f64, y: f64) -> TestResult<()> {
|
||||
self.try_forward(vec![StudioToApp::TouchUpdate(RemoteTouchUpdate {
|
||||
time: now_seconds(),
|
||||
touches: vec![RemoteTouchPoint {
|
||||
state: RemoteTouchState::Stop,
|
||||
abs_x: x,
|
||||
abs_y: y,
|
||||
time: now_seconds(),
|
||||
uid: 0,
|
||||
rotation_angle: 0.0,
|
||||
force: 0.0,
|
||||
radius_x: 4.0,
|
||||
radius_y: 4.0,
|
||||
}],
|
||||
})])?;
|
||||
self.pace_after_action();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn long_press(&self, x: f64, y: f64, duration_ms: f64) {
|
||||
if let Err(err) = self.try_long_press(x, y, duration_ms) {
|
||||
panic_for_error(err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_long_press(&self, x: f64, y: f64, duration_ms: f64) -> TestResult<()> {
|
||||
self.try_forward(vec![StudioToApp::LongPress(RemoteLongPress {
|
||||
x,
|
||||
y,
|
||||
time: now_seconds(),
|
||||
duration_ms,
|
||||
})])?;
|
||||
self.pace_after_action();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn paste_text(&self, text: impl AsRef<str>) {
|
||||
if let Err(err) = self.try_paste_text(text) {
|
||||
panic_for_error(err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_paste_text(&self, text: impl AsRef<str>) -> TestResult<()> {
|
||||
let text = text.as_ref().to_string();
|
||||
self.try_forward(vec![StudioToApp::TextPaste(RemoteTextPaste {
|
||||
text,
|
||||
})])?;
|
||||
self.pace_after_action();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn ime_composition(&self, text: impl AsRef<str>) {
|
||||
if let Err(err) = self.try_ime_composition(text) {
|
||||
panic_for_error(err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_ime_composition(&self, text: impl AsRef<str>) -> TestResult<()> {
|
||||
let text = text.as_ref().to_string();
|
||||
self.try_forward(vec![StudioToApp::IMEComposition(
|
||||
RemoteIMEComposition { text },
|
||||
)])?;
|
||||
self.pace_after_action();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn query_widgets(
|
||||
&self,
|
||||
selector: &Selector,
|
||||
|
|
@ -623,6 +766,10 @@ impl TestApp {
|
|||
if inner.build_stopped.is_some() {
|
||||
return;
|
||||
}
|
||||
if inner.config.android {
|
||||
let full_package = android_full_package_name(&inner.config.package_name);
|
||||
let _ = adb_force_stop(&inner.config, &full_package);
|
||||
}
|
||||
let build_id = inner.build_id;
|
||||
let _ = inner.connection.send(ClientToHub::ClearBuild { build_id });
|
||||
}
|
||||
|
|
@ -964,6 +1111,82 @@ impl Locator {
|
|||
self.app.try_drag_from(&target, dx, dy)
|
||||
}
|
||||
|
||||
pub fn touch_down(self) -> Self {
|
||||
if let Err(err) = self.try_touch_down() {
|
||||
panic_for_error(err);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn try_touch_down(&self) -> TestResult<()> {
|
||||
let target = self.resolve_unique_visible()?;
|
||||
let (x, y) = snapshot_center_f64(&target);
|
||||
self.app.try_touch_down(x, y)
|
||||
}
|
||||
|
||||
pub fn touch_move(self, dx: f64, dy: f64) -> Self {
|
||||
if let Err(err) = self.try_touch_move(dx, dy) {
|
||||
panic_for_error(err);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn try_touch_move(&self, dx: f64, dy: f64) -> TestResult<()> {
|
||||
let target = self.resolve_unique_visible()?;
|
||||
let (cx, cy) = snapshot_center_f64(&target);
|
||||
self.app.try_touch_move(cx + dx, cy + dy)
|
||||
}
|
||||
|
||||
pub fn touch_up(self) -> Self {
|
||||
if let Err(err) = self.try_touch_up() {
|
||||
panic_for_error(err);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn try_touch_up(&self) -> TestResult<()> {
|
||||
let target = self.resolve_unique_visible()?;
|
||||
let (x, y) = snapshot_center_f64(&target);
|
||||
self.app.try_touch_up(x, y)
|
||||
}
|
||||
|
||||
pub fn long_press(self, duration_ms: f64) -> Self {
|
||||
if let Err(err) = self.try_long_press(duration_ms) {
|
||||
panic_for_error(err);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn try_long_press(&self, duration_ms: f64) -> TestResult<()> {
|
||||
let target = self.resolve_unique_visible()?;
|
||||
let (x, y) = snapshot_center_f64(&target);
|
||||
self.app.try_long_press(x, y, duration_ms)
|
||||
}
|
||||
|
||||
pub fn paste(self, text: impl AsRef<str>) -> Self {
|
||||
if let Err(err) = self.try_paste(text) {
|
||||
panic_for_error(err);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn try_paste(&self, text: impl AsRef<str>) -> TestResult<()> {
|
||||
self.try_click()?;
|
||||
self.app.try_paste_text(text)
|
||||
}
|
||||
|
||||
pub fn ime_composition(self, text: impl AsRef<str>) -> Self {
|
||||
if let Err(err) = self.try_ime_composition(text) {
|
||||
panic_for_error(err);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn try_ime_composition(&self, text: impl AsRef<str>) -> TestResult<()> {
|
||||
self.try_click()?;
|
||||
self.app.try_ime_composition(text)
|
||||
}
|
||||
|
||||
pub fn snapshot(&self) -> WidgetSnapshot {
|
||||
match self.try_snapshot() {
|
||||
Ok(widget) => widget,
|
||||
|
|
@ -1511,6 +1734,380 @@ fn env_duration_ms(name: &str) -> Duration {
|
|||
.unwrap_or(Duration::ZERO)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Android test helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const DEFAULT_ANDROID_PORT: u16 = 8001;
|
||||
const ANDROID_STARTUP_TIMEOUT: Duration = Duration::from_secs(120);
|
||||
const ANDROID_LAUNCH_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
|
||||
fn android_test_enabled() -> bool {
|
||||
env_truthy("MAKEPAD_TEST_ANDROID")
|
||||
}
|
||||
|
||||
fn android_device_serial() -> Option<String> {
|
||||
std::env::var("MAKEPAD_TEST_DEVICE")
|
||||
.ok()
|
||||
.map(|v| v.trim().to_string())
|
||||
.filter(|v| !v.is_empty())
|
||||
}
|
||||
|
||||
fn android_hub_port() -> u16 {
|
||||
std::env::var("MAKEPAD_TEST_ANDROID_PORT")
|
||||
.ok()
|
||||
.and_then(|v| v.trim().parse().ok())
|
||||
.unwrap_or(DEFAULT_ANDROID_PORT)
|
||||
}
|
||||
|
||||
fn android_adb_path() -> Option<String> {
|
||||
std::env::var("MAKEPAD_TEST_ADB")
|
||||
.ok()
|
||||
.map(|v| v.trim().to_string())
|
||||
.filter(|v| !v.is_empty())
|
||||
}
|
||||
|
||||
fn adb_command(config: &TestConfig) -> std::process::Command {
|
||||
let adb = config
|
||||
.adb_path
|
||||
.as_deref()
|
||||
.unwrap_or("adb");
|
||||
let mut cmd = std::process::Command::new(adb);
|
||||
if let Some(ref serial) = config.device_serial {
|
||||
cmd.arg("-s").arg(serial);
|
||||
}
|
||||
cmd
|
||||
}
|
||||
|
||||
fn adb_exec(config: &TestConfig, args: &[&str]) -> TestResult<String> {
|
||||
let output = adb_command(config)
|
||||
.args(args)
|
||||
.output()
|
||||
.map_err(|err| TestError::new(format!("failed to run adb: {err}")))?;
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(TestError::new(format!(
|
||||
"adb {} failed: {}",
|
||||
args.join(" "),
|
||||
stderr.trim()
|
||||
)));
|
||||
}
|
||||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||
}
|
||||
|
||||
fn android_full_package_name(package_name: &str) -> String {
|
||||
let underscore = package_name.replace('-', "_");
|
||||
format!("dev.makepad.{underscore}")
|
||||
}
|
||||
|
||||
fn adb_forward(config: &TestConfig, port: u16) -> TestResult<()> {
|
||||
let _ = adb_exec(config, &["reverse", "--remove", &format!("tcp:{port}")]);
|
||||
adb_exec(
|
||||
config,
|
||||
&["reverse", &format!("tcp:{port}"), &format!("tcp:{port}")],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn adb_install(config: &TestConfig, apk_path: &std::path::Path) -> TestResult<()> {
|
||||
let output = adb_command(config)
|
||||
.args(["install", "-r"])
|
||||
.arg(apk_path)
|
||||
.output()
|
||||
.map_err(|err| TestError::new(format!("failed to run adb install: {err}")))?;
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(TestError::new(format!("adb install failed: {}", stderr.trim())));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn adb_launch(
|
||||
config: &TestConfig,
|
||||
package: &str,
|
||||
build_id: u64,
|
||||
crate_name: &str,
|
||||
port: u16,
|
||||
) -> TestResult<()> {
|
||||
// Native-activity builds launch `android.app.NativeActivity` directly (no
|
||||
// Java Activity). Legacy Java builds launch the generated `MakepadApp`
|
||||
// subclass that bridges into `MakepadNative.activityOnCreate`.
|
||||
let activity = if config.android_native_activity {
|
||||
format!("{package}/android.app.NativeActivity")
|
||||
} else {
|
||||
format!("{package}/.MakepadApp")
|
||||
};
|
||||
let studio_host = format!("127.0.0.1:{port}");
|
||||
let output = adb_command(config)
|
||||
.args([
|
||||
"shell", "am", "start",
|
||||
"-n", &activity,
|
||||
"-e", "makepad.STUDIO_HOST", &studio_host,
|
||||
"-e", "makepad.STUDIO_BUILD", &build_id.to_string(),
|
||||
"-e", "makepad.STUDIO_CRATE", crate_name,
|
||||
])
|
||||
.output()
|
||||
.map_err(|err| TestError::new(format!("failed to run adb shell am start: {err}")))?;
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(TestError::new(format!("adb launch failed: {}", stderr.trim())));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn adb_force_stop(config: &TestConfig, package: &str) -> TestResult<()> {
|
||||
let _ = adb_exec(config, &["shell", "am", "force-stop", package]);
|
||||
// Also force-stop known interfering apps that share the Makepad runtime
|
||||
// and may reclaim the foreground during our tests.
|
||||
let interfering = ["rs.robius.robrix"];
|
||||
for pkg in &interfering {
|
||||
let _ = adb_exec(config, &["shell", "am", "force-stop", pkg]);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn adb_enable_package(config: &TestConfig, package: &str) -> TestResult<()> {
|
||||
let _ = adb_exec(config, &["shell", "pm", "enable", package]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn adb_grant_permissions(config: &TestConfig, package: &str) -> TestResult<()> {
|
||||
let perms = [
|
||||
"android.permission.READ_MEDIA_IMAGES",
|
||||
"android.permission.READ_MEDIA_VIDEO",
|
||||
"android.permission.READ_MEDIA_VISUAL_USER_SELECTED",
|
||||
"android.permission.CAMERA",
|
||||
"android.permission.RECORD_AUDIO",
|
||||
"android.permission.READ_EXTERNAL_STORAGE",
|
||||
"android.permission.BLUETOOTH_CONNECT",
|
||||
"android.permission.ACCESS_FINE_LOCATION",
|
||||
"android.permission.ACCESS_COARSE_LOCATION",
|
||||
];
|
||||
for perm in &perms {
|
||||
let _ = adb_exec(config, &["shell", "pm", "grant", package, perm]);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_workspace_root(manifest_dir: &std::path::Path) -> std::path::PathBuf {
|
||||
if let Ok(env_root) = std::env::var("MAKEPAD_WORKSPACE_ROOT") {
|
||||
let root = std::path::PathBuf::from(env_root);
|
||||
if root.join("Cargo.toml").exists() {
|
||||
return root;
|
||||
}
|
||||
}
|
||||
let mut dir = manifest_dir.to_path_buf();
|
||||
loop {
|
||||
let cargo_toml = dir.join("Cargo.toml");
|
||||
if cargo_toml.exists() {
|
||||
if let Ok(content) = std::fs::read_to_string(&cargo_toml) {
|
||||
if content.contains("tools/cargo_makepad") {
|
||||
// skip Cargo.toml files that reference the tool as a path
|
||||
}
|
||||
let has_workspace = content.contains("[workspace]")
|
||||
|| content.contains("workspace.members")
|
||||
|| content.contains("workspace.package");
|
||||
if has_workspace && dir.join("tools/cargo_makepad").exists() {
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !dir.pop() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
manifest_dir.to_path_buf()
|
||||
}
|
||||
|
||||
fn build_android_apk(config: &TestConfig) -> TestResult<std::path::PathBuf> {
|
||||
let workspace_root = resolve_workspace_root(&config.manifest_dir);
|
||||
let cargo_makepad = workspace_root
|
||||
.join("target")
|
||||
.join("release")
|
||||
.join("cargo-makepad");
|
||||
if !cargo_makepad.exists() {
|
||||
return Err(TestError::new(format!(
|
||||
"cargo-makepad not found at {}. Run: cargo build --release -p cargo-makepad",
|
||||
cargo_makepad.display()
|
||||
)));
|
||||
}
|
||||
let mut args = vec!["android"];
|
||||
if config.android_native_activity {
|
||||
args.push("--native-activity");
|
||||
}
|
||||
args.extend_from_slice(&["build", "-p", &config.package_name]);
|
||||
let output = std::process::Command::new(&cargo_makepad)
|
||||
.args(&args)
|
||||
.current_dir(&workspace_root)
|
||||
.output()
|
||||
.map_err(|err| TestError::new(format!("failed to run cargo makepad: {err}")))?;
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
return Err(TestError::new(format!(
|
||||
"android build failed:\nstdout: {}\nstderr: {}",
|
||||
stdout.trim(),
|
||||
stderr.trim()
|
||||
)));
|
||||
}
|
||||
let apk_dir_name = config.package_name.replace('-', "_");
|
||||
let apk_dir = workspace_root
|
||||
.join("target")
|
||||
.join("android")
|
||||
.join("makepad-android-apk")
|
||||
.join(&apk_dir_name)
|
||||
.join("apk");
|
||||
let apk_name = format!("{}.apk", &apk_dir_name);
|
||||
let apk_path = apk_dir.join(&apk_name);
|
||||
if !apk_path.exists() {
|
||||
return Err(TestError::new(format!(
|
||||
"APK not found at {}",
|
||||
apk_path.display()
|
||||
)));
|
||||
}
|
||||
Ok(apk_path)
|
||||
}
|
||||
|
||||
fn wait_for_android_app_started(
|
||||
connection: &TestConnection,
|
||||
build_id: QueryId,
|
||||
timeout: Duration,
|
||||
) -> TestResult<()> {
|
||||
let deadline = Instant::now() + timeout;
|
||||
loop {
|
||||
if Instant::now() >= deadline {
|
||||
return Err(TestError::new(
|
||||
"timed out waiting for Android app to connect to hub",
|
||||
));
|
||||
}
|
||||
let slice = cmp::min(
|
||||
POLL_INTERVAL,
|
||||
deadline.saturating_duration_since(Instant::now()),
|
||||
);
|
||||
let Some(msg) = connection.recv_timeout(slice) else {
|
||||
continue;
|
||||
};
|
||||
match msg {
|
||||
HubToClient::AppStarted {
|
||||
build_id: msg_build_id,
|
||||
} if msg_build_id == build_id => {
|
||||
return Ok(());
|
||||
}
|
||||
HubToClient::Error { message } => return Err(TestError::new(message)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_for_android_app_responsive(
|
||||
connection: &mut TestConnection,
|
||||
build_id: QueryId,
|
||||
timeout: Duration,
|
||||
) -> TestResult<()> {
|
||||
let deadline = Instant::now() + timeout;
|
||||
while Instant::now() < deadline {
|
||||
let query_id = connection.send(ClientToHub::WidgetTreeDump { build_id })?;
|
||||
let attempt_deadline = Instant::now() + ACTION_TIMEOUT;
|
||||
let mut replied = false;
|
||||
while Instant::now() < attempt_deadline {
|
||||
if Instant::now() >= deadline {
|
||||
break;
|
||||
}
|
||||
let slice = cmp::min(
|
||||
POLL_INTERVAL,
|
||||
attempt_deadline.saturating_duration_since(Instant::now()),
|
||||
);
|
||||
let Some(msg) = connection.recv_timeout(slice) else {
|
||||
continue;
|
||||
};
|
||||
if let HubToClient::WidgetTreeDump {
|
||||
query_id: id, dump: _, ..
|
||||
} = &msg
|
||||
{
|
||||
if *id == query_id {
|
||||
replied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if replied {
|
||||
return Ok(());
|
||||
}
|
||||
thread::sleep(POLL_INTERVAL);
|
||||
}
|
||||
Err(TestError::new(
|
||||
"timed out waiting for Android app to become responsive",
|
||||
))
|
||||
}
|
||||
|
||||
fn start_android_app(config: &TestConfig) -> TestResult<(TestConnection, QueryId)> {
|
||||
let hub_port = config.android_port;
|
||||
let listen_address = SocketAddr::from((Ipv4Addr::LOCALHOST, hub_port));
|
||||
let mut connection = TestConnection::InProcess(
|
||||
StudioHub::start_in_process(HubConfig {
|
||||
listen_address,
|
||||
mounts: vec![MountConfig {
|
||||
name: config.mount_name.clone(),
|
||||
path: config.manifest_dir.clone(),
|
||||
}],
|
||||
enable_in_process_gateway: true,
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(TestError::new)?,
|
||||
);
|
||||
|
||||
let build_id = QueryId(1);
|
||||
let full_package = android_full_package_name(&config.package_name);
|
||||
// The hub may have bound a fallback port (e.g. when a real Studio already
|
||||
// owns `android_port`). Route adb and the app at the port actually bound,
|
||||
// never a hardcoded one, or the app would dial a dead listener.
|
||||
let hub_port = connection
|
||||
.studio_addr()
|
||||
.and_then(|addr| addr.rsplit_once(':').map(|(_, p)| p.to_string()))
|
||||
.and_then(|p| p.parse::<u16>().ok())
|
||||
.unwrap_or(config.android_port);
|
||||
|
||||
eprintln!("[makepad-test] Android: forwarding ADB port {hub_port}");
|
||||
adb_forward(config, hub_port)?;
|
||||
|
||||
eprintln!("[makepad-test] Android: building APK for {}", config.package_name);
|
||||
let apk_path = build_android_apk(config)?;
|
||||
|
||||
eprintln!("[makepad-test] Android: installing APK");
|
||||
adb_install(config, &apk_path)?;
|
||||
|
||||
eprintln!("[makepad-test] Android: granting runtime permissions");
|
||||
adb_grant_permissions(config, &full_package)?;
|
||||
|
||||
eprintln!("[makepad-test] Android: force-stopping previous instance");
|
||||
let _ = adb_force_stop(config, &full_package);
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
|
||||
eprintln!("[makepad-test] Android: launching app");
|
||||
adb_launch(config, &full_package, build_id.0, &config.package_name, hub_port)?;
|
||||
|
||||
thread::sleep(Duration::from_secs(2));
|
||||
|
||||
eprintln!("[makepad-test] Android: waiting for app to connect to hub");
|
||||
wait_for_android_app_started(&connection, build_id, ANDROID_STARTUP_TIMEOUT)?;
|
||||
|
||||
eprintln!("[makepad-test] Android: app connected");
|
||||
|
||||
// The websocket connects on a background thread before the app's event
|
||||
// loop is up, so a cold start can answer the handshake well before it can
|
||||
// service hub requests. Legacy Java starts are the slowest: the first
|
||||
// frame (and with it the main loop that drains requests) only comes after
|
||||
// the SurfaceView surface materializes, which on a first launch after
|
||||
// install can exceed the per-request action timeout. Settle until the app
|
||||
// actually answers a request so the test's first query does not lose that
|
||||
// boot race.
|
||||
eprintln!("[makepad-test] Android: waiting for app to become responsive");
|
||||
wait_for_android_app_responsive(&mut connection, build_id, ANDROID_STARTUP_TIMEOUT)?;
|
||||
|
||||
eprintln!("[makepad-test] Android: app responsive");
|
||||
Ok((connection, build_id))
|
||||
}
|
||||
|
||||
fn now_seconds() -> f64 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
|
|
|
|||
|
|
@ -59,24 +59,50 @@ pub enum HttpServerRequest {
|
|||
},
|
||||
}
|
||||
|
||||
pub fn start_http_server(http_server: HttpServer) -> Option<std::thread::JoinHandle<()>> {
|
||||
/// Handle to a running HTTP server. Sending on `shutdown` (or dropping the
|
||||
/// sender) makes the accept loop exit so the listen port is released and the
|
||||
/// thread can be joined.
|
||||
pub struct HttpServerHandle {
|
||||
pub thread: std::thread::JoinHandle<()>,
|
||||
pub shutdown: mpsc::Sender<()>,
|
||||
}
|
||||
|
||||
pub fn start_http_server(http_server: HttpServer) -> Option<HttpServerHandle> {
|
||||
let listener = if let Ok(listener) = TcpListener::bind(http_server.listen_address) {
|
||||
listener
|
||||
} else {
|
||||
println!("Cannot bind http server port");
|
||||
return None;
|
||||
};
|
||||
if listener.set_nonblocking(true).is_err() {
|
||||
println!("Cannot set http server non-blocking");
|
||||
return None;
|
||||
}
|
||||
let (shutdown_tx, shutdown_rx) = mpsc::channel::<()>();
|
||||
|
||||
let listen_thread = {
|
||||
std::thread::spawn(move || {
|
||||
let mut connection_counter = 0u64;
|
||||
for tcp_stream in listener.incoming() {
|
||||
let mut tcp_stream = if let Ok(tcp_stream) = tcp_stream {
|
||||
tcp_stream
|
||||
} else {
|
||||
println!("Incoming stream failure");
|
||||
continue;
|
||||
loop {
|
||||
if shutdown_rx.try_recv().is_ok() {
|
||||
break;
|
||||
}
|
||||
let mut tcp_stream = match listener.accept() {
|
||||
Ok((tcp_stream, _)) => tcp_stream,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
// No pending connection; poll the shutdown channel
|
||||
// periodically so a drop releases the port promptly.
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
continue;
|
||||
}
|
||||
Err(_) => {
|
||||
println!("Incoming stream failure");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if tcp_stream.set_nonblocking(false).is_err() {
|
||||
continue;
|
||||
}
|
||||
let http_server = http_server.clone();
|
||||
connection_counter += 1;
|
||||
let _read_thread = std::thread::spawn(move || {
|
||||
|
|
@ -108,7 +134,10 @@ pub fn start_http_server(http_server: HttpServer) -> Option<std::thread::JoinHan
|
|||
}
|
||||
})
|
||||
};
|
||||
Some(listen_thread)
|
||||
Some(HttpServerHandle {
|
||||
thread: listen_thread,
|
||||
shutdown: shutdown_tx,
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_post(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ pub mod web_socket_parser;
|
|||
|
||||
pub use crate::backend::{EventSink, NetworkBackend, UnsupportedBackend};
|
||||
pub use crate::http_server::{
|
||||
start_http_server, HttpServer, HttpServerRequest, HttpServerResponse,
|
||||
start_http_server, HttpServer, HttpServerHandle, HttpServerRequest, HttpServerResponse,
|
||||
};
|
||||
pub use crate::runtime::{NetworkConfig, NetworkRuntime};
|
||||
pub use crate::socket_stream::SocketStream;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use std::time::Duration;
|
|||
use makepad_live_id::LiveId;
|
||||
|
||||
use crate::backend::{default_backend, EventSink, NetworkBackend};
|
||||
use crate::http_server::HttpServer;
|
||||
use crate::http_server::{HttpServer, HttpServerHandle};
|
||||
use crate::types::{HttpRequest, NetworkError, NetworkResponse, WsSend};
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -62,7 +62,7 @@ impl NetworkRuntime {
|
|||
pub fn start_http_server(
|
||||
&self,
|
||||
http_server: HttpServer,
|
||||
) -> Option<std::thread::JoinHandle<()>> {
|
||||
) -> Option<HttpServerHandle> {
|
||||
crate::http_server::start_http_server(http_server)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -604,6 +604,84 @@ impl Cx {
|
|||
StudioToApp::Custom(data) => {
|
||||
self.call_event_handler(&Event::Custom(data));
|
||||
}
|
||||
StudioToApp::TouchUpdate(remote_touch) => {
|
||||
let touches: Vec<crate::event::finger::TouchPoint> = remote_touch
|
||||
.touches
|
||||
.iter()
|
||||
.map(|rt| crate::event::finger::TouchPoint {
|
||||
state: match rt.state {
|
||||
makepad_studio_protocol::RemoteTouchState::Start => {
|
||||
crate::event::finger::TouchState::Start
|
||||
}
|
||||
makepad_studio_protocol::RemoteTouchState::Stop => {
|
||||
crate::event::finger::TouchState::Stop
|
||||
}
|
||||
makepad_studio_protocol::RemoteTouchState::Move => {
|
||||
crate::event::finger::TouchState::Move
|
||||
}
|
||||
makepad_studio_protocol::RemoteTouchState::Stable => {
|
||||
crate::event::finger::TouchState::Stable
|
||||
}
|
||||
},
|
||||
abs: crate::makepad_math::dvec2(rt.abs_x - pos.x, rt.abs_y - pos.y),
|
||||
time: rt.time,
|
||||
uid: rt.uid,
|
||||
rotation_angle: rt.rotation_angle,
|
||||
force: rt.force,
|
||||
radius: crate::makepad_math::dvec2(rt.radius_x, rt.radius_y),
|
||||
handled: Cell::new(Area::Empty),
|
||||
sweep_lock: Cell::new(Area::Empty),
|
||||
})
|
||||
.collect();
|
||||
self.fingers.process_touch_update_start(remote_touch.time, &touches);
|
||||
self.call_event_handler(&Event::TouchUpdate(
|
||||
crate::event::finger::TouchUpdateEvent {
|
||||
time: remote_touch.time,
|
||||
window_id,
|
||||
modifiers: crate::event::KeyModifiers::default(),
|
||||
touches,
|
||||
},
|
||||
));
|
||||
}
|
||||
StudioToApp::LongPress(remote_long) => {
|
||||
let abs = crate::makepad_math::dvec2(remote_long.x - pos.x, remote_long.y - pos.y);
|
||||
self.fingers.process_tap_count(abs, remote_long.time);
|
||||
self.fingers.mouse_down(crate::event::MouseButton::PRIMARY, window_id);
|
||||
self.call_event_handler(&Event::MouseDown(crate::event::MouseDownEvent {
|
||||
abs,
|
||||
button: crate::event::MouseButton::PRIMARY,
|
||||
window_id,
|
||||
modifiers: crate::event::KeyModifiers::default(),
|
||||
time: remote_long.time,
|
||||
handled: Cell::new(Area::Empty),
|
||||
}));
|
||||
self.call_event_handler(&Event::MouseUp(crate::event::MouseUpEvent {
|
||||
abs,
|
||||
button: crate::event::MouseButton::PRIMARY,
|
||||
window_id,
|
||||
modifiers: crate::event::KeyModifiers::default(),
|
||||
time: remote_long.time + remote_long.duration_ms * 0.001,
|
||||
}));
|
||||
self.fingers.mouse_up(crate::event::MouseButton::PRIMARY);
|
||||
self.fingers.cycle_hover_area(live_id!(mouse).into());
|
||||
self.send_studio_key_focus_rect_response();
|
||||
}
|
||||
StudioToApp::TextPaste(remote_paste) => {
|
||||
self.call_event_handler(&Event::TextInput(crate::event::TextInputEvent {
|
||||
input: remote_paste.text,
|
||||
replace_last: false,
|
||||
was_paste: true,
|
||||
..Default::default()
|
||||
}));
|
||||
}
|
||||
StudioToApp::IMEComposition(remote_ime) => {
|
||||
self.call_event_handler(&Event::TextInput(crate::event::TextInputEvent {
|
||||
input: remote_ime.text,
|
||||
replace_last: true,
|
||||
was_paste: false,
|
||||
..Default::default()
|
||||
}));
|
||||
}
|
||||
StudioToApp::KeepAlive | StudioToApp::None => {}
|
||||
StudioToApp::LiveChange { file_name, content } => {
|
||||
self.script_data
|
||||
|
|
|
|||
|
|
@ -299,7 +299,9 @@ impl Cx {
|
|||
self.display_context.screen_size = self.os.display_size / dpi_factor;
|
||||
self.display_context.safe_area_insets = insets;
|
||||
self.update_safe_inset_script_values(insets);
|
||||
Self::send_studio_message(AppToStudio::BeforeStartup);
|
||||
self.call_event_handler(&Event::Startup);
|
||||
Self::send_studio_message(AppToStudio::AfterStartup);
|
||||
self.redraw_all();
|
||||
|
||||
self.start_network_live_file_watcher();
|
||||
|
|
|
|||
|
|
@ -331,6 +331,7 @@ unsafe fn get_intent_string_extra(
|
|||
const MAKEPAD_PREFS_NAME: &str = "makepad";
|
||||
const MAKEPAD_STUDIO_HOST_PREF_KEY: &str = "studio_host";
|
||||
const MAKEPAD_STUDIO_CRATE_PREF_KEY: &str = "studio_crate";
|
||||
const MAKEPAD_STUDIO_BUILD_PREF_KEY: &str = "studio_build";
|
||||
const ANDROID_MODE_PRIVATE: i32 = 0;
|
||||
|
||||
unsafe fn new_jstring(env: *mut jni_sys::JNIEnv, value: &str) -> Option<jni_sys::jstring> {
|
||||
|
|
@ -453,6 +454,8 @@ pub unsafe fn apply_studio_env_from_activity(activity: *const std::ffi::c_void)
|
|||
.filter(|v| !v.trim().is_empty());
|
||||
let intent_studio_crate = get_intent_string_extra(env, activity, "makepad.STUDIO_CRATE")
|
||||
.filter(|v| !v.trim().is_empty());
|
||||
let intent_studio_build = get_intent_string_extra(env, activity, "makepad.STUDIO_BUILD")
|
||||
.filter(|v| !v.trim().is_empty());
|
||||
|
||||
if let Some(studio_host) = intent_studio_host {
|
||||
let _ = persist_string_pref(env, activity, MAKEPAD_STUDIO_HOST_PREF_KEY, &studio_host);
|
||||
|
|
@ -471,6 +474,15 @@ pub unsafe fn apply_studio_env_from_activity(activity: *const std::ffi::c_void)
|
|||
{
|
||||
std::env::set_var("STUDIO_CRATE", &studio_crate);
|
||||
}
|
||||
|
||||
if let Some(studio_build) = intent_studio_build {
|
||||
let _ = persist_string_pref(env, activity, MAKEPAD_STUDIO_BUILD_PREF_KEY, &studio_build);
|
||||
std::env::set_var("STUDIO_BUILD", &studio_build);
|
||||
} else if let Some(studio_build) =
|
||||
get_persisted_string_pref(env, activity, MAKEPAD_STUDIO_BUILD_PREF_KEY)
|
||||
{
|
||||
std::env::set_var("STUDIO_BUILD", &studio_build);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn attach_jni_env() -> *mut jni_sys::JNIEnv {
|
||||
|
|
|
|||
|
|
@ -240,6 +240,52 @@ pub struct RemoteScroll {
|
|||
pub modifiers: RemoteKeyModifiers,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, SerBin, DeBin, SerJson, DeJson, PartialEq)]
|
||||
pub enum RemoteTouchState {
|
||||
Start,
|
||||
Stop,
|
||||
Move,
|
||||
#[default]
|
||||
Stable,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, SerBin, DeBin, SerJson, DeJson, PartialEq)]
|
||||
pub struct RemoteTouchPoint {
|
||||
pub state: RemoteTouchState,
|
||||
pub abs_x: f64,
|
||||
pub abs_y: f64,
|
||||
pub time: f64,
|
||||
pub uid: u64,
|
||||
pub rotation_angle: f64,
|
||||
pub force: f64,
|
||||
pub radius_x: f64,
|
||||
pub radius_y: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, SerBin, DeBin, SerJson, DeJson, PartialEq)]
|
||||
pub struct RemoteTouchUpdate {
|
||||
pub time: f64,
|
||||
pub touches: Vec<RemoteTouchPoint>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, SerBin, DeBin, SerJson, DeJson, PartialEq)]
|
||||
pub struct RemoteLongPress {
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
pub time: f64,
|
||||
pub duration_ms: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, SerBin, DeBin, SerJson, DeJson, PartialEq)]
|
||||
pub struct RemoteTextPaste {
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, SerBin, DeBin, SerJson, DeJson, PartialEq)]
|
||||
pub struct RemoteIMEComposition {
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(SerBin, DeBin, SerJson, DeJson, Debug, Clone)]
|
||||
pub enum AppToStudio {
|
||||
LogItem(StudioLogItem),
|
||||
|
|
@ -419,6 +465,10 @@ pub enum StudioToApp {
|
|||
/// changes. Level state rather than edges, because that is what the OS
|
||||
/// APIs report and what `Cx::game_input_states` hands back.
|
||||
GameInput(Vec<RemoteGameInput>),
|
||||
TouchUpdate(RemoteTouchUpdate),
|
||||
LongPress(RemoteLongPress),
|
||||
TextPaste(RemoteTextPaste),
|
||||
IMEComposition(RemoteIMEComposition),
|
||||
/// Application-defined event. Delivered to the app as `Event::Custom`.
|
||||
Custom(String),
|
||||
#[default]
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ edition = "2021"
|
|||
description = "Studio2 hub (protocol + gateway + virtual fs)"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[[bin]]
|
||||
name = "hub-server"
|
||||
path = "src/bin/hub_server.rs"
|
||||
|
||||
[dependencies]
|
||||
makepad-script-std = { path = "../../platform/script/std", version = "1.0.0" }
|
||||
makepad-studio-protocol = { path = "../../platform/studio", version = "0.1.0" }
|
||||
|
|
|
|||
38
studio/hub/src/bin/hub_server.rs
Normal file
38
studio/hub/src/bin/hub_server.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use makepad_studio_hub::{HubConfig, MountConfig, StudioHub};
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let port: u16 = std::env::args()
|
||||
.nth(1)
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(8001);
|
||||
|
||||
let listen_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port);
|
||||
|
||||
let mount_path = std::env::args()
|
||||
.nth(2)
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| std::env::current_dir().unwrap());
|
||||
|
||||
println!("[hub-server] Listening on {}", listen_address);
|
||||
println!("[hub-server] Mount path: {}", mount_path.display());
|
||||
|
||||
let _handle = StudioHub::start_headless(HubConfig {
|
||||
listen_address,
|
||||
mounts: vec![MountConfig {
|
||||
name: "makepad".into(),
|
||||
path: mount_path,
|
||||
}],
|
||||
enable_in_process_gateway: true,
|
||||
..Default::default()
|
||||
})
|
||||
.expect("Failed to start hub");
|
||||
|
||||
println!("[hub-server] Hub started successfully on {}", listen_address);
|
||||
println!("[hub-server] Waiting for connections...");
|
||||
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(3600));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
use crate::dispatch::HubEvent;
|
||||
use makepad_micro_serde::SerBin;
|
||||
use makepad_script_std::makepad_network::{
|
||||
start_http_server, HttpServer, HttpServerRequest, HttpServerResponse, ToUISender,
|
||||
start_http_server, HttpServer, HttpServerHandle, HttpServerRequest, HttpServerResponse,
|
||||
ToUISender,
|
||||
};
|
||||
use makepad_studio_protocol::hub_protocol::{HubToClient, QueryId};
|
||||
use std::collections::HashMap;
|
||||
|
|
@ -19,8 +20,21 @@ enum SocketRole {
|
|||
|
||||
pub struct GatewayHandle {
|
||||
pub listen_address: SocketAddr,
|
||||
pub request_thread: JoinHandle<()>,
|
||||
pub http_thread: JoinHandle<()>,
|
||||
pub request_thread: Option<JoinHandle<()>>,
|
||||
pub http_thread: Option<JoinHandle<()>>,
|
||||
http_shutdown: Sender<()>,
|
||||
}
|
||||
|
||||
impl Drop for GatewayHandle {
|
||||
fn drop(&mut self) {
|
||||
// Stop the accept loop so the listen port is released. Dropping the
|
||||
// request channel senders then makes the request thread exit, which
|
||||
// drops its hub event sender and lets the hub core shut down too.
|
||||
let _ = self.http_shutdown.send(());
|
||||
if let Some(http_thread) = self.http_thread.take() {
|
||||
let _ = http_thread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
|
@ -39,7 +53,10 @@ pub fn start_http_gateway(
|
|||
event_tx: Sender<HubEvent>,
|
||||
) -> Result<GatewayHandle, String> {
|
||||
let (request_tx, request_rx) = mpsc::channel::<HttpServerRequest>();
|
||||
let http_thread = start_http_server(HttpServer {
|
||||
let HttpServerHandle {
|
||||
thread: http_thread,
|
||||
shutdown: http_shutdown,
|
||||
} = start_http_server(HttpServer {
|
||||
listen_address,
|
||||
request: request_tx,
|
||||
post_max_size,
|
||||
|
|
@ -218,8 +235,9 @@ pub fn start_http_gateway(
|
|||
|
||||
Ok(GatewayHandle {
|
||||
listen_address,
|
||||
request_thread,
|
||||
http_thread,
|
||||
request_thread: Some(request_thread),
|
||||
http_thread: Some(http_thread),
|
||||
http_shutdown,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ fn main() {
|
|||
}
|
||||
let root_path = args[1].clone();
|
||||
|
||||
net.start_http_server(HttpServer{
|
||||
let _http_server = net.start_http_server(HttpServer{
|
||||
listen_address:addr,
|
||||
post_max_size: 1024*1024,
|
||||
request: tx_request
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@ i_overlay = { path = "../libs/i_overlay", version = "7.0.3", optional = true, de
|
|||
makepad-fast-inflate = { path = "../libs/fast_inflate", optional = true }
|
||||
makepad-voice = { path = "../libs/voice", version = "0.1.0", optional = true }
|
||||
makepad-cef = { path = "../libs/cef", optional = true }
|
||||
# Public optional sibling crates. These are re-exported from lib.rs so an
|
||||
# application can depend on makepad-widgets as its sole Makepad source.
|
||||
makepad-gltf = { path = "../libs/gltf", optional = true }
|
||||
makepad-csg = { path = "../libs/csg/csg", optional = true }
|
||||
makepad-test = { path = "../libs/makepad_test", optional = true }
|
||||
|
||||
makepad-html = { path = "../libs/html", version = "1.0.0" }
|
||||
unicode-segmentation = { version = "1.12.0", path = "../libs/unicode/unicode-segmentation" }
|
||||
|
|
@ -28,32 +33,16 @@ ttf-parser = { path = "../libs/ttf-parser" }
|
|||
|
||||
serde = { version = "1.0", optional = true, features = ["derive"] }
|
||||
|
||||
# Public optional sibling crates, re-exported from lib.rs so an application
|
||||
# can depend on makepad-widgets as its sole Makepad source. These are
|
||||
# fork-local additions; an upstream sync dropped them at abd70f4, which
|
||||
# removed the `test` feature and broke every crate using makepad_test.
|
||||
[dependencies.makepad-gltf]
|
||||
path = "../libs/gltf"
|
||||
optional = true
|
||||
|
||||
[dependencies.makepad-csg]
|
||||
path = "../libs/csg/csg"
|
||||
optional = true
|
||||
|
||||
[dependencies.makepad-test]
|
||||
path = "../libs/makepad_test"
|
||||
optional = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
voice = ["dep:makepad-voice"]
|
||||
maps = ["dep:makepad-mbtile-reader", "dep:makepad-fast-inflate", "dep:i_overlay"]
|
||||
gltf = ["dep:makepad-gltf"]
|
||||
csg = ["dep:makepad-csg"]
|
||||
test = ["dep:makepad-test"]
|
||||
pdf = ["dep:makepad-pdf-parse"]
|
||||
cef = ["dep:makepad-cef"]
|
||||
|
||||
## Enables certain public-facing types to derive serde serialization traits.
|
||||
serde = ["dep:serde", "makepad-draw/serde", "makepad-derive-widget/serde"]
|
||||
gltf = ["dep:makepad-gltf"]
|
||||
csg = ["dep:makepad-csg"]
|
||||
test = ["dep:makepad-test"]
|
||||
|
|
|
|||
|
|
@ -9,22 +9,25 @@ pub use makepad_script::script_eval;
|
|||
pub use makepad_script::{ScriptValue, ScriptVm};
|
||||
|
||||
pub use makepad_html;
|
||||
#[cfg(feature = "pdf")]
|
||||
pub use makepad_pdf_parse;
|
||||
|
||||
// Fork-local re-exports of the optional sibling crates, so an application
|
||||
// can depend on makepad-widgets alone. Lost in the upstream sync at
|
||||
// abd70f4; `makepad_test` in particular is imported as
|
||||
// `makepad_widgets::makepad_test` by every UI test suite in nigig-org.
|
||||
pub use makepad_draw::makepad_zune_jpeg;
|
||||
pub use makepad_draw::makepad_zune_png;
|
||||
|
||||
// Optional sibling Makepad workspace crates. These re-exports permit a
|
||||
// downstream application to depend on makepad-widgets as the single Makepad
|
||||
// source while keeping all extra APIs feature-gated.
|
||||
#[cfg(feature = "maps")]
|
||||
pub use makepad_fast_inflate;
|
||||
#[cfg(feature = "maps")]
|
||||
pub use makepad_mbtile_reader;
|
||||
#[cfg(feature = "gltf")]
|
||||
pub use makepad_gltf;
|
||||
#[cfg(feature = "csg")]
|
||||
pub use makepad_csg;
|
||||
#[cfg(feature = "test")]
|
||||
pub use makepad_test;
|
||||
#[cfg(feature = "pdf")]
|
||||
pub use makepad_pdf_parse;
|
||||
|
||||
pub use makepad_draw::makepad_zune_jpeg;
|
||||
pub use makepad_draw::makepad_zune_png;
|
||||
|
||||
// Core modules (used internally first)
|
||||
pub mod animator;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue