feat: device hot-reload, tools-relative adb, auto port bind (#3586)
* wip: bind to 0.0.0.0 instead of 127.0.0.1 * dont hang in background - ignore suspend commands when tty is disabled * use system adb * pass configs through to android mobile launch * fix: line sizes + clear pushes frame * dont resume window state on mobile * fix screen sizing for ipad * attempt and fail to implement codesigning * Clippy and note about safety in android bindings
This commit is contained in:
1 parent
e199e86dad
commit
8334f7fbaa
22 files changed
+620
-212
No files matched your search
@@ -1,6 +1,7 @@
|
||||
use crate::{
|
||||
serve::{ansi_buffer::AnsiStringLine, Builder, ServeUpdate, Watcher, WebServer},
|
||||
BuildStage, BuildUpdate, DioxusCrate, Platform, ServeArgs, TraceContent, TraceMsg, TraceSrc,
|
||||
BuildStage, BuildUpdate, DioxusCrate, Platform, RustcDetails, ServeArgs, TraceContent,
|
||||
TraceMsg, TraceSrc,
|
||||
};
|
||||
use crossterm::{
|
||||
cursor::{Hide, Show},
|
||||
@@ -28,7 +29,7 @@ use tracing::Level;
|
||||
const TICK_RATE_MS: u64 = 100;
|
||||
const VIEWPORT_MAX_WIDTH: u16 = 100;
|
||||
const VIEWPORT_HEIGHT_SMALL: u16 = 5;
|
||||
const VIEWPORT_HEIGHT_BIG: u16 = 12;
|
||||
const VIEWPORT_HEIGHT_BIG: u16 = 13;
|
||||
|
||||
/// The TUI that drives the console output.
|
||||
///
|
||||
@@ -63,6 +64,8 @@ pub struct Output {
|
||||
// ! needs to be wrapped in an &mut since `render stateful widget` requires &mut... but our
|
||||
// "render" method only borrows &self (for no particular reason at all...)
|
||||
throbber: RefCell<throbber_widgets_tui::ThrobberState>,
|
||||
|
||||
rustc_details: RustcDetails,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
@@ -76,17 +79,9 @@ struct RenderState<'a> {
|
||||
}
|
||||
|
||||
impl Output {
|
||||
pub(crate) fn start(cfg: &ServeArgs) -> io::Result<Self> {
|
||||
pub(crate) async fn start(cfg: &ServeArgs) -> crate::Result<Self> {
|
||||
let mut output = Self {
|
||||
term: Rc::new(RefCell::new(
|
||||
Terminal::with_options(
|
||||
CrosstermBackend::new(stdout()),
|
||||
TerminalOptions {
|
||||
viewport: Viewport::Inline(VIEWPORT_HEIGHT_SMALL),
|
||||
},
|
||||
)
|
||||
.ok(),
|
||||
)),
|
||||
term: Rc::new(RefCell::new(None)),
|
||||
interactive: cfg.is_interactive_tty(),
|
||||
dx_version: format!(
|
||||
"{}-{}",
|
||||
@@ -95,7 +90,6 @@ impl Output {
|
||||
),
|
||||
platform: cfg.build_arguments.platform.expect("To be resolved by now"),
|
||||
events: None,
|
||||
// messages: Vec::new(),
|
||||
more_modal_open: false,
|
||||
pending_logs: VecDeque::new(),
|
||||
throbber: RefCell::new(throbber_widgets_tui::ThrobberState::default()),
|
||||
@@ -107,6 +101,7 @@ impl Output {
|
||||
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
|
||||
interval
|
||||
},
|
||||
rustc_details: RustcDetails::from_cli().await?,
|
||||
};
|
||||
|
||||
output.startup()?;
|
||||
@@ -127,11 +122,24 @@ impl Output {
|
||||
original_hook(info);
|
||||
}));
|
||||
|
||||
enable_raw_mode()?;
|
||||
stdout()
|
||||
.execute(Hide)?
|
||||
.execute(EnableFocusChange)?
|
||||
.execute(EnableBracketedPaste)?;
|
||||
// Check if writing the terminal is going to block infinitely.
|
||||
// If it does, we should disable interactive mode. This ensures we work with programs like `bg`
|
||||
// which suspend the process and cause us to block when writing output.
|
||||
if Self::enable_raw_mode().is_err() {
|
||||
self.term.take();
|
||||
self.interactive = false;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.term.replace(
|
||||
Terminal::with_options(
|
||||
CrosstermBackend::new(stdout()),
|
||||
TerminalOptions {
|
||||
viewport: Viewport::Inline(VIEWPORT_HEIGHT_SMALL),
|
||||
},
|
||||
)
|
||||
.ok(),
|
||||
);
|
||||
|
||||
// Initialize the event stream here - this is optional because an EvenStream in a non-interactive
|
||||
// terminal will cause a panic instead of simply doing nothing.
|
||||
@@ -142,6 +150,36 @@ impl Output {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Enable raw mode, but don't let it block forever.
|
||||
///
|
||||
/// This lets us check if writing to tty is going to block forever and then recover, allowing
|
||||
/// interopability with programs like `bg`.
|
||||
fn enable_raw_mode() -> io::Result<()> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
|
||||
// Ignore SIGTSTP, SIGTTIN, and SIGTTOU
|
||||
_ = signal(SignalKind::from_raw(20))?; // SIGTSTP
|
||||
_ = signal(SignalKind::from_raw(21))?; // SIGTTIN
|
||||
_ = signal(SignalKind::from_raw(22))?; // SIGTTOU
|
||||
}
|
||||
|
||||
use std::io::IsTerminal;
|
||||
|
||||
if !stdout().is_terminal() {
|
||||
return io::Result::Err(io::Error::new(io::ErrorKind::Other, "Not a terminal"));
|
||||
}
|
||||
|
||||
enable_raw_mode()?;
|
||||
stdout()
|
||||
.execute(Hide)?
|
||||
.execute(EnableFocusChange)?
|
||||
.execute(EnableBracketedPaste)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Call the shutdown functions that might mess with the terminal settings - see the related code
|
||||
/// in "startup" for more details about what we need to unset
|
||||
pub(crate) fn shutdown(&self) -> io::Result<()> {
|
||||
@@ -163,6 +201,10 @@ impl Output {
|
||||
use futures_util::future::OptionFuture;
|
||||
use futures_util::StreamExt;
|
||||
|
||||
if !self.interactive {
|
||||
return std::future::pending().await;
|
||||
}
|
||||
|
||||
// Wait for the next user event or animation tick
|
||||
loop {
|
||||
let next = OptionFuture::from(self.events.as_mut().map(|f| f.next()));
|
||||
@@ -226,7 +268,16 @@ impl Output {
|
||||
stdout()
|
||||
.execute(Clear(ClearType::All))?
|
||||
.execute(Clear(ClearType::Purge))?;
|
||||
_ = self.term.borrow_mut().as_mut().map(|t| t.clear());
|
||||
|
||||
// Clear the terminal and push the frame to the bottom
|
||||
_ = self.term.borrow_mut().as_mut().map(|t| {
|
||||
let frame_rect = t.get_frame().area();
|
||||
let term_size = t.size().unwrap();
|
||||
let remaining_space = term_size
|
||||
.height
|
||||
.saturating_sub(frame_rect.y + frame_rect.height);
|
||||
t.insert_before(remaining_space, |_| {})
|
||||
});
|
||||
}
|
||||
|
||||
// Toggle the more modal by swapping the the terminal with a new one
|
||||
@@ -611,7 +662,7 @@ impl Output {
|
||||
self.render_feature_list(frame, app_features, state);
|
||||
|
||||
// todo(jon) should we write https ?
|
||||
let address = match state.server.server_address() {
|
||||
let address = match state.server.displayed_address() {
|
||||
Some(address) => format!("http://{}", address).blue(),
|
||||
None => "no server address".dark_gray(),
|
||||
};
|
||||
@@ -656,16 +707,20 @@ impl Output {
|
||||
);
|
||||
}
|
||||
|
||||
fn render_more_modal(&self, frame: &mut Frame<'_>, area: Rect, _state: RenderState) {
|
||||
fn render_more_modal(&self, frame: &mut Frame<'_>, area: Rect, state: RenderState) {
|
||||
let [col1, col2] =
|
||||
Layout::horizontal([Constraint::Length(50), Constraint::Fill(1)]).areas(area);
|
||||
|
||||
let [top, bottom] = Layout::vertical([Constraint::Fill(1), Constraint::Length(2)])
|
||||
.horizontal_margin(1)
|
||||
.areas(area);
|
||||
.areas(col1);
|
||||
|
||||
let meta_list: [_; 5] = Layout::vertical([
|
||||
let meta_list: [_; 6] = Layout::vertical([
|
||||
Constraint::Length(1), // spacing
|
||||
Constraint::Length(1), // item 1
|
||||
Constraint::Length(1), // item 2
|
||||
Constraint::Length(1), // item 3
|
||||
Constraint::Length(1), // item 4
|
||||
Constraint::Length(1), // Spacing
|
||||
])
|
||||
.areas(top);
|
||||
@@ -680,15 +735,27 @@ impl Output {
|
||||
frame.render_widget(
|
||||
Paragraph::new(Line::from(vec![
|
||||
"rustc: ".gray(),
|
||||
"1.79.9 (nightly)".yellow(),
|
||||
self.rustc_details.version.as_str().yellow(),
|
||||
])),
|
||||
meta_list[2],
|
||||
);
|
||||
frame.render_widget(
|
||||
Paragraph::new(Line::from(vec!["Hotreload: ".gray(), "rsx only".yellow()])),
|
||||
Paragraph::new(Line::from(vec![
|
||||
"Hotreload: ".gray(),
|
||||
"rsx and assets".yellow(),
|
||||
])),
|
||||
meta_list[3],
|
||||
);
|
||||
|
||||
let server_address = match state.server.server_address() {
|
||||
Some(address) => format!("http://{}", address).yellow(),
|
||||
None => "no address".dark_gray(),
|
||||
};
|
||||
frame.render_widget(
|
||||
Paragraph::new(Line::from(vec!["Network: ".gray(), server_address])),
|
||||
meta_list[4],
|
||||
);
|
||||
|
||||
let links_list: [_; 2] =
|
||||
Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(bottom);
|
||||
|
||||
@@ -707,6 +774,35 @@ impl Output {
|
||||
])),
|
||||
links_list[1],
|
||||
);
|
||||
|
||||
let cmds = [
|
||||
"",
|
||||
"r: rebuild the app",
|
||||
"o: open the app",
|
||||
"p: pause rebuilds",
|
||||
"v: toggle verbose logs",
|
||||
"t: toggle tracing logs ",
|
||||
"c: clear the screen",
|
||||
"/: toggle more commands",
|
||||
];
|
||||
let layout: [_; 8] = Layout::vertical(cmds.iter().map(|_| Constraint::Length(1)))
|
||||
.horizontal_margin(1)
|
||||
.areas(col2);
|
||||
for (idx, cmd) in cmds.iter().enumerate() {
|
||||
if cmd.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let (cmd, detail) = cmd.split_once(": ").unwrap_or((cmd, ""));
|
||||
frame.render_widget(
|
||||
Paragraph::new(Line::from(vec![
|
||||
cmd.gray(),
|
||||
": ".gray(),
|
||||
detail.dark_gray(),
|
||||
])),
|
||||
layout[idx],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Render borders around the terminal, forcing an inner clear while we're at it
|
||||
@@ -905,9 +1001,12 @@ impl Output {
|
||||
line = line.dark_gray();
|
||||
}
|
||||
|
||||
let line_length: usize = line.spans.iter().map(|f| f.content.len()).sum();
|
||||
|
||||
lines.push(AnsiStringLine::new(line_length.max(100) as _).render(&line));
|
||||
// Create the ansi -> raw string line with a width of either the viewport width or the max width
|
||||
let line_length = line.styled_graphemes(Style::default()).count();
|
||||
lines.push(
|
||||
AnsiStringLine::new(line_length.max(VIEWPORT_MAX_WIDTH.into()) as _)
|
||||
.render(&line),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user