mod.rs - icy_draw - [fork] icy_draw is the successor to mystic draw.
 (HTM) git clone https://git.drkhsh.at/icy_draw.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
       ---
       mod.rs (17215B)
       ---
            1 #![allow(unsafe_code, clippy::wildcard_imports)]
            2 
            3 use chrono::Utc;
            4 use egui::Vec2;
            5 use egui_bind::BindTarget;
            6 use i18n_embed_fl::fl;
            7 use icy_engine::{BufferParser, Caret, Position};
            8 use icy_engine_gui::BufferView;
            9 use icy_net::protocol::TransferProtocolType;
           10 use icy_net::telnet::TerminalEmulation;
           11 use std::mem;
           12 use std::path::PathBuf;
           13 use std::sync::Arc;
           14 use std::thread::{sleep, JoinHandle};
           15 use std::time::Instant;
           16 use tokio::runtime::Runtime;
           17 use tokio::sync::mpsc;
           18 
           19 use eframe::egui::Key;
           20 
           21 use crate::features::AutoLogin;
           22 use crate::ui::connect::OpenConnectionData;
           23 use crate::{get_parser, get_unicode_converter, Options};
           24 
           25 pub mod app;
           26 pub mod com_thread;
           27 pub mod connect;
           28 
           29 pub mod terminal_window;
           30 
           31 pub mod util;
           32 pub use util::*;
           33 
           34 use self::connect::SendData;
           35 use self::terminal_thread::TerminalThread;
           36 
           37 pub mod dialogs;
           38 
           39 pub mod terminal_thread;
           40 
           41 #[macro_export]
           42 macro_rules! check_error {
           43     ($main_window: expr, $res: expr, $terminate_connection: expr) => {{
           44         /*   if let Err(err) = $res {
           45             log::error!("{err}");
           46             $main_window.output_string(format!("\n\r{err}\n\r").as_str());
           47 
           48             if $terminate_connection {
           49                 if let Some(con) = $main_window.buffer_update_thread.lock().connection.lock().as_mut() {
           50                     con.disconnect().unwrap_or_default();
           51                 }
           52             }
           53         }*/
           54     }};
           55 }
           56 
           57 #[derive(Clone, PartialEq, Eq, Default, Debug)]
           58 pub enum MainWindowMode {
           59     ShowTerminal,
           60     #[default]
           61     ShowDialingDirectory,
           62     ///Shows settings - parameter: show dialing_directory
           63     ShowSettings,
           64     SelectProtocol(bool),
           65     FileTransfer(bool),
           66     DeleteSelectedAddress(usize),
           67     ShowCaptureDialog,
           68     ShowExportDialog,
           69     ShowUploadDialog,
           70     ShowIEMSI,
           71     ShowDisconnectedMessage(String, String),
           72 }
           73 
           74 #[derive(Default)]
           75 pub struct MainWindowState {
           76     pub mode: MainWindowMode,
           77     pub options: Options,
           78 
           79     pub settings_dialog: dialogs::settings_dialog::DialogState,
           80 
           81     // don't store files in unit test mode
           82     #[cfg(test)]
           83     pub options_written: bool,
           84 }
           85 
           86 impl MainWindowState {
           87     #[cfg(test)]
           88     pub fn store_options(&mut self) {
           89         self.options_written = true;
           90     }
           91 
           92     #[cfg(not(test))]
           93     pub fn store_options(&mut self) {
           94         if let Err(err) = self.options.store_options() {
           95             log::error!("{err}");
           96         }
           97     }
           98 }
           99 
          100 pub struct MainWindow {
          101     buffer_view: Arc<eframe::epaint::mutex::Mutex<BufferView>>,
          102 
          103     pub state: MainWindowState,
          104 
          105     screen_mode: ScreenMode,
          106     is_fullscreen_mode: bool,
          107     drag_start: Option<Vec2>,
          108     last_pos: Position,
          109     shift_pressed_during_selection: bool,
          110     use_rip: bool,
          111 
          112     terminal_thread: Arc<egui::mutex::Mutex<TerminalThread>>,
          113     terminal_thread_handle: Option<JoinHandle<()>>,
          114     pub tx: mpsc::Sender<SendData>,
          115     pub rx: mpsc::Receiver<SendData>,
          116 
          117     pub initial_upload_directory: Option<PathBuf>,
          118     // protocols
          119     // pub current_file_transfer: Option<FileTransferThread>,
          120     pub dialing_directory_dialog: dialogs::dialing_directory_dialog::DialogState,
          121     pub export_dialog: dialogs::export_dialog::DialogState,
          122     pub upload_dialog: dialogs::upload_dialog::DialogState,
          123 
          124     pub show_find_dialog: bool,
          125     pub find_dialog: dialogs::find_dialog::DialogState,
          126     show_disconnect: bool,
          127     title: String,
          128     buffer_parser: Box<dyn BufferParser>,
          129     #[cfg(target_arch = "wasm32")]
          130     poll_thread: com_thread::ConnectionThreadData,
          131 }
          132 
          133 impl MainWindow {
          134     pub fn get_options(&self) -> &Options {
          135         &self.state.options
          136     }
          137 
          138     pub fn get_mode(&self) -> MainWindowMode {
          139         self.state.mode.clone()
          140     }
          141 
          142     pub fn set_mode(&mut self, ctx: &egui::Context, mode: MainWindowMode) {
          143         if self.state.mode == mode {
          144             return;
          145         }
          146         self.state.mode = mode;
          147         ctx.request_repaint()
          148     }
          149 
          150     pub fn println(&mut self, str: &str) {
          151         for ch in str.chars() {
          152             if ch as u32 > 255 {
          153                 continue;
          154             }
          155             self.print_char(ch as u8);
          156         }
          157     }
          158 
          159     pub fn output_char(&mut self, ch: char) {
          160         let translated_char = self.buffer_view.lock().get_unicode_converter().convert_from_unicode(ch, 0);
          161         if self.terminal_thread.lock().is_connected {
          162             self.send_vec(vec![translated_char as u8]);
          163         } else {
          164             self.print_char(translated_char as u8);
          165         }
          166     }
          167 
          168     pub fn output_string(&mut self, str: &str) {
          169         if self.terminal_thread.lock().is_connected {
          170             let mut v = Vec::new();
          171             for ch in str.chars() {
          172                 let translated_char = self.buffer_view.lock().get_unicode_converter().convert_from_unicode(ch, 0);
          173                 v.push(translated_char as u8);
          174             }
          175             self.send_vec(v);
          176         } else {
          177             for ch in str.chars() {
          178                 let translated_char = self.buffer_view.lock().get_unicode_converter().convert_from_unicode(ch, 0);
          179                 self.print_char(translated_char as u8);
          180             }
          181         }
          182     }
          183 
          184     pub fn print_char(&mut self, c: u8) {
          185         let buffer_view = &mut self.buffer_view.lock();
          186         buffer_view.get_edit_state_mut().set_is_buffer_dirty();
          187         let mut caret = Caret::default();
          188         mem::swap(&mut caret, buffer_view.get_caret_mut());
          189         let _ = self.buffer_parser.print_char(buffer_view.get_buffer_mut(), 0, &mut caret, c as char);
          190         mem::swap(&mut caret, buffer_view.get_caret_mut());
          191     }
          192 
          193     #[cfg(target_arch = "wasm32")]
          194 
          195     fn start_file_transfer(&mut self, protocol_type: crate::protocol::TransferType, download: bool, files_opt: Option<Vec<FileDescriptor>>) {
          196         // TODO
          197     }
          198 
          199     #[cfg(not(target_arch = "wasm32"))]
          200     fn upload(&mut self, ctx: &egui::Context, protocol_type: TransferProtocolType, files: Vec<PathBuf>) {
          201         use icy_net::protocol::TransferState;
          202         self.terminal_thread.lock().current_transfer = TransferState::new(String::new());
          203 
          204         self.set_mode(ctx, MainWindowMode::FileTransfer(false));
          205         self.send_data(SendData::Upload(protocol_type, files));
          206 
          207         //        check_error!(self, r, false);
          208     }
          209 
          210     fn download(&mut self, ctx: &egui::Context, protocol_type: TransferProtocolType, file_name: Option<String>) {
          211         use icy_net::protocol::TransferState;
          212         self.terminal_thread.lock().current_transfer = TransferState::new(String::new());
          213 
          214         self.set_mode(ctx, MainWindowMode::FileTransfer(true));
          215         self.send_data(SendData::Download(protocol_type, file_name));
          216 
          217         //let r = self.tx.send(SendData::Download(protocol_type));
          218         check_error!(self, r, false);
          219     }
          220 
          221     pub(crate) fn initiate_file_transfer(&mut self, ctx: &egui::Context, protocol_type: TransferProtocolType, download: bool, file_name: Option<String>) {
          222         self.set_mode(ctx, MainWindowMode::ShowTerminal);
          223         if download {
          224             self.download(ctx, protocol_type, file_name);
          225         } else {
          226             self.init_upload_dialog(ctx, protocol_type);
          227         }
          228     }
          229 
          230     pub fn set_screen_mode(&mut self, mode: ScreenMode) {
          231         self.screen_mode = mode;
          232         mode.set_mode(self);
          233     }
          234 
          235     pub fn call_bbs_uuid(&mut self, ctx: &egui::Context, uuid: Option<usize>) {
          236         if uuid.is_none() {
          237             self.call_bbs(ctx, 0);
          238             return;
          239         }
          240 
          241         let uuid = uuid.unwrap();
          242         for (i, adr) in self.dialing_directory_dialog.addresses.addresses.iter().enumerate() {
          243             if adr.id == uuid {
          244                 self.call_bbs(ctx, i);
          245                 return;
          246             }
          247         }
          248     }
          249 
          250     pub fn call_bbs(&mut self, ctx: &egui::Context, i: usize) {
          251         self.set_mode(ctx, MainWindowMode::ShowTerminal);
          252         let cloned_addr = self.dialing_directory_dialog.addresses.addresses[i].clone();
          253         let iemsi_auto_login = self.get_options().iemsi.autologin;
          254 
          255         {
          256             let address = &mut self.dialing_directory_dialog.addresses.addresses[i];
          257             let mut adr = address.address.clone();
          258             if !adr.contains(':') {
          259                 adr.push_str(":23");
          260             }
          261             address.number_of_calls += 1;
          262             address.last_call = Some(Utc::now());
          263 
          264             let (user_name, password) = if address.override_iemsi_settings {
          265                 (address.iemsi_user.clone(), address.iemsi_password.clone())
          266             } else {
          267                 (address.user_name.clone(), address.password.clone())
          268             };
          269             self.terminal_thread.lock().auto_login = if user_name.is_empty() || password.is_empty() || !iemsi_auto_login {
          270                 None
          271             } else {
          272                 Some(AutoLogin::new(&cloned_addr.auto_login, user_name, password))
          273             };
          274 
          275             if let Some(rip_cache) = address.get_rip_cache() {
          276                 self.terminal_thread.lock().cache_directory = rip_cache;
          277             }
          278 
          279             self.use_rip = matches!(address.terminal_type, TerminalEmulation::Rip);
          280             self.terminal_thread.lock().terminal_type = Some((address.terminal_type, address.ansi_music));
          281             self.buffer_view.lock().clear_reference_image();
          282             self.buffer_view.lock().get_buffer_mut().layers[0].clear();
          283             self.buffer_view.lock().get_buffer_mut().stop_sixel_threads();
          284 
          285             self.dialing_directory_dialog.cur_addr = i;
          286             let converter = get_unicode_converter(&address.terminal_type);
          287 
          288             self.buffer_view.lock().set_unicode_converter(converter);
          289             self.buffer_view.lock().get_buffer_mut().terminal_state.set_baud_rate(address.baud_emulation);
          290 
          291             self.buffer_view.lock().redraw_font();
          292             self.buffer_view.lock().redraw_view();
          293             self.buffer_view.lock().clear();
          294         }
          295         self.set_screen_mode(cloned_addr.screen_mode);
          296         let _r = self.dialing_directory_dialog.addresses.store_phone_book();
          297         check_error!(self, r, false);
          298 
          299         self.println(&fl!(crate::LANGUAGE_LOADER, "connect-to", address = cloned_addr.address.clone()));
          300 
          301         let timeout = self.get_options().connect_timeout;
          302         let window_size = self.screen_mode.get_window_size();
          303 
          304         let data = OpenConnectionData::from(&cloned_addr, timeout, window_size, Some(self.get_options().modem.clone()));
          305 
          306         if let Some(_handle) = self.terminal_thread_handle.take() {
          307             self.send_data(SendData::Disconnect);
          308         }
          309         self.buffer_parser = get_parser(&data.term_caps.terminal, data.use_ansi_music, PathBuf::new());
          310         let (update_thread_handle, tx, rx) = crate::ui::terminal_thread::start_update_thread(ctx, data, self.terminal_thread.clone());
          311         self.terminal_thread_handle = Some(update_thread_handle);
          312         self.tx = tx;
          313         self.rx = rx;
          314         self.send_data(SendData::SetBaudRate(cloned_addr.baud_emulation.get_baud_rate()));
          315     }
          316 
          317     pub fn send_data(&mut self, data: SendData) {
          318         let rt = Runtime::new().unwrap();
          319         rt.block_on(async move {
          320             let _res = self.tx.send(data).await;
          321         });
          322     }
          323 
          324     pub fn hangup(&mut self, ctx: &egui::Context) {
          325         self.send_data(SendData::Disconnect);
          326         self.terminal_thread_handle = None;
          327         self.terminal_thread.lock().sound_thread.lock().clear();
          328         self.set_mode(ctx, MainWindowMode::ShowDialingDirectory);
          329     }
          330 
          331     pub fn send_login(&mut self) {
          332         let user_name = self
          333             .dialing_directory_dialog
          334             .addresses
          335             .addresses
          336             .get(self.dialing_directory_dialog.cur_addr)
          337             .unwrap()
          338             .user_name
          339             .clone();
          340         let password = self
          341             .dialing_directory_dialog
          342             .addresses
          343             .addresses
          344             .get(self.dialing_directory_dialog.cur_addr)
          345             .unwrap()
          346             .password
          347             .clone();
          348         let mut cr: Vec<u8> = [self.buffer_view.lock().get_unicode_converter().convert_from_unicode('\r', 0) as u8].to_vec();
          349         for (k, v) in self.screen_mode.get_input_mode().cur_map() {
          350             if *k == Key::Enter as u32 {
          351                 cr = v.to_vec();
          352                 break;
          353             }
          354         }
          355 
          356         self.output_string(&user_name);
          357         self.send_vec(cr.clone());
          358         sleep(std::time::Duration::from_millis(350));
          359         self.output_string(&password);
          360         self.send_vec(cr);
          361     }
          362 
          363     #[cfg(not(target_arch = "wasm32"))]
          364     pub fn update_title(&mut self, ctx: &egui::Context) {
          365         let title = if let MainWindowMode::ShowDialingDirectory = self.get_mode() {
          366             crate::DEFAULT_TITLE.to_string()
          367         } else {
          368             let d = Instant::now().duration_since(self.terminal_thread.lock().connection_time);
          369             let sec = d.as_secs();
          370             let minutes = sec / 60;
          371             let hours = minutes / 60;
          372             let cur = &self.dialing_directory_dialog.addresses.addresses[self.dialing_directory_dialog.cur_addr];
          373             let connection_time = format!("{:02}:{:02}:{:02}", hours, minutes % 60, sec % 60);
          374             let system_name = if cur.system_name.is_empty() {
          375                 cur.address.clone()
          376             } else {
          377                 cur.system_name.clone()
          378             };
          379 
          380             let is_connected = self.terminal_thread.lock().is_connected;
          381             let title = if is_connected {
          382                 self.show_disconnect = true;
          383                 fl!(
          384                     crate::LANGUAGE_LOADER,
          385                     "title-connected",
          386                     version = crate::VERSION.to_string(),
          387                     time = connection_time.clone(),
          388                     name = system_name.clone()
          389                 )
          390             } else {
          391                 fl!(crate::LANGUAGE_LOADER, "title-offline", version = crate::VERSION.to_string())
          392             };
          393             if self.show_disconnect && !is_connected {
          394                 self.show_disconnect = false;
          395                 self.set_mode(ctx, MainWindowMode::ShowDisconnectedMessage(system_name.clone(), connection_time.clone()));
          396                 self.output_string("\nNO CARRIER\n");
          397             }
          398             title
          399         };
          400 
          401         if self.title != title {
          402             self.title = title.clone();
          403             ctx.send_viewport_cmd(egui::ViewportCommand::Title(title));
          404         }
          405     }
          406 
          407     fn handle_terminal_key_binds(&mut self, ctx: &egui::Context) {
          408         if self.get_options().bind.clear_screen.pressed(ctx) {
          409             ctx.input_mut(|i| i.events.clear());
          410             self.buffer_view.lock().clear_buffer_screen();
          411         }
          412         if self.get_options().bind.dialing_directory.pressed(ctx) {
          413             ctx.input_mut(|i| i.events.clear());
          414             self.set_mode(ctx, MainWindowMode::ShowDialingDirectory);
          415         }
          416         if self.get_options().bind.hangup.pressed(ctx) {
          417             ctx.input_mut(|i| i.events.clear());
          418             self.hangup(ctx);
          419         }
          420         if self.get_options().bind.send_login_pw.pressed(ctx) {
          421             ctx.input_mut(|i| i.events.clear());
          422             self.send_login();
          423         }
          424         if self.get_options().bind.show_settings.pressed(ctx) {
          425             ctx.input_mut(|i| i.events.clear());
          426             self.set_mode(ctx, MainWindowMode::ShowSettings);
          427         }
          428         if self.get_options().bind.show_capture.pressed(ctx) {
          429             ctx.input_mut(|i| i.events.clear());
          430             self.set_mode(ctx, MainWindowMode::ShowCaptureDialog);
          431         }
          432         if self.get_options().bind.quit.pressed(ctx) {
          433             ctx.input_mut(|i| i.events.clear());
          434             #[cfg(not(target_arch = "wasm32"))]
          435             ctx.send_viewport_cmd(egui::ViewportCommand::Close);
          436         }
          437         if self.get_options().bind.full_screen.pressed(ctx) {
          438             ctx.input_mut(|i| i.events.clear());
          439             self.is_fullscreen_mode = !self.is_fullscreen_mode;
          440             #[cfg(not(target_arch = "wasm32"))]
          441             ctx.send_viewport_cmd(egui::ViewportCommand::Fullscreen(self.is_fullscreen_mode));
          442         }
          443         if self.get_options().bind.upload.pressed(ctx) {
          444             ctx.input_mut(|i| i.events.clear());
          445             self.set_mode(ctx, MainWindowMode::SelectProtocol(false));
          446         }
          447         if self.get_options().bind.download.pressed(ctx) {
          448             ctx.input_mut(|i| i.events.clear());
          449             self.set_mode(ctx, MainWindowMode::SelectProtocol(true));
          450         }
          451 
          452         if self.get_options().bind.show_find.pressed(ctx) {
          453             ctx.input_mut(|i| i.events.clear());
          454             self.show_find_dialog = true;
          455             let lock = &mut self.buffer_view.lock();
          456             let (buffer, _, parser) = lock.get_edit_state_mut().get_buffer_and_caret_mut();
          457             self.find_dialog.search_pattern(buffer, (*parser).as_ref());
          458             self.find_dialog.update_pattern(lock);
          459         }
          460     }
          461 
          462     fn send_vec(&mut self, to_vec: Vec<u8>) {
          463         if !self.terminal_thread.lock().is_connected {
          464             return;
          465         }
          466         self.send_data(SendData::Data(to_vec));
          467 
          468         /*
          469         if let Err(err) = self.tx.send(SendData::Data(to_vec)) {
          470             self.buffer_update_thread.lock().is_connected = false;
          471             log::error!("{err}");
          472             self.output_string(&format!("\n{err}"));
          473         }*/
          474     }
          475 }
          476 
          477 pub fn button_tint(ui: &egui::Ui) -> egui::Color32 {
          478     ui.visuals().widgets.active.fg_stroke.color
          479 }