linux: Primary clipboard (#10534)

Implements copying from and pasting to the primary selection.

Release Notes:

- N/A
This commit is contained in:
apricotbucket28 2024-04-18 18:54:18 -03:00 committed by GitHub
parent 98db7fa61e
commit b31df39ab0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 173 additions and 16 deletions

View file

@ -1828,6 +1828,29 @@ impl Editor {
old_cursor_position: &Anchor, old_cursor_position: &Anchor,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
// Copy selections to primary selection buffer
#[cfg(target_os = "linux")]
if local {
let selections = self.selections.all::<usize>(cx);
let buffer_handle = self.buffer.read(cx).read(cx);
let mut text = String::new();
for (index, selection) in selections.iter().enumerate() {
let text_for_selection = buffer_handle
.text_for_range(selection.start..selection.end)
.collect::<String>();
text.push_str(&text_for_selection);
if index != selections.len() - 1 {
text.push('\n');
}
}
if !text.is_empty() {
cx.write_to_primary(ClipboardItem::new(text));
}
}
if self.focus_handle.is_focused(cx) && self.leader_peer_id.is_none() { if self.focus_handle.is_focused(cx) && self.leader_peer_id.is_none() {
self.buffer.update(cx, |buffer, cx| { self.buffer.update(cx, |buffer, cx| {
buffer.set_active_selections( buffer.set_active_selections(

View file

@ -502,6 +502,34 @@ impl EditorElement {
cx.stop_propagation(); cx.stop_propagation();
} }
fn mouse_middle_down(
editor: &mut Editor,
event: &MouseDownEvent,
position_map: &PositionMap,
text_hitbox: &Hitbox,
cx: &mut ViewContext<Editor>,
) {
if !text_hitbox.is_hovered(cx) || editor.read_only(cx) {
return;
}
if let Some(item) = cx.read_from_primary() {
let point_for_position =
position_map.point_for_position(text_hitbox.bounds, event.position);
let position = point_for_position.previous_valid;
editor.select(
SelectPhase::Begin {
position,
add: false,
click_count: 1,
},
cx,
);
editor.insert(item.text(), cx);
}
}
fn mouse_up( fn mouse_up(
editor: &mut Editor, editor: &mut Editor,
event: &MouseUpEvent, event: &MouseUpEvent,
@ -2903,6 +2931,9 @@ impl EditorElement {
MouseButton::Right => editor.update(cx, |editor, cx| { MouseButton::Right => editor.update(cx, |editor, cx| {
Self::mouse_right_down(editor, event, &position_map, &text_hitbox, cx); Self::mouse_right_down(editor, event, &position_map, &text_hitbox, cx);
}), }),
MouseButton::Middle => editor.update(cx, |editor, cx| {
Self::mouse_middle_down(editor, event, &position_map, &text_hitbox, cx);
}),
_ => {} _ => {}
}; };
} }

View file

@ -547,11 +547,23 @@ impl AppContext {
self.platform.window_appearance() self.platform.window_appearance()
} }
/// Writes data to the primary selection buffer.
/// Only available on Linux.
pub fn write_to_primary(&self, item: ClipboardItem) {
self.platform.write_to_primary(item)
}
/// Writes data to the platform clipboard. /// Writes data to the platform clipboard.
pub fn write_to_clipboard(&self, item: ClipboardItem) { pub fn write_to_clipboard(&self, item: ClipboardItem) {
self.platform.write_to_clipboard(item) self.platform.write_to_clipboard(item)
} }
/// Reads data from the primary selection buffer.
/// Only available on Linux.
pub fn read_from_primary(&self) -> Option<ClipboardItem> {
self.platform.read_from_primary()
}
/// Reads data from the platform clipboard. /// Reads data from the platform clipboard.
pub fn read_from_clipboard(&self) -> Option<ClipboardItem> { pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
self.platform.read_from_clipboard() self.platform.read_from_clipboard()

View file

@ -151,7 +151,9 @@ pub(crate) trait Platform: 'static {
fn set_cursor_style(&self, style: CursorStyle); fn set_cursor_style(&self, style: CursorStyle);
fn should_auto_hide_scrollbars(&self) -> bool; fn should_auto_hide_scrollbars(&self) -> bool;
fn write_to_primary(&self, item: ClipboardItem);
fn write_to_clipboard(&self, item: ClipboardItem); fn write_to_clipboard(&self, item: ClipboardItem);
fn read_from_primary(&self) -> Option<ClipboardItem>;
fn read_from_clipboard(&self) -> Option<ClipboardItem>; fn read_from_clipboard(&self) -> Option<ClipboardItem>;
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>; fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;

View file

@ -79,8 +79,14 @@ impl LinuxClient for HeadlessClient {
//todo(linux) //todo(linux)
fn set_cursor_style(&self, _style: CursorStyle) {} fn set_cursor_style(&self, _style: CursorStyle) {}
fn write_to_primary(&self, item: crate::ClipboardItem) {}
fn write_to_clipboard(&self, item: crate::ClipboardItem) {} fn write_to_clipboard(&self, item: crate::ClipboardItem) {}
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
None
}
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> { fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
None None
} }

View file

@ -56,7 +56,9 @@ pub trait LinuxClient {
options: WindowParams, options: WindowParams,
) -> Box<dyn PlatformWindow>; ) -> Box<dyn PlatformWindow>;
fn set_cursor_style(&self, style: CursorStyle); fn set_cursor_style(&self, style: CursorStyle);
fn write_to_primary(&self, item: ClipboardItem);
fn write_to_clipboard(&self, item: ClipboardItem); fn write_to_clipboard(&self, item: ClipboardItem);
fn read_from_primary(&self) -> Option<ClipboardItem>;
fn read_from_clipboard(&self) -> Option<ClipboardItem>; fn read_from_clipboard(&self) -> Option<ClipboardItem>;
fn run(&self); fn run(&self);
} }
@ -406,7 +408,6 @@ impl<P: LinuxClient + 'static> Platform for P {
}) })
} }
//todo(linux): add trait methods for accessing the primary selection
fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> { fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
let url = url.to_string(); let url = url.to_string();
self.background_executor().spawn(async move { self.background_executor().spawn(async move {
@ -461,10 +462,18 @@ impl<P: LinuxClient + 'static> Platform for P {
Task::ready(Err(anyhow!("register_url_scheme unimplemented"))) Task::ready(Err(anyhow!("register_url_scheme unimplemented")))
} }
fn write_to_primary(&self, item: ClipboardItem) {
self.write_to_primary(item)
}
fn write_to_clipboard(&self, item: ClipboardItem) { fn write_to_clipboard(&self, item: ClipboardItem) {
self.write_to_clipboard(item) self.write_to_clipboard(item)
} }
fn read_from_primary(&self) -> Option<ClipboardItem> {
self.read_from_primary()
}
fn read_from_clipboard(&self) -> Option<ClipboardItem> { fn read_from_clipboard(&self) -> Option<ClipboardItem> {
self.read_from_clipboard() self.read_from_clipboard()
} }

View file

@ -377,10 +377,26 @@ impl LinuxClient for WaylandClient {
.log_err(); .log_err();
} }
fn write_to_primary(&self, item: crate::ClipboardItem) {
self.0.borrow_mut().primary.set_contents(item.text);
}
fn write_to_clipboard(&self, item: crate::ClipboardItem) { fn write_to_clipboard(&self, item: crate::ClipboardItem) {
self.0.borrow_mut().clipboard.set_contents(item.text); self.0.borrow_mut().clipboard.set_contents(item.text);
} }
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
self.0
.borrow_mut()
.primary
.get_contents()
.ok()
.map(|s| crate::ClipboardItem {
text: s,
metadata: None,
})
}
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> { fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
self.0 self.0
.borrow_mut() .borrow_mut()

View file

@ -603,10 +603,26 @@ impl LinuxClient for X11Client {
//todo(linux) //todo(linux)
fn set_cursor_style(&self, _style: CursorStyle) {} fn set_cursor_style(&self, _style: CursorStyle) {}
fn write_to_primary(&self, item: crate::ClipboardItem) {
self.0.borrow_mut().primary.set_contents(item.text);
}
fn write_to_clipboard(&self, item: crate::ClipboardItem) { fn write_to_clipboard(&self, item: crate::ClipboardItem) {
self.0.borrow_mut().clipboard.set_contents(item.text); self.0.borrow_mut().clipboard.set_contents(item.text);
} }
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
self.0
.borrow_mut()
.primary
.get_contents()
.ok()
.map(|text| crate::ClipboardItem {
text,
metadata: None,
})
}
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> { fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
self.0 self.0
.borrow_mut() .borrow_mut()

View file

@ -849,6 +849,8 @@ impl Platform for MacPlatform {
} }
} }
fn write_to_primary(&self, _item: ClipboardItem) {}
fn write_to_clipboard(&self, item: ClipboardItem) { fn write_to_clipboard(&self, item: ClipboardItem) {
let state = self.0.lock(); let state = self.0.lock();
unsafe { unsafe {
@ -886,6 +888,10 @@ impl Platform for MacPlatform {
} }
} }
fn read_from_primary(&self) -> Option<ClipboardItem> {
None
}
fn read_from_clipboard(&self) -> Option<ClipboardItem> { fn read_from_clipboard(&self) -> Option<ClipboardItem> {
let state = self.0.lock(); let state = self.0.lock();
unsafe { unsafe {

View file

@ -23,6 +23,7 @@ pub(crate) struct TestPlatform {
active_display: Rc<dyn PlatformDisplay>, active_display: Rc<dyn PlatformDisplay>,
active_cursor: Mutex<CursorStyle>, active_cursor: Mutex<CursorStyle>,
current_clipboard_item: Mutex<Option<ClipboardItem>>, current_clipboard_item: Mutex<Option<ClipboardItem>>,
current_primary_item: Mutex<Option<ClipboardItem>>,
pub(crate) prompts: RefCell<TestPrompts>, pub(crate) prompts: RefCell<TestPrompts>,
pub opened_url: RefCell<Option<String>>, pub opened_url: RefCell<Option<String>>,
weak: Weak<Self>, weak: Weak<Self>,
@ -44,6 +45,7 @@ impl TestPlatform {
active_display: Rc::new(TestDisplay::new()), active_display: Rc::new(TestDisplay::new()),
active_window: Default::default(), active_window: Default::default(),
current_clipboard_item: Mutex::new(None), current_clipboard_item: Mutex::new(None),
current_primary_item: Mutex::new(None),
weak: weak.clone(), weak: weak.clone(),
opened_url: Default::default(), opened_url: Default::default(),
}) })
@ -282,10 +284,18 @@ impl Platform for TestPlatform {
false false
} }
fn write_to_primary(&self, item: ClipboardItem) {
*self.current_primary_item.lock() = Some(item);
}
fn write_to_clipboard(&self, item: ClipboardItem) { fn write_to_clipboard(&self, item: ClipboardItem) {
*self.current_clipboard_item.lock() = Some(item); *self.current_clipboard_item.lock() = Some(item);
} }
fn read_from_primary(&self) -> Option<ClipboardItem> {
self.current_primary_item.lock().clone()
}
fn read_from_clipboard(&self) -> Option<ClipboardItem> { fn read_from_clipboard(&self) -> Option<ClipboardItem> {
self.current_clipboard_item.lock().clone() self.current_clipboard_item.lock().clone()
} }

View file

@ -692,6 +692,8 @@ impl Platform for WindowsPlatform {
false false
} }
fn write_to_primary(&self, _item: ClipboardItem) {}
fn write_to_clipboard(&self, item: ClipboardItem) { fn write_to_clipboard(&self, item: ClipboardItem) {
if item.text.len() > 0 { if item.text.len() > 0 {
let mut ctx = ClipboardContext::new().unwrap(); let mut ctx = ClipboardContext::new().unwrap();
@ -699,6 +701,10 @@ impl Platform for WindowsPlatform {
} }
} }
fn read_from_primary(&self) -> Option<ClipboardItem> {
None
}
fn read_from_clipboard(&self) -> Option<ClipboardItem> { fn read_from_clipboard(&self) -> Option<ClipboardItem> {
let mut ctx = ClipboardContext::new().unwrap(); let mut ctx = ClipboardContext::new().unwrap();
let content = ctx.get_contents().unwrap(); let content = ctx.get_contents().unwrap();

View file

@ -750,6 +750,11 @@ impl Terminal {
InternalEvent::SetSelection(selection) => { InternalEvent::SetSelection(selection) => {
term.selection = selection.as_ref().map(|(sel, _)| sel.clone()); term.selection = selection.as_ref().map(|(sel, _)| sel.clone());
#[cfg(target_os = "linux")]
if let Some(selection_text) = term.selection_to_string() {
cx.write_to_primary(ClipboardItem::new(selection_text));
}
if let Some((_, head)) = selection { if let Some((_, head)) = selection {
self.selection_head = Some(*head); self.selection_head = Some(*head);
} }
@ -766,6 +771,11 @@ impl Terminal {
selection.update(point, side); selection.update(point, side);
term.selection = Some(selection); term.selection = Some(selection);
#[cfg(target_os = "linux")]
if let Some(selection_text) = term.selection_to_string() {
cx.write_to_primary(ClipboardItem::new(selection_text));
}
self.selection_head = Some(point); self.selection_head = Some(point);
cx.emit(Event::SelectionsChanged) cx.emit(Event::SelectionsChanged)
} }
@ -1192,7 +1202,12 @@ impl Terminal {
Some(scroll_delta) Some(scroll_delta)
} }
pub fn mouse_down(&mut self, e: &MouseDownEvent, origin: Point<Pixels>) { pub fn mouse_down(
&mut self,
e: &MouseDownEvent,
origin: Point<Pixels>,
cx: &mut ModelContext<Self>,
) {
let position = e.position - origin; let position = e.position - origin;
let point = grid_point( let point = grid_point(
position, position,
@ -1229,6 +1244,11 @@ impl Terminal {
self.events self.events
.push_back(InternalEvent::SetSelection(Some((sel, point)))); .push_back(InternalEvent::SetSelection(Some((sel, point))));
} }
} else if e.button == MouseButton::Middle {
if let Some(item) = cx.read_from_primary() {
let text = item.text().to_string();
self.input(text);
}
} }
} }

View file

@ -429,7 +429,7 @@ impl TerminalElement {
move |e, cx| { move |e, cx| {
cx.focus(&focus); cx.focus(&focus);
terminal.update(cx, |terminal, cx| { terminal.update(cx, |terminal, cx| {
terminal.mouse_down(&e, origin); terminal.mouse_down(&e, origin, cx);
cx.notify(); cx.notify();
}) })
} }
@ -479,6 +479,17 @@ impl TerminalElement {
}, },
), ),
); );
self.interactivity.on_mouse_down(
MouseButton::Middle,
TerminalElement::generic_button_handler(
terminal.clone(),
origin,
focus.clone(),
move |terminal, origin, e, cx| {
terminal.mouse_down(&e, origin, cx);
},
),
);
self.interactivity.on_scroll_wheel({ self.interactivity.on_scroll_wheel({
let terminal = terminal.clone(); let terminal = terminal.clone();
move |e, cx| { move |e, cx| {
@ -498,19 +509,8 @@ impl TerminalElement {
terminal.clone(), terminal.clone(),
origin, origin,
focus.clone(), focus.clone(),
move |terminal, origin, e, _cx| { move |terminal, origin, e, cx| {
terminal.mouse_down(&e, origin); terminal.mouse_down(&e, origin, cx);
},
),
);
self.interactivity.on_mouse_down(
MouseButton::Middle,
TerminalElement::generic_button_handler(
terminal.clone(),
origin,
focus.clone(),
move |terminal, origin, e, _cx| {
terminal.mouse_down(&e, origin);
}, },
), ),
); );