Skip to content

Commit d9bb1dc

Browse files
committed
Real VTE terminal to display task output
1 parent 6f6bdcc commit d9bb1dc

File tree

8 files changed

+329
-21
lines changed

8 files changed

+329
-21
lines changed

src/application.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,13 @@ mod imp {
157157
animation: pop-warning 1s;
158158
animation-iteration-count: 3;
159159
}}
160+
161+
.task-output-terminal {{
162+
padding: 8px;
163+
/* A larger radius isn't rendered properly in VTE, because the background of therminal is drawn over it */
164+
border-radius: 4px;
165+
border: 2px solid @borders;
166+
}}
160167
"));
161168
// We give the CssProvided to the default screen so the CSS rules we added
162169
// can be applied to our window.

src/dialogs/task_manager_dialog.rs

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ use gtk::glib::clone;
66
use crate::gtk_utils::reaction;
77
use crate::i18n::gettext;
88
use crate::models::{DistroboxTask, RootStore};
9+
use crate::widgets::TaskOutputTerminal;
910

10-
use gtk::glib::{Properties, derived_properties};
11+
use gtk::glib::Properties;
1112
use std::cell::RefCell;
1213

1314
mod imp {
@@ -32,13 +33,13 @@ mod imp {
3233
pub selected_task_view: adw::ToolbarView,
3334
}
3435

35-
#[derived_properties]
36+
#[glib::derived_properties]
3637
impl ObjectImpl for TaskManagerDialog {
3738
fn constructed(&self) {
3839
self.parent_constructed();
3940
let obj = self.obj();
4041
obj.set_title(&gettext("Running Tasks"));
41-
obj.set_content_width(360);
42+
obj.set_content_width(640);
4243
obj.set_content_height(640);
4344

4445
let header_bar = adw::HeaderBar::new();
@@ -259,25 +260,21 @@ impl TaskManagerDialog {
259260
update_status_ui(task);
260261
});
261262

262-
let text_view = gtk::TextView::builder()
263-
.buffer(&task.output())
264-
.editable(false)
265-
.cursor_visible(false)
266-
.wrap_mode(gtk::WrapMode::Word)
267-
.css_classes(vec!["output".to_string()])
268-
.top_margin(12)
269-
.bottom_margin(12)
270-
.left_margin(12)
271-
.right_margin(12)
272-
.build();
263+
// Create VTE terminal for output display
264+
let vte_terminal = TaskOutputTerminal::new();
265+
266+
// Restore historical output from TextBuffer if it exists
267+
let output_buffer = task.output();
268+
let buffer_text = output_buffer.text(&output_buffer.start_iter(), &output_buffer.end_iter(), false);
269+
if !buffer_text.is_empty() {
270+
vte_terminal.write_buffer(&buffer_text);
271+
}
272+
273+
task.set_vte_terminal(Some(vte_terminal.clone()));
273274

274-
let scrolled_window = gtk::ScrolledWindow::builder()
275-
.child(&text_view)
276-
.propagate_natural_height(true)
277-
.height_request(300)
278-
.vexpand(true)
279-
.build();
280-
content.append(&scrolled_window);
275+
276+
277+
content.append(&vte_terminal);
281278

282279
let button_row = gtk::Box::new(gtk::Orientation::Horizontal, 6);
283280
button_row.set_hexpand(true);

src/gtk_utils/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ use gtk::glib;
55
use gtk::prelude::*;
66
mod typed_list_store;
77
pub use typed_list_store::TypedListStore;
8+
mod terminal_colors;
9+
#[allow(unused_imports)]
10+
pub use terminal_colors::ColorPalette;
811

912
pub fn reconcile_properties<T: IsA<glib::Object>>(dest: &T, src: &T, properties: &[&str]) {
1013
for prop in dest.list_properties() {

src/gtk_utils/terminal_colors.rs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/// Terminal color scheme utilities for VTE4 with libadwaita theme support
2+
/// Provides ANSI color palettes that respect the system light/dark theme preference
3+
use gtk::gdk;
4+
use adw;
5+
use vte4::prelude::*;
6+
7+
/// ANSI color palette entry
8+
#[derive(Clone, Copy)]
9+
pub struct ColorPalette {
10+
/// Foreground text color
11+
pub foreground: gdk::RGBA,
12+
/// Background color
13+
pub background: gdk::RGBA,
14+
/// Standard ANSI colors (0-15)
15+
pub palette: [gdk::RGBA; 16],
16+
}
17+
18+
impl ColorPalette {
19+
/// Create a color palette from RGB components
20+
fn color(r: f32, g: f32, b: f32) -> gdk::RGBA {
21+
gdk::RGBA::new(r, g, b, 1.0)
22+
}
23+
24+
/// Get the dark theme color palette (similar to GNOME Terminal/Ptyxis dark)
25+
/// This palette respects libadwaita's dark theme colors
26+
pub fn dark() -> Self {
27+
Self {
28+
// Adwaita dark: text on dark background
29+
foreground: Self::color(0.92, 0.92, 0.92), // #ebebeb
30+
background: Self::color(0.1, 0.1, 0.1), // #1a1a1a
31+
palette: [
32+
// Standard colors (0-7)
33+
Self::color(0.2, 0.2, 0.2), // 0: black (darker than bg for contrast)
34+
Self::color(0.89, 0.35, 0.36), // 1: red
35+
Self::color(0.37, 0.76, 0.36), // 2: green
36+
Self::color(0.87, 0.75, 0.29), // 3: yellow
37+
Self::color(0.36, 0.62, 0.89), // 4: blue
38+
Self::color(0.76, 0.51, 0.85), // 5: magenta
39+
Self::color(0.36, 0.78, 0.85), // 6: cyan
40+
Self::color(0.82, 0.82, 0.82), // 7: white (lighter)
41+
// Bright colors (8-15)
42+
Self::color(0.5, 0.5, 0.5), // 8: bright black (gray)
43+
Self::color(1.0, 0.55, 0.56), // 9: bright red
44+
Self::color(0.56, 0.93, 0.56), // 10: bright green
45+
Self::color(1.0, 0.93, 0.56), // 11: bright yellow
46+
Self::color(0.56, 0.8, 1.0), // 12: bright blue
47+
Self::color(0.94, 0.71, 1.0), // 13: bright magenta
48+
Self::color(0.56, 0.96, 1.0), // 14: bright cyan
49+
Self::color(1.0, 1.0, 1.0), // 15: bright white
50+
],
51+
}
52+
}
53+
54+
/// Get the light theme color palette (similar to GNOME Terminal/Ptyxis light)
55+
/// This palette respects libadwaita's light theme colors
56+
pub fn light() -> Self {
57+
Self {
58+
// Adwaita light: dark text on light background
59+
foreground: Self::color(0.2, 0.2, 0.2), // #333333
60+
background: Self::color(0.98, 0.98, 0.98), // #fafafa (nearly white)
61+
palette: [
62+
// Standard colors (0-7)
63+
Self::color(0.2, 0.2, 0.2), // 0: black
64+
Self::color(0.8, 0.0, 0.0), // 1: red
65+
Self::color(0.0, 0.6, 0.0), // 2: green
66+
Self::color(0.8, 0.62, 0.0), // 3: yellow
67+
Self::color(0.13, 0.34, 0.76), // 4: blue
68+
Self::color(0.76, 0.27, 0.76), // 5: magenta
69+
Self::color(0.0, 0.6, 0.76), // 6: cyan
70+
Self::color(0.7, 0.7, 0.7), // 7: white (gray)
71+
// Bright colors (8-15)
72+
Self::color(0.5, 0.5, 0.5), // 8: bright black (gray)
73+
Self::color(1.0, 0.0, 0.0), // 9: bright red
74+
Self::color(0.0, 1.0, 0.0), // 10: bright green
75+
Self::color(1.0, 1.0, 0.0), // 11: bright yellow
76+
Self::color(0.0, 0.0, 1.0), // 12: bright blue
77+
Self::color(1.0, 0.0, 1.0), // 13: bright magenta
78+
Self::color(0.0, 1.0, 1.0), // 14: bright cyan
79+
Self::color(0.99, 0.99, 0.99), // 15: bright white
80+
],
81+
}
82+
}
83+
84+
/// Get the appropriate palette based on the current adwaita theme
85+
/// Checks the system style manager to determine if dark or light theme is active
86+
pub fn current() -> Self {
87+
let style_manager = adw::StyleManager::default();
88+
if style_manager.is_dark() {
89+
Self::dark()
90+
} else {
91+
Self::light()
92+
}
93+
}
94+
95+
/// Apply this color palette to a VTE terminal
96+
pub fn apply_to_terminal(&self, terminal: &vte4::Terminal) {
97+
let palette_refs: Vec<&gdk::RGBA> = self.palette.iter().collect();
98+
terminal.set_colors(
99+
Some(&self.foreground),
100+
Some(&self.background),
101+
&palette_refs,
102+
);
103+
}
104+
}
105+
106+
#[cfg(test)]
107+
mod tests {
108+
use super::*;
109+
110+
#[test]
111+
fn test_dark_palette() {
112+
let palette = ColorPalette::dark();
113+
assert_ne!(palette.foreground, palette.background);
114+
assert_eq!(palette.palette.len(), 16);
115+
}
116+
117+
#[test]
118+
fn test_light_palette() {
119+
let palette = ColorPalette::light();
120+
assert_ne!(palette.foreground, palette.background);
121+
assert_eq!(palette.palette.len(), 16);
122+
}
123+
}

src/models/distrobox_task.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::future::Future;
99
use tracing::{debug, info, warn};
1010

1111
use crate::fakers::Child;
12+
use crate::widgets::TaskOutputTerminal;
1213

1314
/// Status of a DistroboxTask
1415
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, glib::Enum)]
@@ -39,6 +40,7 @@ mod imp {
3940
pub status: RefCell<TaskStatus>,
4041
pub error: RefCell<Option<anyhow::Error>>, // set only if status is Failed
4142
pub cancellable: RefCell<Option<gtk::gio::Cancellable>>,
43+
pub vte_terminal: RefCell<Option<TaskOutputTerminal>>, // Optional VTE terminal
4244
}
4345

4446
#[glib::derived_properties]
@@ -114,8 +116,14 @@ impl DistroboxTask {
114116
let mut err_lines = BufReader::new(stderr).lines();
115117

116118
let insert_line = |line: String| {
119+
// Write to TextBuffer
117120
self.output().insert(&mut self.output().end_iter(), &line);
118121
self.output().insert(&mut self.output().end_iter(), "\n");
122+
123+
// Write to VTE terminal if available
124+
if let Some(vte) = self.imp().vte_terminal.borrow().as_ref() {
125+
vte.write_line(&line);
126+
}
119127
};
120128

121129
let mut cancel_rx = cancel_rx.fuse();
@@ -191,4 +199,14 @@ impl DistroboxTask {
191199
pub fn error_message(&self) -> Option<String> {
192200
self.imp().error.borrow().as_ref().map(|e| e.to_string())
193201
}
202+
203+
/// Set the VTE terminal for this task to write output to
204+
pub fn set_vte_terminal(&self, terminal: Option<TaskOutputTerminal>) {
205+
*self.imp().vte_terminal.borrow_mut() = terminal;
206+
}
207+
208+
/// Get the current VTE terminal if one is attached
209+
pub fn vte_terminal(&self) -> Option<TaskOutputTerminal> {
210+
self.imp().vte_terminal.borrow().clone()
211+
}
194212
}

src/widgets/integrated_terminal.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use gtk::{
77
use vte4::prelude::*;
88

99
use crate::i18n::gettext;
10+
use crate::gtk_utils::ColorPalette;
1011
use crate::models::Container;
1112

1213
mod imp {
@@ -75,6 +76,14 @@ impl IntegratedTerminal {
7576
let terminal = &imp.terminal;
7677
let reload_button = &imp.reload_button;
7778

79+
// Configure terminal appearance
80+
terminal.set_scroll_on_output(true);
81+
terminal.set_scroll_on_keystroke(true);
82+
83+
// Apply the current theme's color palette
84+
let palette = ColorPalette::current();
85+
palette.apply_to_terminal(terminal);
86+
7887
// Create context menu actions
7988
let action_group = gio::SimpleActionGroup::new();
8089

src/widgets/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod image_row_item;
44
mod integrated_terminal;
55
mod sidebar_row;
66
mod tasks_button;
7+
mod task_output_terminal;
78
mod terminal_combo_row;
89
mod welcome_view;
910
mod window;
@@ -14,6 +15,7 @@ pub use image_row_item::ImageRowItem;
1415
pub use integrated_terminal::IntegratedTerminal;
1516
pub use sidebar_row::SidebarRow;
1617
pub use tasks_button::TasksButton;
18+
pub use task_output_terminal::TaskOutputTerminal;
1719
pub use terminal_combo_row::TerminalComboRow;
1820
pub use welcome_view::WelcomeView;
1921
pub use window::DistroShelfWindow;

0 commit comments

Comments
 (0)