From b93afb62e9600116b22a0f89a0d82239350f0a1c Mon Sep 17 00:00:00 2001 From: Gabriele Musco Date: Sun, 7 Jan 2024 14:05:19 +0000 Subject: [PATCH] feat!: use vte for debug view log --- Cargo.lock | 56 +++++++- Cargo.toml | 1 + src/ui/debug_view.rs | 201 +++++++++++------------------ src/ui/main_view.rs | 9 +- src/ui/steam_launch_options_box.rs | 6 +- src/ui/util.rs | 13 +- 6 files changed, 144 insertions(+), 142 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f48193a..22fa4e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,6 +304,7 @@ dependencies = [ "sourceview5", "tracker", "uuid", + "zoha-vte4", ] [[package]] @@ -591,9 +592,9 @@ checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "gio" -version = "0.18.2" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57052f84e8e5999b258e8adf8f5f2af0ac69033864936b8b6838321db2f759b1" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" dependencies = [ "futures-channel", "futures-core", @@ -638,9 +639,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.18.2" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c316afb01ce8067c5eaab1fc4f2cd47dc21ce7b6296358605e2ffab23ccbd19" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ "bitflags 2.4.0", "futures-channel", @@ -961,6 +962,17 @@ dependencies = [ "hashbrown 0.14.0", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + [[package]] name = "ipnet" version = "2.8.0" @@ -1375,9 +1387,9 @@ dependencies = [ [[package]] name = "pango" -version = "0.18.0" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06a9e54b831d033206160096b825f2070cf5fda7e35167b1c01e9e774f9202d1" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" dependencies = [ "gio", "glib", @@ -2417,3 +2429,35 @@ dependencies = [ "cfg-if", "windows-sys", ] + +[[package]] +name = "zoha-vte4" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e09b58dbfab3b62c5544cafadc504db3b7d12f21ac6e55048489dbf90c979caf" +dependencies = [ + "bitflags 1.3.2", + "gdk4", + "gio", + "glib", + "gtk4", + "io-lifetimes", + "libc", + "pango", + "zoha-vte4-sys", +] + +[[package]] +name = "zoha-vte4-sys" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "749df284a2c2e68c2c359762b277854d533a4d50c7a216a3adf45cd5e5ee2993" +dependencies = [ + "gdk4-sys", + "gio-sys", + "glib", + "gtk4-sys", + "libc", + "pango-sys", + "system-deps", +] diff --git a/Cargo.toml b/Cargo.toml index 0283528..2336889 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,3 +41,4 @@ sourceview5 = { version = "0.7.1", features = [ ] } tracker = "0.2.1" uuid = { version = "1.4.1", features = ["v4", "fast-rng"] } +zoha-vte4 = { version = "0.0.2", features = ["v0_72"] } diff --git a/src/ui/debug_view.rs b/src/ui/debug_view.rs index d695a82..d0ef108 100644 --- a/src/ui/debug_view.rs +++ b/src/ui/debug_view.rs @@ -1,11 +1,12 @@ use crate::log_level::LogLevel; use crate::log_parser::MonadoLog; use crate::ui::app::{DebugOpenDataAction, DebugOpenPrefixAction}; +use crate::ui::util::copy_text; use gtk::glib::clone; use gtk::prelude::*; use relm4::prelude::*; use relm4::{ComponentSender, SimpleComponent}; -use sourceview5::prelude::*; +use zoha_vte4::{Terminal, TerminalExt}; #[derive(Debug)] pub enum SearchDirection { @@ -17,7 +18,8 @@ pub enum SearchDirection { pub enum DebugViewMsg { LogUpdated(Vec), ClearLog, - FilterLog(SearchDirection), + DoSearch, + SearchFindMatch(SearchDirection), LogLevelChanged(LogLevel), XRServiceActiveChanged(bool), SetColorScheme, @@ -27,30 +29,20 @@ pub enum DebugViewMsg { pub struct DebugView { xrservice_active: bool, #[tracker::do_not_track] - textbuf: sourceview5::Buffer, - #[tracker::do_not_track] - textview: Option, - #[tracker::do_not_track] searchbar: Option, #[tracker::do_not_track] search_entry: Option, #[tracker::do_not_track] dropdown: Option, #[tracker::do_not_track] - scrolledwin: Option, - #[tracker::do_not_track] - search_ctx: sourceview5::SearchContext, - #[tracker::do_not_track] - search_settings: sourceview5::SearchSettings, - #[tracker::do_not_track] - search_mark: Option, - #[tracker::do_not_track] log_level: LogLevel, + #[tracker::do_not_track] + vte_terminal: Terminal, } pub struct DebugViewInit {} -const MAX_SCROLLBACK: i32 = 2000; +const MAX_SCROLLBACK: u32 = 2000; #[relm4::component(pub)] impl SimpleComponent for DebugView { @@ -68,11 +60,10 @@ impl SimpleComponent for DebugView { } view! { - gtk::Box { - set_orientation: gtk::Orientation::Vertical, + adw::ToolbarView { set_hexpand: true, set_vexpand: true, - adw::HeaderBar { + add_top_bar: hb = &adw::HeaderBar { set_hexpand: true, set_vexpand: false, add_css_class: "flat", @@ -87,8 +78,7 @@ impl SimpleComponent for DebugView { }, pack_start: &log_level_dropdown, }, - #[name(searchbar)] - gtk::SearchBar { + add_top_bar: searchbar = >k::SearchBar { set_margin_start: 1, set_hexpand: true, #[chain(flags(gtk::glib::BindingFlags::BIDIRECTIONAL).build())] @@ -102,44 +92,34 @@ impl SimpleComponent for DebugView { gtk::SearchEntry { set_hexpand: true, connect_changed[sender] => move |_| { - sender.input(Self::Input::FilterLog(SearchDirection::Forward)); + sender.input(Self::Input::DoSearch); }, connect_activate[sender] => move |_| { - sender.input(Self::Input::FilterLog(SearchDirection::Forward)); + sender.input(Self::Input::SearchFindMatch(SearchDirection::Forward)); }, }, gtk::Button { set_icon_name: "go-up-symbolic", set_tooltip_text: Some("Previous Match"), connect_clicked[sender] => move |_| { - sender.input(Self::Input::FilterLog(SearchDirection::Backward)) + sender.input(Self::Input::SearchFindMatch(SearchDirection::Backward)) }, }, gtk::Button { set_icon_name: "go-down-symbolic", set_tooltip_text: Some("Next Match"), connect_clicked[sender] => move |_| { - sender.input(Self::Input::FilterLog(SearchDirection::Forward)) + sender.input(Self::Input::SearchFindMatch(SearchDirection::Forward)) }, }, }, connect_entry: &search_entry, }, - #[name(scrolledwin)] - gtk::ScrolledWindow { + #[wrap(Some)] + set_content: sw = >k::ScrolledWindow { set_hexpand: true, set_vexpand: true, - add_css_class: "undershoot-top", - #[name(textview)] - sourceview5::View { - add_css_class: "sourceview-transparent-bg", - set_margin_start: 1, - set_hexpand: true, - set_vexpand: true, - set_editable: false, - set_monospace: true, - set_buffer: Some(&model.textbuf), - }, + model.vte_terminal.clone(), } } } @@ -157,85 +137,57 @@ impl SimpleComponent for DebugView { Self::Input::LogLevelChanged(lvl) => { self.log_level = lvl; } - Self::Input::FilterLog(direction) => { + Self::Input::DoSearch => { let searchbar = self.searchbar.as_ref().unwrap().clone(); let search_entry = self.search_entry.as_ref().unwrap().clone(); let search_text = search_entry.text().to_string(); if searchbar.is_search_mode() && !search_text.is_empty() { - self.search_settings - .set_search_text(Some(search_text.as_str())); - self.search_mark = Some(self.textbuf.get_insert()); - let mut iter = self - .textbuf - .iter_at_mark(self.search_mark.as_ref().unwrap()); - iter.forward_char(); - let search_res = match direction { - SearchDirection::Forward => self.search_ctx.forward(&iter), - SearchDirection::Backward => self.search_ctx.backward(&iter), - }; - match search_res { - None => { - // TODO: mark search entry red - } - Some((start, end, _)) => { - self.textbuf.move_mark( - self.search_mark.as_ref().unwrap(), - match direction { - SearchDirection::Forward => &end, - SearchDirection::Backward => &start, - }, - ); - self.textbuf.select_range(&start, &end); - self.textview - .as_ref() - .unwrap() - .scroll_mark_onscreen(&self.textbuf.create_mark(None, &end, false)); - } + if let Ok(regex) = zoha_vte4::Regex::for_search(&search_text, 0) { + self.vte_terminal.search_set_regex(Some(®ex), 0); } } else { - self.search_settings.set_search_text(None); + self.vte_terminal.search_set_regex(None, 0); } } + Self::Input::SearchFindMatch(direction) => match direction { + SearchDirection::Forward => { + self.vte_terminal.search_find_next(); + } + SearchDirection::Backward => { + self.vte_terminal.search_find_previous(); + } + }, Self::Input::LogUpdated(n_log) => { for row in n_log { let txt = match MonadoLog::new_from_str(row.as_str()) { Some(o) => match o.level >= self.log_level { false => None, true => Some(format!( - "{lvl}\t[{file}:{func}]\n\t{msg}\n", + "{lvl}\t[{file}:{func}]\r\n\t{msg}\r\n", lvl = o.level, file = o.file, func = o.func, - msg = o.message + msg = o.message.replace("\n", "\r\n") )), }, None => Some(row), }; if let Some(t) = txt { - self.textbuf - .insert(&mut self.textbuf.end_iter(), t.as_str()); + self.vte_terminal.feed(t.as_bytes()) } } - if !self.searchbar.as_ref().unwrap().is_search_mode() { - let textbuf = self.textbuf.clone(); - let textview = self.textview.as_ref().unwrap().clone(); - gtk::glib::idle_add_local_once(move || { - let end_mark = textbuf.create_mark(None, &textbuf.end_iter(), false); - textview.scroll_mark_onscreen(&end_mark); - }); - } - while self.textbuf.line_count() > MAX_SCROLLBACK { - let mut start = self.textbuf.start_iter(); - let mut end = self.textbuf.start_iter(); - end.forward_line(); - self.textbuf.delete(&mut start, &mut end); - } } Self::Input::ClearLog => { - self.textbuf.set_text(""); + self.vte_terminal.feed("\x1bc".as_bytes()); } Self::Input::SetColorScheme => { - Self::set_color_scheme(&self.textbuf); + if adw::StyleManager::default().is_dark() { + self.vte_terminal + .set_color_foreground(>k::gdk::RGBA::new(1.0, 1.0, 1.0, 1.0)); + } else { + self.vte_terminal + .set_color_foreground(>k::gdk::RGBA::new(0.0, 0.0, 0.0, 1.0)); + } } } } @@ -245,19 +197,6 @@ impl SimpleComponent for DebugView { root: &Self::Root, sender: ComponentSender, ) -> ComponentParts { - let textbuf = sourceview5::Buffer::builder() - .highlight_syntax(false) - .enable_undo(false) - .build(); - let search_settings = sourceview5::SearchSettings::builder() - .wrap_around(true) - .case_sensitive(false) - .build(); - let search_ctx = sourceview5::SearchContext::builder() - .buffer(&textbuf) - .settings(&search_settings) - .build(); - let log_level_dropdown = gtk::DropDown::from_strings( LogLevel::iter() .map(|lvl| lvl.to_string()) @@ -282,46 +221,56 @@ impl SimpleComponent for DebugView { adw::StyleManager::default().connect_dark_notify(clone!(@strong sender => move |_| { sender.input(Self::Input::SetColorScheme); })); - Self::set_color_scheme(&textbuf); let mut model = Self { xrservice_active: false, tracker: 0, - textbuf, - textview: None, searchbar: None, search_entry: None, dropdown: None, - scrolledwin: None, - search_settings, - search_ctx, - search_mark: None, log_level: LogLevel::Trace, + vte_terminal: { + let t = Terminal::builder() + .scroll_on_output(true) + .scrollback_lines(MAX_SCROLLBACK) + .scroll_unit_is_pixels(true) + .vexpand(true) + .hexpand(true) + .build(); + t.set_clear_background(false); + t.search_set_wrap_around(true); + t + }, }; + { + let sc = gtk::ShortcutController::new(); + let term = model.vte_terminal.clone(); + sc.add_shortcut(gtk::Shortcut::new( + gtk::ShortcutTrigger::parse_string("c"), + Some(gtk::CallbackAction::new(move |_, _| { + if let Some(text) = term.text_selected(zoha_vte4::Format::Text) { + copy_text(text.as_str()); + } + true + })), + )); + let term = model.vte_terminal.clone(); + sc.add_shortcut(gtk::Shortcut::new( + gtk::ShortcutTrigger::parse_string("a"), + Some(gtk::CallbackAction::new(move |_, _| { + term.select_all(); + true + })), + )); + model.vte_terminal.add_controller(sc); + } + let widgets = view_output!(); model.searchbar = Some(widgets.searchbar.clone()); model.search_entry = Some(widgets.search_entry.clone()); - model.textview = Some(widgets.textview.clone()); model.dropdown = Some(log_level_dropdown.clone()); - model.scrolledwin = Some(widgets.scrolledwin.clone()); ComponentParts { model, widgets } } } - -impl DebugView { - fn set_color_scheme(textbuf: &sourceview5::Buffer) { - let sourceview_scheme_name = if adw::StyleManager::default().is_dark() { - "Adwaita-dark" - } else { - "Adwaita" - }; - if let Some(scheme) = &sourceview5::StyleSchemeManager::new().scheme(sourceview_scheme_name) - { - textbuf.set_style_scheme(Some(scheme)); - } else { - println!("gtksourceview style scheme not found") - } - } -} diff --git a/src/ui/main_view.rs b/src/ui/main_view.rs index de61d76..6d6acb0 100644 --- a/src/ui/main_view.rs +++ b/src/ui/main_view.rs @@ -18,7 +18,7 @@ use crate::ui::app::{ }; use crate::ui::profile_editor::ProfileEditorInit; use crate::ui::steamvr_calibration_box::SteamVrCalibrationBoxMsg; -use crate::ui::util::{limit_dropdown_width, warning_heading}; +use crate::ui::util::{copy_text, limit_dropdown_width, warning_heading}; use crate::xr_devices::XRDevice; use gtk::prelude::*; use relm4::adw::traits::MessageDialogExt; @@ -320,10 +320,9 @@ impl SimpleComponent for MainView { set_valign: gtk::Align::Center, connect_clicked => move |_| { if let Some(GpuSysDrm::Amd(d)) = get_first_amd_gpu() { - gtk::gdk::Display::default() - .expect("Could not find default display") - .clipboard() - .set_text(get_set_amd_vr_pow_prof_cmd(d.as_str()).as_str()); + copy_text( + &get_set_amd_vr_pow_prof_cmd(d.as_str()) + ) } } }, diff --git a/src/ui/steam_launch_options_box.rs b/src/ui/steam_launch_options_box.rs index f1d2321..848a733 100644 --- a/src/ui/steam_launch_options_box.rs +++ b/src/ui/steam_launch_options_box.rs @@ -1,3 +1,4 @@ +use super::util::copy_text; use crate::{constants::APP_NAME, profile::Profile}; use gtk::prelude::*; use relm4::prelude::*; @@ -100,10 +101,7 @@ impl SimpleComponent for SteamLaunchOptionsBox { self.set_launch_options(prof.get_steam_launch_options()); } Self::Input::CopyLaunchOptions => { - gtk::gdk::Display::default() - .expect("Could not find default display") - .clipboard() - .set_text(self.launch_options.as_str()); + copy_text(self.launch_options.as_str()); } } } diff --git a/src/ui/util.rs b/src/ui/util.rs index 17906d0..c1826f7 100644 --- a/src/ui/util.rs +++ b/src/ui/util.rs @@ -1,4 +1,4 @@ -use gtk4::{gio, prelude::*}; +use gtk4::{gdk, gio, prelude::*}; pub fn limit_dropdown_width(dd: >k4::DropDown, chars: i32) { let mut dd_child = dd @@ -50,3 +50,14 @@ pub fn open_with_default_handler(uri: &str) { eprintln!("Error opening uri {}: {}", uri, e) }; } + +pub fn copy_text(txt: &str) { + match gdk::Display::default() { + None => { + eprintln!("Warning: could not get default gdk display") + } + Some(d) => { + d.clipboard().set_text(txt); + } + } +}