1use crossterm::event::{Event as CEvent, KeyCode, KeyEventKind, KeyModifiers};
7use tokio::sync::mpsc;
8
9use crate::state::{AppState, Focus, PackageItem, PkgbuildCheckRequest, QueryInput};
10
11mod distro;
12mod global;
13mod install;
15mod modals;
16mod mouse;
17mod preflight;
18mod recent;
20mod search;
21pub mod utils;
23
24pub use search::open_preflight_modal;
26
27pub use preflight::start_execution;
29
30pub fn try_interactive_auth_handoff() -> Result<bool, String> {
51 let tool = crate::logic::privilege::active_tool()?;
52
53 crate::app::terminal::restore_terminal()
54 .map_err(|e| format!("Failed to restore terminal for interactive auth: {e}"))?;
55
56 let auth_result = crate::logic::privilege::run_interactive_auth(tool);
57
58 if let Err(e) = crate::app::terminal::setup_terminal() {
59 tracing::error!(error = %e, "Failed to re-setup terminal after interactive auth");
60 return Err(format!("Failed to re-setup terminal: {e}"));
61 }
62
63 auth_result
64}
65
66pub fn spawn_downgrade_in_terminal(app: &mut AppState, items: &[PackageItem]) -> bool {
80 let names: Vec<String> = items.iter().map(|p| p.name.clone()).collect();
81 let joined = names.join(" ");
82
83 let tool = match crate::logic::privilege::active_tool() {
84 Ok(t) => t,
85 Err(msg) => {
86 app.modal = crate::state::Modal::Alert { message: msg };
87 return true;
88 }
89 };
90
91 let downgrade_cmd =
92 crate::logic::privilege::build_privilege_command(tool, &format!("downgrade {joined}"));
93 let cmd = if app.dry_run {
94 let quoted = crate::install::shell_single_quote(&downgrade_cmd);
95 format!("echo DRY RUN: {quoted}")
96 } else {
97 format!(
98 "if (command -v downgrade >/dev/null 2>&1) || pacman -Qi downgrade >/dev/null 2>&1; then {downgrade_cmd}; else echo 'downgrade tool not found. Install \"downgrade\" package.'; fi"
99 )
100 };
101
102 app.downgrade_list.clear();
103 app.downgrade_list_names.clear();
104 app.downgrade_state.select(None);
105
106 crate::install::spawn_shell_commands_in_terminal(&[cmd]);
107
108 app.toast_message = Some(crate::i18n::t(app, "app.toasts.downgrade_started"));
109 app.toast_expires_at = Some(std::time::Instant::now() + std::time::Duration::from_secs(3));
110
111 true
112}
113
114#[allow(clippy::too_many_arguments)]
136pub fn handle_event(
137 ev: &CEvent,
138 app: &mut AppState,
139 query_tx: &mpsc::UnboundedSender<QueryInput>,
140 details_tx: &mpsc::UnboundedSender<PackageItem>,
141 preview_tx: &mpsc::UnboundedSender<PackageItem>,
142 add_tx: &mpsc::UnboundedSender<PackageItem>,
143 pkgb_tx: &mpsc::UnboundedSender<PackageItem>,
144 comments_tx: &mpsc::UnboundedSender<String>,
145 pkgb_check_tx: &mpsc::UnboundedSender<PkgbuildCheckRequest>,
146) -> bool {
147 handle_event_with_pkgbuild_checks(
148 ev,
149 app,
150 query_tx,
151 details_tx,
152 preview_tx,
153 add_tx,
154 pkgb_tx,
155 comments_tx,
156 pkgb_check_tx,
157 )
158}
159
160#[allow(clippy::too_many_arguments)]
161pub fn handle_event_with_pkgbuild_checks(
163 ev: &CEvent,
164 app: &mut AppState,
165 query_tx: &mpsc::UnboundedSender<QueryInput>,
166 details_tx: &mpsc::UnboundedSender<PackageItem>,
167 preview_tx: &mpsc::UnboundedSender<PackageItem>,
168 add_tx: &mpsc::UnboundedSender<PackageItem>,
169 pkgb_tx: &mpsc::UnboundedSender<PackageItem>,
170 comments_tx: &mpsc::UnboundedSender<String>,
171 pkgb_check_tx: &mpsc::UnboundedSender<PkgbuildCheckRequest>,
172) -> bool {
173 if let CEvent::Key(ke) = ev {
174 if ke.kind != KeyEventKind::Press {
175 return false;
176 }
177
178 if ke.code == KeyCode::Char('t') && ke.modifiers.contains(KeyModifiers::CONTROL) {
180 tracing::debug!(
181 "[Event] Ctrl+T key event: code={:?}, mods={:?}, modal={:?}, focus={:?}",
182 ke.code,
183 ke.modifiers,
184 app.modal,
185 app.focus
186 );
187 }
188
189 if let Some(should_exit) = global::handle_global_key(
192 *ke,
193 app,
194 details_tx,
195 pkgb_tx,
196 comments_tx,
197 query_tx,
198 pkgb_check_tx,
199 ) {
200 if ke.code == KeyCode::Char('t') && ke.modifiers.contains(KeyModifiers::CONTROL) {
201 tracing::debug!(
202 "[Event] Global handler returned should_exit={}",
203 should_exit
204 );
205 }
206 if should_exit {
207 return true; }
209 return false;
211 }
212
213 if ke.code == KeyCode::Char('t') && ke.modifiers.contains(KeyModifiers::CONTROL) {
215 tracing::warn!(
216 "[Event] Ctrl+T was NOT handled by global handler, continuing to other handlers"
217 );
218 }
219
220 if matches!(app.modal, crate::state::Modal::Preflight { .. }) {
222 return preflight::handle_preflight_key(*ke, app);
223 }
224
225 if modals::handle_modal_key(*ke, app, add_tx) {
227 return false;
228 }
229
230 if !matches!(app.modal, crate::state::Modal::None) {
232 return false;
233 }
234
235 if matches!(app.focus, Focus::Recent) {
238 let should_exit =
239 recent::handle_recent_key(*ke, app, query_tx, details_tx, preview_tx, add_tx);
240 return should_exit;
241 }
242
243 if matches!(app.focus, Focus::Install) {
245 let should_exit = install::handle_install_key(*ke, app, details_tx, preview_tx, add_tx);
246 return should_exit;
247 }
248
249 if matches!(app.focus, Focus::Search) {
251 let should_exit = search::handle_search_key(
252 *ke,
253 app,
254 query_tx,
255 details_tx,
256 add_tx,
257 preview_tx,
258 comments_tx,
259 );
260 return should_exit;
261 }
262
263 return false;
265 }
266
267 if let CEvent::Mouse(m) = ev {
269 return mouse::handle_mouse_event_with_pkgbuild_checks(
270 *m,
271 app,
272 details_tx,
273 preview_tx,
274 add_tx,
275 pkgb_tx,
276 comments_tx,
277 query_tx,
278 pkgb_check_tx,
279 );
280 }
281 false
282}
283
284#[cfg(all(test, not(target_os = "windows")))]
285mod tests {
286 use super::*;
287 use crossterm::event::{
288 Event as CEvent, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseButton, MouseEvent,
289 MouseEventKind,
290 };
291 use std::fs;
292 use std::os::unix::fs::PermissionsExt;
293 use std::path::PathBuf;
294
295 #[test]
296 fn ui_options_update_system_enter_triggers_xfce4_args_shape() {
307 let _guard = crate::global_test_mutex_lock();
308 let mut dir: PathBuf = std::env::temp_dir();
310 dir.push(format!(
311 "pacsea_test_term_{}_{}",
312 std::process::id(),
313 std::time::SystemTime::now()
314 .duration_since(std::time::UNIX_EPOCH)
315 .expect("System time is before UNIX epoch")
316 .as_nanos()
317 ));
318 fs::create_dir_all(&dir).expect("create test directory");
319 let mut out_path = dir.clone();
320 out_path.push("args.txt");
321 let mut term_path = dir.clone();
322 term_path.push("xfce4-terminal");
323 let script = "#!/bin/sh\n: > \"$PACSEA_TEST_OUT\"\nfor a in \"$@\"; do printf '%s\n' \"$a\" >> \"$PACSEA_TEST_OUT\"; done\n";
324 fs::write(&term_path, script.as_bytes()).expect("Failed to write test terminal script");
325 let mut perms = fs::metadata(&term_path)
326 .expect("Failed to read test terminal script metadata")
327 .permissions();
328 perms.set_mode(0o755);
329 fs::set_permissions(&term_path, perms)
330 .expect("Failed to set test terminal script permissions");
331 let orig_path = std::env::var_os("PATH");
332 let combined_path = std::env::var("PATH").map_or_else(
334 |_| dir.display().to_string(),
335 |p| format!("{}:{p}", dir.display()),
336 );
337 unsafe {
338 std::env::set_var("PATH", combined_path);
339 std::env::set_var("PACSEA_TEST_OUT", out_path.display().to_string());
340 std::env::set_var("PACSEA_TEST_HEADLESS", "1");
341 }
342
343 let mut app = AppState::default();
344 let (qtx, _qrx) = mpsc::unbounded_channel();
345 let (dtx, _drx) = mpsc::unbounded_channel();
346 let (ptx, _prx) = mpsc::unbounded_channel();
347 let (atx, _arx) = mpsc::unbounded_channel();
348 let (pkgb_tx, _pkgb_rx) = mpsc::unbounded_channel();
349 let (pkgb_check_tx, _pkgb_check_rx) = mpsc::unbounded_channel::<PkgbuildCheckRequest>();
350 app.options_button_rect = Some((5, 5, 10, 1));
351 let click_options = CEvent::Mouse(MouseEvent {
352 kind: MouseEventKind::Down(MouseButton::Left),
353 column: 6,
354 row: 5,
355 modifiers: KeyModifiers::empty(),
356 });
357 let (comments_tx, _comments_rx) = mpsc::unbounded_channel::<String>();
358 let _ = super::handle_event(
359 &click_options,
360 &mut app,
361 &qtx,
362 &dtx,
363 &ptx,
364 &atx,
365 &pkgb_tx,
366 &comments_tx,
367 &pkgb_check_tx,
368 );
369 assert!(app.options_menu_open);
370 app.options_menu_rect = Some((5, 6, 20, 3));
371 let click_menu_update = CEvent::Mouse(MouseEvent {
372 kind: MouseEventKind::Down(MouseButton::Left),
373 column: 6,
374 row: 7,
375 modifiers: KeyModifiers::empty(),
376 });
377 let (comments_tx, _comments_rx) = mpsc::unbounded_channel::<String>();
378 let _ = super::handle_event(
379 &click_menu_update,
380 &mut app,
381 &qtx,
382 &dtx,
383 &ptx,
384 &atx,
385 &pkgb_tx,
386 &comments_tx,
387 &pkgb_check_tx,
388 );
389 let enter = CEvent::Key(KeyEvent::new(KeyCode::Enter, KeyModifiers::empty()));
390 let (comments_tx, _comments_rx) = mpsc::unbounded_channel::<String>();
391 let _ = super::handle_event(
392 &enter,
393 &mut app,
394 &qtx,
395 &dtx,
396 &ptx,
397 &atx,
398 &pkgb_tx,
399 &comments_tx,
400 &pkgb_check_tx,
401 );
402 let mut attempts = 0;
404 while !out_path.exists() && attempts < 50 {
405 std::thread::sleep(std::time::Duration::from_millis(10));
406 attempts += 1;
407 }
408 std::thread::sleep(std::time::Duration::from_millis(100));
410 let body = fs::read_to_string(&out_path).expect("fake terminal args file written");
411 let lines: Vec<&str> = body.lines().collect();
412 let command_idx = lines.iter().rposition(|&l| l == "--command");
416 if command_idx.is_none() {
417 eprintln!(
420 "Warning: xfce4-terminal was not used (no --command found, got: {lines:?}), skipping xfce4-specific assertion"
421 );
422 unsafe {
423 if let Some(v) = orig_path {
424 std::env::set_var("PATH", v);
425 } else {
426 std::env::remove_var("PATH");
427 }
428 std::env::remove_var("PACSEA_TEST_OUT");
429 }
430 return;
431 }
432 let command_idx = command_idx.expect("command_idx should be Some after is_none() check");
433 assert!(
434 command_idx + 1 < lines.len(),
435 "--command found at index {command_idx} but no following argument. Lines: {lines:?}"
436 );
437 assert!(
438 lines[command_idx + 1].starts_with("bash -lc "),
439 "Expected argument after --command to start with 'bash -lc ', got: '{}'. All lines: {:?}",
440 lines[command_idx + 1],
441 lines
442 );
443 unsafe {
444 if let Some(v) = orig_path {
445 std::env::set_var("PATH", v);
446 } else {
447 std::env::remove_var("PATH");
448 }
449 std::env::remove_var("PACSEA_TEST_OUT");
450 }
451 }
452
453 #[test]
454 fn optional_deps_rows_reflect_installed_and_x11_and_reflector() {
465 let _guard = crate::global_test_mutex_lock();
466 let (dir, orig_path, orig_wl) = setup_test_executables();
467 let (mut app, channels) = setup_app_with_translations();
468 open_optional_deps_modal(&mut app, &channels);
469
470 verify_optional_deps_rows(&app.modal);
471 teardown_test_environment(orig_path, orig_wl, &dir);
472 }
473
474 fn setup_test_executables() -> (
484 std::path::PathBuf,
485 Option<std::ffi::OsString>,
486 Option<std::ffi::OsString>,
487 ) {
488 use std::fs;
489 use std::os::unix::fs::PermissionsExt;
490 use std::path::PathBuf;
491
492 let mut dir: PathBuf = std::env::temp_dir();
493 dir.push(format!(
494 "pacsea_test_optional_deps_{}_{}",
495 std::process::id(),
496 std::time::SystemTime::now()
497 .duration_since(std::time::UNIX_EPOCH)
498 .expect("System time is before UNIX epoch")
499 .as_nanos()
500 ));
501 let _ = fs::create_dir_all(&dir);
502
503 let make_exec = |name: &str| {
504 let mut p = dir.clone();
505 p.push(name);
506 fs::write(&p, b"#!/bin/sh\nexit 0\n").expect("Failed to write test executable stub");
507 let mut perms = fs::metadata(&p)
508 .expect("Failed to read test executable stub metadata")
509 .permissions();
510 perms.set_mode(0o755);
511 fs::set_permissions(&p, perms).expect("Failed to set test executable stub permissions");
512 };
513
514 make_exec("nvim");
515 make_exec("kitty");
516
517 let orig_path = std::env::var_os("PATH");
518 unsafe {
519 std::env::set_var("PATH", dir.display().to_string());
520 std::env::set_var("PACSEA_TEST_HEADLESS", "1");
521 };
522 let orig_wl = std::env::var_os("WAYLAND_DISPLAY");
523 unsafe { std::env::remove_var("WAYLAND_DISPLAY") };
524 (dir, orig_path, orig_wl)
525 }
526
527 type AppChannels = (
531 tokio::sync::mpsc::UnboundedSender<QueryInput>,
532 tokio::sync::mpsc::UnboundedSender<PackageItem>,
533 tokio::sync::mpsc::UnboundedSender<PackageItem>,
534 tokio::sync::mpsc::UnboundedSender<PackageItem>,
535 tokio::sync::mpsc::UnboundedSender<PackageItem>,
536 tokio::sync::mpsc::UnboundedSender<String>,
537 tokio::sync::mpsc::UnboundedSender<PkgbuildCheckRequest>,
538 );
539
540 type SetupAppResult = (AppState, AppChannels);
544
545 fn setup_app_with_translations() -> SetupAppResult {
555 use std::collections::HashMap;
556 let mut app = AppState::default();
557 let mut translations = HashMap::new();
558 translations.insert(
559 "app.optional_deps.categories.editor".to_string(),
560 "Editor".to_string(),
561 );
562 translations.insert(
563 "app.optional_deps.categories.terminal".to_string(),
564 "Terminal".to_string(),
565 );
566 translations.insert(
567 "app.optional_deps.categories.clipboard".to_string(),
568 "Clipboard".to_string(),
569 );
570 translations.insert(
571 "app.optional_deps.categories.aur_helper".to_string(),
572 "AUR Helper".to_string(),
573 );
574 translations.insert(
575 "app.optional_deps.categories.security".to_string(),
576 "Security".to_string(),
577 );
578 app.translations.clone_from(&translations);
579 app.translations_fallback = translations;
580 let (qtx, _qrx) = mpsc::unbounded_channel();
581 let (dtx, _drx) = mpsc::unbounded_channel();
582 let (ptx, _prx) = mpsc::unbounded_channel();
583 let (atx, _arx) = mpsc::unbounded_channel();
584 let (pkgb_tx, _pkgb_rx) = mpsc::unbounded_channel();
585 let (comments_tx, _comments_rx) = mpsc::unbounded_channel();
586 let (pkgb_check_tx, _pkgb_check_rx) = mpsc::unbounded_channel::<PkgbuildCheckRequest>();
587 (
588 app,
589 (qtx, dtx, ptx, atx, pkgb_tx, comments_tx, pkgb_check_tx),
590 )
591 }
592
593 fn open_optional_deps_modal(app: &mut AppState, channels: &AppChannels) {
604 app.options_button_rect = Some((5, 5, 12, 1));
605 let click_options = CEvent::Mouse(crossterm::event::MouseEvent {
606 kind: crossterm::event::MouseEventKind::Down(crossterm::event::MouseButton::Left),
607 column: 6,
608 row: 5,
609 modifiers: KeyModifiers::empty(),
610 });
611 let _ = super::handle_event(
612 &click_options,
613 app,
614 &channels.0,
615 &channels.1,
616 &channels.2,
617 &channels.3,
618 &channels.4,
619 &channels.5,
620 &channels.6,
621 );
622 assert!(app.options_menu_open);
623
624 let mut key_three_event =
628 crossterm::event::KeyEvent::new(KeyCode::Char('3'), KeyModifiers::empty());
629 key_three_event.kind = KeyEventKind::Press;
630 let key_three = CEvent::Key(key_three_event);
631 let _ = super::handle_event(
632 &key_three,
633 app,
634 &channels.0,
635 &channels.1,
636 &channels.2,
637 &channels.3,
638 &channels.4,
639 &channels.5,
640 &channels.6,
641 );
642 }
643
644 fn verify_optional_deps_rows(modal: &crate::state::Modal) {
654 match modal {
655 crate::state::Modal::OptionalDeps { rows, .. } => {
656 let find = |prefix: &str| rows.iter().find(|r| r.label.starts_with(prefix));
657
658 let ed = find("Editor: nvim").expect("editor row nvim");
659 assert!(ed.installed, "nvim should be marked installed");
660 assert!(!ed.selectable, "installed editor should not be selectable");
661
662 let term = find("Terminal: kitty").expect("terminal row kitty");
663 assert!(term.installed, "kitty should be marked installed");
664 assert!(
665 !term.selectable,
666 "installed terminal should not be selectable"
667 );
668
669 let clip = find("Clipboard: xclip").expect("clipboard xclip row");
670 assert!(
671 !clip.installed,
672 "xclip should not appear installed by default"
673 );
674 assert!(
675 clip.selectable,
676 "xclip should be selectable when not installed"
677 );
678 assert_eq!(clip.note.as_deref(), Some("X11"));
679
680 let mirrors = find("Mirrors: reflector").expect("reflector row");
681 assert!(
682 !mirrors.installed,
683 "reflector should not be installed by default"
684 );
685 assert!(mirrors.selectable, "reflector should be selectable");
686
687 let paru = find("AUR Helper: paru").expect("paru row");
688 assert!(!paru.installed);
689 assert!(paru.selectable);
690 let yay = find("AUR Helper: yay").expect("yay row");
691 assert!(!yay.installed);
692 assert!(yay.selectable);
693 }
694 other => panic!("Expected OptionalDeps modal, got {other:?}"),
695 }
696 }
697
698 fn teardown_test_environment(
710 orig_path: Option<std::ffi::OsString>,
711 orig_wl: Option<std::ffi::OsString>,
712 dir: &std::path::PathBuf,
713 ) {
714 unsafe {
715 if let Some(v) = orig_path {
716 std::env::set_var("PATH", v);
717 } else {
718 std::env::remove_var("PATH");
719 }
720 if let Some(v) = orig_wl {
721 std::env::set_var("WAYLAND_DISPLAY", v);
722 } else {
723 std::env::remove_var("WAYLAND_DISPLAY");
724 }
725 }
726 let _ = std::fs::remove_dir_all(dir);
727 }
728
729 #[test]
730 fn optional_deps_rows_wayland_shows_wl_clipboard() {
735 use std::collections::HashMap;
736 use std::fs;
737 use std::path::PathBuf;
738 let _guard = crate::global_test_mutex_lock();
739
740 let mut dir: PathBuf = std::env::temp_dir();
742 dir.push(format!(
743 "pacsea_test_optional_deps_wl_{}_{}",
744 std::process::id(),
745 std::time::SystemTime::now()
746 .duration_since(std::time::UNIX_EPOCH)
747 .expect("System time is before UNIX epoch")
748 .as_nanos()
749 ));
750 let _ = fs::create_dir_all(&dir);
751
752 let orig_path = std::env::var_os("PATH");
753 unsafe {
754 std::env::set_var("PATH", dir.display().to_string());
755 std::env::set_var("PACSEA_TEST_HEADLESS", "1");
756 };
757 let orig_wl = std::env::var_os("WAYLAND_DISPLAY");
758 unsafe { std::env::set_var("WAYLAND_DISPLAY", "1") };
759
760 let mut app = AppState::default();
761 let mut translations = HashMap::new();
763 translations.insert(
764 "app.optional_deps.categories.editor".to_string(),
765 "Editor".to_string(),
766 );
767 translations.insert(
768 "app.optional_deps.categories.terminal".to_string(),
769 "Terminal".to_string(),
770 );
771 translations.insert(
772 "app.optional_deps.categories.clipboard".to_string(),
773 "Clipboard".to_string(),
774 );
775 translations.insert(
776 "app.optional_deps.categories.aur_helper".to_string(),
777 "AUR Helper".to_string(),
778 );
779 translations.insert(
780 "app.optional_deps.categories.security".to_string(),
781 "Security".to_string(),
782 );
783 app.translations.clone_from(&translations);
784 app.translations_fallback = translations;
785 let (qtx, _qrx) = mpsc::unbounded_channel();
786 let (dtx, _drx) = mpsc::unbounded_channel();
787 let (ptx, _prx) = mpsc::unbounded_channel();
788 let (atx, _arx) = mpsc::unbounded_channel();
789 let (pkgb_tx, _pkgb_rx) = mpsc::unbounded_channel();
790 let (pkgb_check_tx, _pkgb_check_rx) = mpsc::unbounded_channel::<PkgbuildCheckRequest>();
791
792 app.options_button_rect = Some((5, 5, 12, 1));
794 let click_options = CEvent::Mouse(crossterm::event::MouseEvent {
795 kind: crossterm::event::MouseEventKind::Down(crossterm::event::MouseButton::Left),
796 column: 6,
797 row: 5,
798 modifiers: KeyModifiers::empty(),
799 });
800 let (comments_tx, _comments_rx) = mpsc::unbounded_channel::<String>();
801 let _ = super::handle_event(
802 &click_options,
803 &mut app,
804 &qtx,
805 &dtx,
806 &ptx,
807 &atx,
808 &pkgb_tx,
809 &comments_tx,
810 &pkgb_check_tx,
811 );
812 assert!(app.options_menu_open);
813
814 let mut key_three_event =
816 crossterm::event::KeyEvent::new(KeyCode::Char('3'), KeyModifiers::empty());
817 key_three_event.kind = KeyEventKind::Press;
818 let key_three = CEvent::Key(key_three_event);
819 let (comments_tx, _comments_rx) = mpsc::unbounded_channel::<String>();
820 let _ = super::handle_event(
821 &key_three,
822 &mut app,
823 &qtx,
824 &dtx,
825 &ptx,
826 &atx,
827 &pkgb_tx,
828 &comments_tx,
829 &pkgb_check_tx,
830 );
831
832 match &app.modal {
833 crate::state::Modal::OptionalDeps { rows, .. } => {
834 let clip = rows
835 .iter()
836 .find(|r| r.label.starts_with("Clipboard: wl-clipboard"))
837 .expect("wl-clipboard row");
838 assert_eq!(clip.note.as_deref(), Some("Wayland"));
839 assert!(!clip.installed);
840 assert!(clip.selectable);
841 assert!(
843 !rows.iter().any(|r| r.label.starts_with("Clipboard: xclip")),
844 "xclip should not be listed on Wayland"
845 );
846 }
847 other => panic!("Expected OptionalDeps modal, got {other:?}"),
848 }
849
850 unsafe {
852 if let Some(v) = orig_path {
853 std::env::set_var("PATH", v);
854 } else {
855 std::env::remove_var("PATH");
856 }
857 if let Some(v) = orig_wl {
858 std::env::set_var("WAYLAND_DISPLAY", v);
859 } else {
860 std::env::remove_var("WAYLAND_DISPLAY");
861 }
862 }
863 let _ = fs::remove_dir_all(&dir);
864 }
865}