Skip to content

Commit 1be65b7

Browse files
CahbOleksandr Mazur
authored andcommitted
fix: clipboard content lost on Linux after copy
ClipboardContext was created locally and dropped immediately, causing the app to lose X11/Wayland selection ownership before any paste. Move the context into RootComponent as a persistent field for the app's lifetime. Route RecordsComponent's direct clipboard call through Action::CopyToClipboard so all copy paths go via one place. Signed-off-by: Oleksandr Mazur <cahbua@gmail.com>
1 parent 38b7c6c commit 1be65b7

3 files changed

Lines changed: 11 additions & 13 deletions

File tree

crates/tui/src/component/records_component.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
//! Component showing in real time incoming kafka records.
22
33
use app::{configuration::TimestampFormat, search::ValidSearchQuery};
4-
use copypasta::{ClipboardContext, ClipboardProvider};
54
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
65
use lib::ExportedKafkaRecord;
76
use ratatui::{
@@ -17,7 +16,6 @@ use tokio::sync::mpsc::UnboundedSender;
1716

1817
use crate::{
1918
Action,
20-
action::{Level, Notification},
2119
component::RecordsReceiver,
2220
error::TuiError,
2321
records_buffer::RecordsBuffer,
@@ -235,13 +233,13 @@ impl Component for RecordsComponent {
235233
KeyCode::Char('c') => {
236234
if let Some(s) = self.state.selected() {
237235
let record = self.records.get(s).unwrap();
238-
let mut ctx = ClipboardContext::new().unwrap();
239236
let exported_record: ExportedKafkaRecord = record.into();
240-
self.action_tx.as_ref().unwrap().send(Action::Notification(
241-
Notification::new(Level::Info, "Copied to clipboard".to_string()),
242-
))?;
243-
ctx.set_contents(serde_json::to_string_pretty(&exported_record)?)
244-
.unwrap();
237+
self.action_tx
238+
.as_ref()
239+
.unwrap()
240+
.send(Action::CopyToClipboard(serde_json::to_string_pretty(
241+
&exported_record,
242+
)?))?;
245243
}
246244
}
247245
KeyCode::Char('f') => self.follow(!self.follow)?,

crates/tui/src/component/root_component.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub(crate) struct RootComponent {
3636
focus_history: Vec<ComponentName>,
3737
focus_order: Vec<ComponentName>,
3838
action_tx: Option<UnboundedSender<Action>>,
39+
clipboard: ClipboardContext,
3940
}
4041

4142
impl RootComponent {
@@ -93,6 +94,7 @@ impl RootComponent {
9394
focus_history: vec![],
9495
state,
9596
action_tx: None,
97+
clipboard: ClipboardContext::new().expect("Failed to initialize clipboard"),
9698
}
9799
}
98100

@@ -349,15 +351,14 @@ impl Component for RootComponent {
349351
self.notify_footer()?;
350352
}
351353
Action::CopyToClipboard(ref content) => {
352-
let mut ctx = ClipboardContext::new().unwrap();
353354
self.action_tx
354355
.as_ref()
355356
.unwrap()
356357
.send(Action::Notification(Notification::new(
357358
Level::Info,
358359
"Copied to clipboard".to_string(),
359360
)))?;
360-
ctx.set_contents(content.to_string()).unwrap();
361+
self.clipboard.set_contents(content.to_string()).unwrap();
361362
}
362363
_ => (),
363364
}

crates/tui/src/component/topic_details_component.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,10 @@ impl Component for TopicDetailsComponent {
100100
self.refreshing_data = false;
101101
self.details = details;
102102
}
103-
Action::RequestTopicDetails(_details) => {
104-
if !self.details.is_empty() {
103+
Action::RequestTopicDetails(_details)
104+
if !self.details.is_empty() => {
105105
self.refreshing_data = true;
106106
}
107-
}
108107
_ => (),
109108
}
110109
Ok(None)

0 commit comments

Comments
 (0)