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 }