Create tcp server
This commit is contained in:
		
							parent
							
								
									1a54c2e6fc
								
							
						
					
					
						commit
						47a05968fb
					
				| 
						 | 
				
			
			@ -28,6 +28,15 @@ dependencies = [
 | 
			
		|||
 "serde",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "addr2line"
 | 
			
		||||
version = "0.24.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "gimli",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "adler2"
 | 
			
		||||
version = "2.0.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -400,6 +409,21 @@ version = "1.4.0"
 | 
			
		|||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "backtrace"
 | 
			
		||||
version = "0.3.74"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "addr2line",
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "libc",
 | 
			
		||||
 "miniz_oxide",
 | 
			
		||||
 "object",
 | 
			
		||||
 "rustc-demangle",
 | 
			
		||||
 "windows-targets 0.52.6",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bindgen"
 | 
			
		||||
version = "0.70.1"
 | 
			
		||||
| 
						 | 
				
			
			@ -1306,6 +1330,12 @@ dependencies = [
 | 
			
		|||
 "windows 0.58.0",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "gimli"
 | 
			
		||||
version = "0.31.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "gl_generator"
 | 
			
		||||
version = "0.14.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -1736,6 +1766,7 @@ dependencies = [
 | 
			
		|||
 "serde",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
 "thread-priority",
 | 
			
		||||
 "tokio",
 | 
			
		||||
 "wgpu",
 | 
			
		||||
 "windows 0.58.0",
 | 
			
		||||
 "winit",
 | 
			
		||||
| 
						 | 
				
			
			@ -1902,6 +1933,17 @@ dependencies = [
 | 
			
		|||
 "simd-adler32",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "mio"
 | 
			
		||||
version = "1.0.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "libc",
 | 
			
		||||
 "wasi",
 | 
			
		||||
 "windows-sys 0.52.0",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "naga"
 | 
			
		||||
version = "23.1.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -2276,6 +2318,15 @@ dependencies = [
 | 
			
		|||
 "objc2-foundation",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "object"
 | 
			
		||||
version = "0.36.7"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "memchr",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "oboe"
 | 
			
		||||
version = "0.6.1"
 | 
			
		||||
| 
						 | 
				
			
			@ -2696,6 +2747,12 @@ dependencies = [
 | 
			
		|||
 "realfft",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rustc-demangle"
 | 
			
		||||
version = "0.1.24"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rustc-hash"
 | 
			
		||||
version = "1.1.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -2918,6 +2975,16 @@ dependencies = [
 | 
			
		|||
 "serde",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "socket2"
 | 
			
		||||
version = "0.5.8"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "libc",
 | 
			
		||||
 "windows-sys 0.52.0",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "spirv"
 | 
			
		||||
version = "0.3.0+sdk-1.3.268.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -3070,6 +3137,33 @@ dependencies = [
 | 
			
		|||
 "zerovec",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tokio"
 | 
			
		||||
version = "1.42.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "backtrace",
 | 
			
		||||
 "bytes",
 | 
			
		||||
 "libc",
 | 
			
		||||
 "mio",
 | 
			
		||||
 "pin-project-lite",
 | 
			
		||||
 "socket2",
 | 
			
		||||
 "tokio-macros",
 | 
			
		||||
 "windows-sys 0.52.0",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tokio-macros"
 | 
			
		||||
version = "2.4.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "toml"
 | 
			
		||||
version = "0.8.19"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,6 +32,7 @@ rubato = "0.16"
 | 
			
		|||
serde = { version = "1", features = ["derive"] }
 | 
			
		||||
serde_json = "1"
 | 
			
		||||
thread-priority = "1"
 | 
			
		||||
tokio = { version = "1", features = ["io-util", "macros", "net", "rt", "sync"] }
 | 
			
		||||
wgpu = "23"
 | 
			
		||||
winit = { version = "0.30", features = ["serde"] }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,7 @@ use crate::{
 | 
			
		|||
    emulator::{EmulatorClient, EmulatorCommand, SimId},
 | 
			
		||||
    input::MappingProvider,
 | 
			
		||||
    persistence::Persistence,
 | 
			
		||||
    window::{AboutWindow, AppWindow, GameWindow, InputWindow},
 | 
			
		||||
    window::{AboutWindow, AppWindow, GameWindow, GdbServerWindow, InputWindow},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
fn load_icon() -> anyhow::Result<IconData> {
 | 
			
		||||
| 
						 | 
				
			
			@ -183,6 +183,10 @@ impl ApplicationHandler<UserEvent> for Application {
 | 
			
		|||
                let about = AboutWindow;
 | 
			
		||||
                self.open(event_loop, Box::new(about));
 | 
			
		||||
            }
 | 
			
		||||
            UserEvent::OpenDebugger(sim_id) => {
 | 
			
		||||
                let debugger = GdbServerWindow::new(sim_id, self.client.clone());
 | 
			
		||||
                self.open(event_loop, Box::new(debugger));
 | 
			
		||||
            }
 | 
			
		||||
            UserEvent::OpenInput => {
 | 
			
		||||
                let input = InputWindow::new(self.mappings.clone());
 | 
			
		||||
                self.open(event_loop, Box::new(input));
 | 
			
		||||
| 
						 | 
				
			
			@ -374,6 +378,7 @@ impl Drop for Viewport {
 | 
			
		|||
pub enum UserEvent {
 | 
			
		||||
    GamepadEvent(gilrs::Event),
 | 
			
		||||
    OpenAbout,
 | 
			
		||||
    OpenDebugger(SimId),
 | 
			
		||||
    OpenInput,
 | 
			
		||||
    OpenPlayer2,
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
use std::{
 | 
			
		||||
    collections::HashMap,
 | 
			
		||||
    fmt::Display,
 | 
			
		||||
    fs::{self, File},
 | 
			
		||||
    io::{Read, Seek, SeekFrom, Write},
 | 
			
		||||
    path::{Path, PathBuf},
 | 
			
		||||
| 
						 | 
				
			
			@ -45,6 +46,14 @@ impl SimId {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
impl Display for SimId {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        f.write_str(match self {
 | 
			
		||||
            Self::Player1 => "Player 1",
 | 
			
		||||
            Self::Player2 => "Player 2",
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct Cart {
 | 
			
		||||
    rom_path: PathBuf,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,154 @@
 | 
			
		|||
use std::{
 | 
			
		||||
    error::Error,
 | 
			
		||||
    sync::{Arc, Mutex},
 | 
			
		||||
    thread,
 | 
			
		||||
};
 | 
			
		||||
use tokio::{
 | 
			
		||||
    io::{AsyncReadExt, AsyncWriteExt as _, BufReader, BufWriter},
 | 
			
		||||
    net::{
 | 
			
		||||
        tcp::{OwnedReadHalf, OwnedWriteHalf},
 | 
			
		||||
        TcpListener, TcpStream,
 | 
			
		||||
    },
 | 
			
		||||
    select,
 | 
			
		||||
    sync::oneshot,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::emulator::{EmulatorClient, EmulatorCommand, SimId};
 | 
			
		||||
 | 
			
		||||
pub struct GdbServer {
 | 
			
		||||
    sim_id: SimId,
 | 
			
		||||
    client: EmulatorClient,
 | 
			
		||||
    status: Arc<Mutex<GdbServerStatus>>,
 | 
			
		||||
    killer: Option<oneshot::Sender<()>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl GdbServer {
 | 
			
		||||
    pub fn new(sim_id: SimId, client: EmulatorClient) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            sim_id,
 | 
			
		||||
            client,
 | 
			
		||||
            status: Arc::new(Mutex::new(GdbServerStatus::Stopped)),
 | 
			
		||||
            killer: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn status(&self) -> GdbServerStatus {
 | 
			
		||||
        self.status.lock().unwrap().clone()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn start(&mut self, port: u16) {
 | 
			
		||||
        let sim_id = self.sim_id;
 | 
			
		||||
        let client = self.client.clone();
 | 
			
		||||
        let status = self.status.clone();
 | 
			
		||||
        let (tx, rx) = oneshot::channel();
 | 
			
		||||
        self.killer = Some(tx);
 | 
			
		||||
        thread::spawn(move || {
 | 
			
		||||
            tokio::runtime::Builder::new_current_thread()
 | 
			
		||||
                .enable_all()
 | 
			
		||||
                .build()
 | 
			
		||||
                .unwrap()
 | 
			
		||||
                .block_on(async move {
 | 
			
		||||
                    select! {
 | 
			
		||||
                        _ = run_server(sim_id, client, port, &status) => {}
 | 
			
		||||
                        _ = rx => {
 | 
			
		||||
                            *status.lock().unwrap() = GdbServerStatus::Stopped;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn stop(&mut self) {
 | 
			
		||||
        if let Some(killer) = self.killer.take() {
 | 
			
		||||
            let _ = killer.send(());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn run_server(
 | 
			
		||||
    sim_id: SimId,
 | 
			
		||||
    client: EmulatorClient,
 | 
			
		||||
    port: u16,
 | 
			
		||||
    status: &Mutex<GdbServerStatus>,
 | 
			
		||||
) {
 | 
			
		||||
    client.send_command(EmulatorCommand::Pause);
 | 
			
		||||
    let Some(stream) = try_connect(port, status).await else {
 | 
			
		||||
        return;
 | 
			
		||||
    };
 | 
			
		||||
    let connection = GdbConnection::new(sim_id, client, stream);
 | 
			
		||||
    match connection.run().await {
 | 
			
		||||
        Ok(()) => {
 | 
			
		||||
            *status.lock().unwrap() = GdbServerStatus::Stopped;
 | 
			
		||||
        }
 | 
			
		||||
        Err(error) => {
 | 
			
		||||
            *status.lock().unwrap() = GdbServerStatus::Error(error.to_string());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn try_connect(port: u16, status: &Mutex<GdbServerStatus>) -> Option<TcpStream> {
 | 
			
		||||
    *status.lock().unwrap() = GdbServerStatus::Connecting;
 | 
			
		||||
    let listener = match TcpListener::bind(("127.0.0.1", port)).await {
 | 
			
		||||
        Ok(l) => l,
 | 
			
		||||
        Err(err) => {
 | 
			
		||||
            *status.lock().unwrap() = GdbServerStatus::Error(err.to_string());
 | 
			
		||||
            return None;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    match listener.accept().await {
 | 
			
		||||
        Ok((stream, _)) => {
 | 
			
		||||
            *status.lock().unwrap() = GdbServerStatus::Running;
 | 
			
		||||
            Some(stream)
 | 
			
		||||
        }
 | 
			
		||||
        Err(err) => {
 | 
			
		||||
            *status.lock().unwrap() = GdbServerStatus::Error(err.to_string());
 | 
			
		||||
            None
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub enum GdbServerStatus {
 | 
			
		||||
    Stopped,
 | 
			
		||||
    Connecting,
 | 
			
		||||
    Running,
 | 
			
		||||
    Error(String),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl GdbServerStatus {
 | 
			
		||||
    pub fn running(&self) -> bool {
 | 
			
		||||
        matches!(self, Self::Connecting | Self::Running)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct GdbConnection {
 | 
			
		||||
    sim_id: SimId,
 | 
			
		||||
    client: EmulatorClient,
 | 
			
		||||
    stream_in: BufReader<OwnedReadHalf>,
 | 
			
		||||
    stream_out: BufWriter<OwnedWriteHalf>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl GdbConnection {
 | 
			
		||||
    fn new(sim_id: SimId, client: EmulatorClient, stream: TcpStream) -> Self {
 | 
			
		||||
        let (rx, tx) = stream.into_split();
 | 
			
		||||
        Self {
 | 
			
		||||
            sim_id,
 | 
			
		||||
            client,
 | 
			
		||||
            stream_in: BufReader::new(rx),
 | 
			
		||||
            stream_out: BufWriter::new(tx),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    async fn run(mut self) -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        println!("Connected for {}", self.sim_id);
 | 
			
		||||
        self.client.send_command(EmulatorCommand::Resume);
 | 
			
		||||
        loop {
 | 
			
		||||
            let byte = self.read_byte().await?;
 | 
			
		||||
            println!("{byte}");
 | 
			
		||||
            self.stream_out.write_u8(byte).await?;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn read_byte(&mut self) -> std::io::Result<u8> {
 | 
			
		||||
        self.stream_in.read_u8().await
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -14,6 +14,7 @@ mod app;
 | 
			
		|||
mod audio;
 | 
			
		||||
mod controller;
 | 
			
		||||
mod emulator;
 | 
			
		||||
mod gdbserver;
 | 
			
		||||
mod graphics;
 | 
			
		||||
mod input;
 | 
			
		||||
mod persistence;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +1,14 @@
 | 
			
		|||
pub use about::AboutWindow;
 | 
			
		||||
use egui::{Context, ViewportBuilder, ViewportId};
 | 
			
		||||
pub use game::GameWindow;
 | 
			
		||||
pub use gdb::GdbServerWindow;
 | 
			
		||||
pub use input::InputWindow;
 | 
			
		||||
use winit::event::KeyEvent;
 | 
			
		||||
 | 
			
		||||
mod about;
 | 
			
		||||
mod game;
 | 
			
		||||
mod game_screen;
 | 
			
		||||
mod gdb;
 | 
			
		||||
mod input;
 | 
			
		||||
 | 
			
		||||
pub trait AppWindow {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -121,6 +121,14 @@ impl GameWindow {
 | 
			
		|||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        ui.menu_button("Tools", |ui| {
 | 
			
		||||
            if ui.button("GDB Server").clicked() {
 | 
			
		||||
                self.proxy
 | 
			
		||||
                    .send_event(UserEvent::OpenDebugger(self.sim_id))
 | 
			
		||||
                    .unwrap();
 | 
			
		||||
                ui.close_menu();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        ui.menu_button("About", |ui| {
 | 
			
		||||
            self.proxy.send_event(UserEvent::OpenAbout).unwrap();
 | 
			
		||||
            ui.close_menu();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,80 @@
 | 
			
		|||
use egui::{Button, CentralPanel, TextEdit, ViewportBuilder, ViewportId};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    emulator::{EmulatorClient, SimId},
 | 
			
		||||
    gdbserver::{GdbServer, GdbServerStatus},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::AppWindow;
 | 
			
		||||
 | 
			
		||||
pub struct GdbServerWindow {
 | 
			
		||||
    sim_id: SimId,
 | 
			
		||||
    port_str: String,
 | 
			
		||||
    server: GdbServer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl GdbServerWindow {
 | 
			
		||||
    pub fn new(sim_id: SimId, client: EmulatorClient) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            sim_id,
 | 
			
		||||
            port_str: (8080 + sim_id.to_index()).to_string(),
 | 
			
		||||
            server: GdbServer::new(sim_id, client),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AppWindow for GdbServerWindow {
 | 
			
		||||
    fn viewport_id(&self) -> ViewportId {
 | 
			
		||||
        ViewportId::from_hash_of(format!("Debugger-{}", self.sim_id))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn initial_viewport(&self) -> ViewportBuilder {
 | 
			
		||||
        ViewportBuilder::default()
 | 
			
		||||
            .with_title(format!("GDB Server ({})", self.sim_id))
 | 
			
		||||
            .with_inner_size((300.0, 200.0))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn show(&mut self, ctx: &egui::Context) {
 | 
			
		||||
        let port_num: Option<u16> = self.port_str.parse().ok();
 | 
			
		||||
        let status = self.server.status();
 | 
			
		||||
        CentralPanel::default().show(ctx, |ui| {
 | 
			
		||||
            ui.horizontal(|ui| {
 | 
			
		||||
                if port_num.is_none() {
 | 
			
		||||
                    let style = ui.style_mut();
 | 
			
		||||
                    let error = style.visuals.error_fg_color;
 | 
			
		||||
                    style.visuals.widgets.active.bg_stroke.color = error;
 | 
			
		||||
                    style.visuals.widgets.hovered.bg_stroke.color = error;
 | 
			
		||||
                }
 | 
			
		||||
                ui.label("Port");
 | 
			
		||||
                let port_editor = TextEdit::singleline(&mut self.port_str).desired_width(100.0);
 | 
			
		||||
                ui.add_enabled(!status.running(), port_editor);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if !status.running() {
 | 
			
		||||
                let start_button = Button::new("Start");
 | 
			
		||||
                if ui.add_enabled(port_num.is_some(), start_button).clicked() {
 | 
			
		||||
                    let port = port_num.unwrap();
 | 
			
		||||
                    self.server.start(port);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                let stop_button = Button::new("Stop");
 | 
			
		||||
                if ui.add(stop_button).clicked() {
 | 
			
		||||
                    self.server.stop();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            match &status {
 | 
			
		||||
                GdbServerStatus::Stopped => {}
 | 
			
		||||
                GdbServerStatus::Connecting => {
 | 
			
		||||
                    ui.label("Connecting...");
 | 
			
		||||
                }
 | 
			
		||||
                GdbServerStatus::Running => {
 | 
			
		||||
                    ui.label("Running");
 | 
			
		||||
                }
 | 
			
		||||
                GdbServerStatus::Error(message) => {
 | 
			
		||||
                    ui.label(message);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue