#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![allow(rustdoc::missing_crate_level_docs)] use eframe::egui; fn main() -> eframe::Result { env_logger::init(); let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default().with_inner_size([800.0, 600.0]), ..Default::default() }; eframe::run_native( "Text Input Test - IBus/Wayland", options, Box::new(|_cc| Ok(Box::::default())), ) } struct TestApp { // Single-line text inputs single_line_1: String, single_line_2: String, // Multi-line text input multi_line: String, // Code editor content code: String, // Password field password: String, // Input event log event_log: Vec, } impl Default for TestApp { fn default() -> Self { Self { single_line_1: String::new(), single_line_2: "Pre-filled text".to_owned(), multi_line: "Type multiple lines here.\nLine 2\nLine 3".to_owned(), code: r#"fn main() { println!("Hello, world!"); } "#.to_owned(), password: String::new(), event_log: Vec::new(), } } } impl eframe::App for TestApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { // Log input events ctx.input(|i| { for event in &i.events { match event { egui::Event::Text(text) => { self.event_log.push(format!(">>> TEXT: {:?}", text)); } egui::Event::Key { key, pressed, modifiers, .. } => { self.event_log.push(format!("KEY: {:?} pressed={} (mods: {:?})", key, pressed, modifiers)); } egui::Event::Ime(ime) => { // Filter out the spammy Disabled events match ime { egui::ImeEvent::Disabled => {} // Skip logging these _ => self.event_log.push(format!("IME: {:?}", ime)), } } egui::Event::Paste(text) => { self.event_log.push(format!("PASTE: {:?}", text)); } _ => {} } } }); // Keep event log manageable if self.event_log.len() > 100 { self.event_log.drain(0..50); } egui::SidePanel::right("event_log").show(ctx, |ui| { ui.heading("Input Events"); if ui.button("Clear").clicked() { self.event_log.clear(); } ui.separator(); egui::ScrollArea::vertical().show(ui, |ui| { for event in self.event_log.iter().rev() { ui.label(event); } }); }); egui::CentralPanel::default().show(ctx, |ui| { ui.heading("Text Input Test - IBus/Wayland Bug"); ui.label("Test various text input widgets. If IBus under Wayland is broken, typing won't work."); // Show environment info ui.add_space(5.0); ui.group(|ui| { ui.label("Environment Detection:"); let session_type = std::env::var("XDG_SESSION_TYPE").unwrap_or_else(|_| "unknown".to_string()); let wayland_display = std::env::var("WAYLAND_DISPLAY").is_ok(); let gtk_im = std::env::var("GTK_IM_MODULE").unwrap_or_else(|_| "not set".to_string()); let qt_im = std::env::var("QT_IM_MODULE").unwrap_or_else(|_| "not set".to_string()); let xmodifiers = std::env::var("XMODIFIERS").unwrap_or_else(|_| "not set".to_string()); let ime_override = std::env::var("EGUI_IME_DISABLED").unwrap_or_else(|_| "not set".to_string()); ui.label(format!(" XDG_SESSION_TYPE: {session_type}")); ui.label(format!(" WAYLAND_DISPLAY set: {wayland_display}")); ui.label(format!(" GTK_IM_MODULE: {gtk_im}")); ui.label(format!(" QT_IM_MODULE: {qt_im}")); ui.label(format!(" XMODIFIERS: {xmodifiers}")); ui.label(format!(" EGUI_IME_DISABLED: {ime_override}")); let is_wayland = session_type == "wayland" || wayland_display; let is_ibus = gtk_im == "ibus" || qt_im == "ibus" || xmodifiers.contains("ibus"); if is_wayland && is_ibus { ui.colored_label(egui::Color32::GREEN, "IBus on Wayland detected - IME workaround is active!"); } }); ui.separator(); egui::ScrollArea::vertical().show(ui, |ui| { ui.group(|ui| { ui.heading("Single-line Text Edits"); ui.horizontal(|ui| { ui.label("Empty field:"); ui.text_edit_singleline(&mut self.single_line_1); }); ui.horizontal(|ui| { ui.label("Pre-filled:"); ui.text_edit_singleline(&mut self.single_line_2); }); }); ui.add_space(10.0); ui.group(|ui| { ui.heading("Password Field"); ui.horizontal(|ui| { ui.label("Password:"); ui.add(egui::TextEdit::singleline(&mut self.password).password(true)); }); }); ui.add_space(10.0); ui.group(|ui| { ui.heading("Multi-line Text Edit"); ui.add( egui::TextEdit::multiline(&mut self.multi_line) .desired_width(f32::INFINITY) .desired_rows(6) ); }); ui.add_space(10.0); ui.group(|ui| { ui.heading("Code Editor (with syntax highlighting)"); let theme = egui_extras::syntax_highlighting::CodeTheme::from_memory(ui.ctx(), ui.style()); let mut layouter = |ui: &egui::Ui, string: &dyn egui::TextBuffer, wrap_width: f32| { let mut layout_job = egui_extras::syntax_highlighting::highlight( ui.ctx(), ui.style(), &theme, string.as_str(), "rs", ); layout_job.wrap.max_width = wrap_width; ui.fonts_mut(|f| f.layout_job(layout_job)) }; ui.add( egui::TextEdit::multiline(&mut self.code) .font(egui::TextStyle::Monospace) .code_editor() .desired_width(f32::INFINITY) .desired_rows(10) .layouter(&mut layouter) ); }); ui.add_space(10.0); ui.group(|ui| { ui.heading("Current Values"); ui.label(format!("Single line 1: {:?}", self.single_line_1)); ui.label(format!("Single line 2: {:?}", self.single_line_2)); ui.label(format!("Password length: {}", self.password.len())); ui.label(format!("Multi-line lines: {}", self.multi_line.lines().count())); ui.label(format!("Code lines: {}", self.code.lines().count())); }); }); }); } }